mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Merge branch 'v0.7.0' into v0.8.0
This commit is contained in:
@@ -16,32 +16,51 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""The entry module for IfcOpenShell
|
||||
"""Welcome to IfcOpenShell! IfcOpenShell provides a way to read and write IFCs.
|
||||
|
||||
Typically used for opening an IFC via a filepath, or accessing one of the
|
||||
submodules.
|
||||
IfcOpenShell can open IFC files, read entities (such as walls, buildings,
|
||||
properties, systems, etc), edit attributes, write out ``.ifc`` files and more.
|
||||
|
||||
This module provides primitive functions to interact with IFC, including:
|
||||
|
||||
- For most users, you can open and read IFC models, see docs for :func:`open`.
|
||||
This returns an :class:`file` object representing the IFC model. You can then
|
||||
query the model to filter elements.
|
||||
- For developers, you can query the schema itself, see docs for
|
||||
:func:`schema_by_name`. This returns a schema object which you can use to
|
||||
analyse the definitions of IFC classes and data types.
|
||||
|
||||
You may also be interested in:
|
||||
|
||||
- For model authoring and editing operations, see :mod:`ifcopenshell.api`.
|
||||
- For extracting information from models, see :mod:`ifcopenshell.util`.
|
||||
- For processing geometry, see :mod:`ifcopenshell.geom`.
|
||||
|
||||
|
||||
For more details, consult https://docs.ifcopenshell.org/
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
import ifcopenshell
|
||||
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
||||
model = ifcopenshell.open("/path/to/model.ifc")
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
print(ifcopenshell.version) # v0.7.0-1b1fd1e6
|
||||
|
||||
model = ifcopenshell.open("/path/to/model.ifc")
|
||||
walls = model.by_type("IfcWall")
|
||||
|
||||
for wall in walls:
|
||||
print(wall.Name)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
import ifcopenshell.util.file
|
||||
|
||||
if hasattr(os, "uname"):
|
||||
platform_system = os.uname()[0].lower()
|
||||
@@ -60,22 +79,29 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "lib", p
|
||||
|
||||
try:
|
||||
from . import ifcopenshell_wrapper
|
||||
except Exception as e:
|
||||
if int(python_version_tuple[0]) == 2:
|
||||
# Only for py2, as py3 has exception chaining
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print("-" * 64)
|
||||
except Exception:
|
||||
raise ImportError("IfcOpenShell not built for '%s'" % python_distribution)
|
||||
|
||||
from . import guid
|
||||
from .file import file
|
||||
from .entity_instance import entity_instance, register_schema_attributes
|
||||
from .sql import sqlite, sqlite_entity
|
||||
|
||||
# explicitly specify available imported symbols
|
||||
# (it's a requirement for a typed library)
|
||||
__all__ = [
|
||||
"ifcopenshell_wrapper",
|
||||
"file",
|
||||
"entity_instance",
|
||||
"sqlite",
|
||||
"sqlite_entity",
|
||||
"stream",
|
||||
"stream_entity",
|
||||
]
|
||||
|
||||
try:
|
||||
from .stream import stream, stream_entity
|
||||
except: pass
|
||||
except:
|
||||
pass
|
||||
|
||||
READ_ERROR = ifcopenshell_wrapper.file_open_status.READ_ERROR
|
||||
NO_HEADER = ifcopenshell_wrapper.file_open_status.NO_HEADER
|
||||
@@ -84,19 +110,22 @@ UNSUPPORTED_SCHEMA = ifcopenshell_wrapper.file_open_status.UNSUPPORTED_SCHEMA
|
||||
|
||||
class Error(Exception):
|
||||
"""Error used when a generic problem occurs"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SchemaError(Error):
|
||||
"""Error used when an IFC schema related problem occurs"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def open(path: "os.PathLike | str", format: str = None, should_stream: bool = False) -> file:
|
||||
def open(path: Union[os.PathLike, str], format: Optional[str] = None, should_stream: bool = False) -> file:
|
||||
"""Loads an IFC dataset from a filepath
|
||||
|
||||
You can specify a file format. If no format is given, it is guessed from its extension.
|
||||
Currently supported specified format : .ifc | .ifcZIP | .ifcXML
|
||||
You can specify a file format. If no format is given, it is guessed from
|
||||
its extension. Currently supported specified format: .ifc | .ifcZIP |
|
||||
.ifcXML.
|
||||
|
||||
You can then filter by element ID, class, etc, and subscript by id or guid.
|
||||
|
||||
@@ -114,7 +143,7 @@ def open(path: "os.PathLike | str", format: str = None, should_stream: bool = Fa
|
||||
"""
|
||||
path = Path(path)
|
||||
if format is None:
|
||||
format = ifcopenshell.util.file.guess_format(path)
|
||||
format = guess_format(path)
|
||||
if format == ".ifcXML":
|
||||
f = ifcopenshell_wrapper.parse_ifcxml(str(path.absolute()))
|
||||
if f:
|
||||
@@ -141,8 +170,7 @@ def open(path: "os.PathLike | str", format: str = None, should_stream: bool = Fa
|
||||
NO_HEADER: (Error, "Unable to parse IFC SPF header"),
|
||||
UNSUPPORTED_SCHEMA: (
|
||||
SchemaError,
|
||||
"Unsupported schema: %s"
|
||||
% ",".join(f.header.file_schema.schema_identifiers),
|
||||
"Unsupported schema: %s" % ",".join(f.header.file_schema.schema_identifiers),
|
||||
),
|
||||
}[f.good().value()]
|
||||
raise exc(msg)
|
||||
@@ -152,7 +180,7 @@ def create_entity(type, schema="IFC4", *args, **kwargs):
|
||||
"""Creates a new IFC entity that does not belong to an IFC file object
|
||||
|
||||
Note that it is more common to create entities within a existing file
|
||||
object. See :meth:`ifcopenshell.file.file.create_entity`.
|
||||
object. See :meth:`ifcopenshell.file.create_entity`.
|
||||
|
||||
:param type: Case insensitive name of the IFC class
|
||||
:type type: string
|
||||
@@ -161,7 +189,7 @@ def create_entity(type, schema="IFC4", *args, **kwargs):
|
||||
:param args: The positional arguments of the IFC class
|
||||
:param kwargs: The keyword arguments of the IFC class
|
||||
:returns: An entity instance
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
@@ -226,4 +254,35 @@ def schema_by_name(
|
||||
return ifcopenshell_wrapper.schema_by_name(schema)
|
||||
|
||||
|
||||
from .main import *
|
||||
def guess_format(path: Path) -> Union[str, None]:
|
||||
"""Guesses the IFC format using file extension
|
||||
|
||||
IFCs may be serialised as different formats. The most common is a ``.ifc``
|
||||
file, which is plaintext and stores data using the STEP Physical File
|
||||
format. IFC can also be stored as a Zipfile, XML, JSON, or SQL.
|
||||
|
||||
This will return the canonical form of the format. For example, if a path
|
||||
has the extension of .xml or .ifcxml (case insensitive), it will return
|
||||
.ifcXML.
|
||||
|
||||
Users generally won't call this function. The :func:`open` function uses
|
||||
this internally to guess the file format.
|
||||
|
||||
:return: Either .ifc, .ifcZIP, .ifcXML, .ifcJSON, .ifcSQLite, or None.
|
||||
"""
|
||||
suffix = path.suffix.lower()
|
||||
if suffix == ".ifc":
|
||||
return ".ifc"
|
||||
elif suffix in (".ifczip", ".zip"):
|
||||
return ".ifcZIP"
|
||||
elif suffix in (".ifcxml", ".xml"):
|
||||
return ".ifcXML"
|
||||
elif suffix in (".ifcjson", ".json"):
|
||||
return ".ifcJSON"
|
||||
elif suffix in (".ifcsqlite", ".sqlite", ".db"):
|
||||
return ".ifcSQLite"
|
||||
return None
|
||||
|
||||
|
||||
version = ifcopenshell_wrapper.version()
|
||||
get_log = ifcopenshell_wrapper.get_log
|
||||
|
||||
@@ -16,19 +16,39 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""High level user-oriented IFC authoring capabilities"""
|
||||
"""High level IFC authoring and editing functions
|
||||
|
||||
Authoring, editing, and deleting IFC data requires a detailed understanding of
|
||||
the rules of the IFC schema. This API module provides simple to use authoring
|
||||
functions that hide this complexity from you. Things like managing differences
|
||||
between IFC versions, tracking owernship changes, or cleaning up after orphaned
|
||||
relationships are all handled automatically.
|
||||
|
||||
If you're new to IFC authoring, start by looking at the following APIs:
|
||||
|
||||
- See :func:`ifcopenshell.api.project.create_file` to create a new IFC.
|
||||
- See :func:`ifcopenshell.api.root.create_entity` to create new entities, like
|
||||
the mandatory IfcProject, and then an IfcSite, IfcWall, etc.
|
||||
- See :func:`ifcopenshell.api.aggregate.assign_object` to create a spatial
|
||||
hierarchy.
|
||||
- See :func:`ifcopenshell.api.spatial.assign_container` to place physical
|
||||
elements (e.g. walls) inside spatial elements (e.g. building storeys).
|
||||
|
||||
Also see how to `create a simple model from scratch
|
||||
<https://docs.ifcopenshell.org/ifcopenshell-python/code_examples.html#create-a-simple-model-from-scratch>`_.
|
||||
"""
|
||||
|
||||
import json
|
||||
import numpy
|
||||
import inspect
|
||||
import importlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
from typing import Callable, Any, Optional
|
||||
from functools import partial
|
||||
|
||||
|
||||
pre_listeners = {}
|
||||
post_listeners = {}
|
||||
pre_listeners: dict[str, dict] = {}
|
||||
post_listeners: dict[str, dict] = {}
|
||||
|
||||
|
||||
def batching_argument_deprecation(
|
||||
@@ -47,6 +67,20 @@ def batching_argument_deprecation(
|
||||
return (replace_usecase or usecase_path, settings)
|
||||
|
||||
|
||||
def renamed_arguments_deprecation(
|
||||
usecase_path: str, settings: dict, arguments_remapped: dict[str, str]
|
||||
) -> tuple[str, dict]:
|
||||
for prev_argument, new_argument in arguments_remapped.items():
|
||||
if prev_argument in settings:
|
||||
print(
|
||||
f"WARNING. `{prev_argument}` argument is deprecated for API method "
|
||||
f'"{usecase_path}" and should be replaced with `{new_argument}`.'
|
||||
)
|
||||
settings = settings | {new_argument: settings[prev_argument]}
|
||||
settings.pop(prev_argument)
|
||||
return (usecase_path, settings)
|
||||
|
||||
|
||||
ARGUMENTS_DEPRECATION = {
|
||||
"spatial.assign_container": partial(
|
||||
batching_argument_deprecation, prev_argument="product", new_argument="products"
|
||||
@@ -117,50 +151,43 @@ ARGUMENTS_DEPRECATION = {
|
||||
"constraint.unassign_constraint": partial(
|
||||
batching_argument_deprecation, prev_argument="product", new_argument="products"
|
||||
),
|
||||
"project.assign_declaration": partial(
|
||||
batching_argument_deprecation, prev_argument="definition", new_argument="definitions"
|
||||
),
|
||||
"project.unassign_declaration": partial(
|
||||
batching_argument_deprecation, prev_argument="definition", new_argument="definitions"
|
||||
),
|
||||
"group.add_group": partial(
|
||||
renamed_arguments_deprecation, arguments_remapped={"Name": "name", "Description": "description"}
|
||||
),
|
||||
"layer.add_layer": partial(renamed_arguments_deprecation, arguments_remapped={"Name": "name"}),
|
||||
}
|
||||
|
||||
|
||||
CACHED_USECASE_CLASSES = dict()
|
||||
CACHED_USECASE_CLASSES: dict[str, Callable] = {}
|
||||
CACHED_USECASES: dict[str, Callable] = {}
|
||||
|
||||
|
||||
def run(
|
||||
usecase_path: str,
|
||||
ifc_file: Optional[ifcopenshell.file] = None,
|
||||
should_run_listeners=True,
|
||||
should_run_listeners: bool = True,
|
||||
**settings: Any,
|
||||
) -> Any:
|
||||
usecase_function = CACHED_USECASES.get(usecase_path)
|
||||
if not usecase_function:
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
module, usecase = usecase_path.split(".")
|
||||
usecase_function = getattr(getattr(ifcopenshell.api, module), usecase)
|
||||
CACHED_USECASES[usecase_path] = usecase_function
|
||||
if ifc_file:
|
||||
return usecase_function(ifc_file, should_run_listeners=should_run_listeners, **settings)
|
||||
return usecase_function(should_run_listeners=should_run_listeners, **settings)
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
# see #4531
|
||||
if usecase_path in ARGUMENTS_DEPRECATION:
|
||||
usecase_path, settings = ARGUMENTS_DEPRECATION[usecase_path](usecase_path, settings)
|
||||
|
||||
# TODO: settings serialization for client-server systems
|
||||
# def serialise_entity_instance(entity):
|
||||
# return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
# vcs_settings = settings.copy()
|
||||
# for key, value in settings.items():
|
||||
# if isinstance(value, ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = serialise_entity_instance(value)
|
||||
# elif isinstance(value, numpy.ndarray):
|
||||
# vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
# elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
# vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
if "add_representation" in usecase_path:
|
||||
pass
|
||||
# print(usecase_path, "{ ... settings too complex right now ... }")
|
||||
elif "owner." in usecase_path:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
# print(vcs_settings)
|
||||
# try:
|
||||
# print(usecase_path, json.dumps(vcs_settings))
|
||||
# except:
|
||||
# print(usecase_path, vcs_settings)
|
||||
|
||||
usecase_class = CACHED_USECASE_CLASSES.get(usecase_path)
|
||||
if usecase_class is None:
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
@@ -229,11 +256,8 @@ def remove_all_listeners():
|
||||
|
||||
def extract_docs(module, usecase):
|
||||
import typing
|
||||
import inspect
|
||||
import collections
|
||||
|
||||
results = []
|
||||
|
||||
inputs = collections.OrderedDict()
|
||||
|
||||
function_init = getattr(getattr(ifcopenshell.api, module), usecase).Usecase.__init__
|
||||
@@ -275,3 +299,87 @@ def extract_docs(module, usecase):
|
||||
node_data["description"] = description.strip()
|
||||
node_data["inputs"] = inputs
|
||||
return node_data
|
||||
|
||||
|
||||
def serialise_settings(settings):
|
||||
def serialise_entity_instance(entity):
|
||||
return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
|
||||
vcs_settings = settings.copy()
|
||||
for key, value in settings.items():
|
||||
if isinstance(value, ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = serialise_entity_instance(value)
|
||||
elif isinstance(value, numpy.ndarray):
|
||||
vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
else:
|
||||
try:
|
||||
vcs_settings[key] = str(value)
|
||||
except:
|
||||
vcs_settings[key] = "n/a"
|
||||
try:
|
||||
return json.dumps(vcs_settings)
|
||||
except:
|
||||
return str(vcs_settings)
|
||||
|
||||
|
||||
def wrap_usecase(usecase_path, usecase):
|
||||
"""Wraps an API function in pre/post listeners."""
|
||||
|
||||
def wrapper(*args, should_run_listeners: bool = True, **settings):
|
||||
ifc_file = args[0] if args else None
|
||||
nonlocal usecase_path
|
||||
if should_run_listeners:
|
||||
listeners = list(pre_listeners.get(usecase_path, {}).values())
|
||||
listeners += pre_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
# see #4531
|
||||
if usecase_path in ARGUMENTS_DEPRECATION:
|
||||
usecase_path, settings = ARGUMENTS_DEPRECATION[usecase_path](usecase_path, settings)
|
||||
|
||||
try:
|
||||
result = usecase(*args, **settings)
|
||||
except TypeError as e:
|
||||
if not e.args[0].startswith(f"{usecase.__name__}()"):
|
||||
# signature errors typically start with function name
|
||||
# e.g. "TypeError: edit_library() got an unexpected keyword argument 'test'"
|
||||
# otherwise it's an error inside api call and we shouldn't get in the way
|
||||
raise e
|
||||
msg = (
|
||||
f"Incorrect function arguments provided for {usecase_path}\n{str(e)}. "
|
||||
f"You specified args {args} and settings {settings}\n\n"
|
||||
f"Correct signature is {inspect.signature(usecase)}\n"
|
||||
f"See help(ifcopenshell.api.{usecase_path}) for documentation."
|
||||
)
|
||||
raise TypeError(msg) from e
|
||||
|
||||
if should_run_listeners:
|
||||
listeners = list(post_listeners.get(usecase_path, {}).values())
|
||||
listeners += post_listeners.get("*", {}).values()
|
||||
for listener in listeners:
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
return result
|
||||
|
||||
wrapper.__signature__ = inspect.signature(usecase)
|
||||
wrapper.__doc__ = usecase.__doc__
|
||||
wrapper.__name__ = usecase_path
|
||||
return wrapper
|
||||
|
||||
|
||||
def wrap_usecases(path, name):
|
||||
"""This developer feature wraps an API module's usecases with listeners."""
|
||||
import sys
|
||||
import pkgutil
|
||||
|
||||
module_name = name.split(".")[-1]
|
||||
module = sys.modules[name]
|
||||
for loader, usecase_name, is_pkg in pkgutil.iter_modules(path):
|
||||
# We may not be able to get the usecase if we are missing a dependency.
|
||||
usecase = getattr(module, usecase_name, None)
|
||||
if callable(usecase):
|
||||
usecase_path = f"{module_name}.{usecase_name}"
|
||||
setattr(module, usecase_name, wrap_usecase(usecase_path, usecase))
|
||||
|
||||
@@ -16,9 +16,20 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Aggregates are the concept of breaking down larger wholes into smaller parts.
|
||||
"""Aggregates is the concept of breaking down larger wholes into smaller parts.
|
||||
|
||||
One common use is spatial elements, such as how a site has multiple buildings,
|
||||
and a building has multiple storeys. Another is for regular elements, such as
|
||||
how a wall is made out of members and coverings.
|
||||
For example, spatial elements such as sites are broken down into one or more
|
||||
buildings, and a building is broken down into storeys. Another example is for
|
||||
physical elements, such as how a wall is made out of members and coverings.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_object import assign_object
|
||||
from .unassign_object import unassign_object
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_object",
|
||||
"unassign_object",
|
||||
]
|
||||
|
||||
@@ -18,153 +18,150 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
relating_object: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns object as an aggregate to the products
|
||||
def assign_object(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
relating_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns object as an aggregate to the products
|
||||
|
||||
All physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", where large things are made up of
|
||||
smaller things. This tree always begins at an "IfcProject" and is then
|
||||
broken down using "decomposition" relationships, of which aggregation is
|
||||
the first relationship you will use.
|
||||
All physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", where large things are made up of
|
||||
smaller things. This tree always begins at an "IfcProject" and is then
|
||||
broken down using "decomposition" relationships, of which aggregation is
|
||||
the first relationship you will use.
|
||||
|
||||
Typically used when you want to describe how large spaces are made up of
|
||||
smaller spaces. For example large spatial elements (e.g. sites,
|
||||
buidings) can be made out of smaller spatial elements (e.g. storeys,
|
||||
spaces).
|
||||
Typically used when you want to describe how large spaces are made up of
|
||||
smaller spaces. For example large spatial elements (e.g. sites,
|
||||
buidings) can be made out of smaller spatial elements (e.g. storeys,
|
||||
spaces).
|
||||
|
||||
The largest space (typically the IfcSite) can then be aggregated in a
|
||||
project. It is requirement for all spatial structures to be directly or
|
||||
indirectly aggregated back to the IfcProject to create a hierarchy of
|
||||
spaces.
|
||||
The largest space (typically the IfcSite) can then be aggregated in a
|
||||
project. It is requirement for all spatial structures to be directly or
|
||||
indirectly aggregated back to the IfcProject to create a hierarchy of
|
||||
spaces.
|
||||
|
||||
The other common usecase is when larger physical products are made up of
|
||||
smaller physical products. For example, a stair might be made out of a
|
||||
flight, a landing, a railing and so on. Or a wall might be made out of
|
||||
stud members, and coverings.
|
||||
The other common usecase is when larger physical products are made up of
|
||||
smaller physical products. For example, a stair might be made out of a
|
||||
flight, a landing, a railing and so on. Or a wall might be made out of
|
||||
stud members, and coverings.
|
||||
|
||||
As a product may only have a single location in the "spatial
|
||||
decomposition" tree, assigning an aggregate relationship will remove any
|
||||
previous aggregation, containment, or nesting relationships it may have.
|
||||
As a product may only have a single location in the "spatial
|
||||
decomposition" tree, assigning an aggregate relationship will remove any
|
||||
previous aggregation, containment, or nesting relationships it may have.
|
||||
|
||||
IFC placements follow a convention where the placement is relative to
|
||||
its parent in the spatial hierarchy. If your product has a placement,
|
||||
its placement will be recalculated to follow this convention.
|
||||
IFC placements follow a convention where the placement is relative to
|
||||
its parent in the spatial hierarchy. If your product has a placement,
|
||||
its placement will be recalculated to follow this convention.
|
||||
|
||||
:param products: The list of parts of the aggregate, typically of IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param relating_object: The whole of the aggregate, typically an
|
||||
IfcElement or IfcSpatialStructureElement subclass
|
||||
:type relating_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
||||
:param products: The list of parts of the aggregate, typically of IfcElement or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param relating_object: The whole of the aggregate, typically an
|
||||
IfcElement or IfcSpatialStructureElement subclass
|
||||
:type relating_object: ifcopenshell.entity_instance
|
||||
:return: The IfcRelAggregate relationship instance
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject")
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
|
||||
# The project contains a site (note that project aggregation is a special case in IFC)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
|
||||
# The project contains a site (note that project aggregation is a special case in IFC)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[element], relating_object=project)
|
||||
|
||||
# The site has a building
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"relating_object": relating_object,
|
||||
}
|
||||
# The site has a building
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement], relating_object=element)
|
||||
"""
|
||||
settings = {
|
||||
"products": products,
|
||||
"relating_object": relating_object,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
if not self.settings["products"]:
|
||||
return
|
||||
if not settings["products"]:
|
||||
return
|
||||
|
||||
products = set(self.settings["products"])
|
||||
relating_object = self.settings["relating_object"]
|
||||
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
|
||||
products = set(settings["products"])
|
||||
relating_object = settings["relating_object"]
|
||||
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
|
||||
|
||||
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products_without_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
products_with_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products_without_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
products_with_aggregates: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
# check if there is anything to change
|
||||
for product in products:
|
||||
product_rel = next(iter(product.Decomposes), None)
|
||||
# check if there is anything to change
|
||||
for product in products:
|
||||
product_rel = next(iter(product.Decomposes), None)
|
||||
|
||||
if product_rel is None:
|
||||
products_without_aggregates.append(product)
|
||||
continue
|
||||
if product_rel is None:
|
||||
products_without_aggregates.append(product)
|
||||
continue
|
||||
|
||||
# either is_decomposed_by is None or product is part of different rel
|
||||
if product_rel != is_decomposed_by:
|
||||
previous_aggregates_rels.add(product_rel)
|
||||
products_with_aggregates.append(product)
|
||||
# either is_decomposed_by is None or product is part of different rel
|
||||
if product_rel != is_decomposed_by:
|
||||
previous_aggregates_rels.add(product_rel)
|
||||
products_with_aggregates.append(product)
|
||||
|
||||
# products with already assigned aggregates will be skipped
|
||||
# products with already assigned aggregates will be skipped
|
||||
|
||||
products_to_change = products_without_aggregates + products_with_aggregates
|
||||
# nothing to change
|
||||
if not products_to_change:
|
||||
return is_decomposed_by
|
||||
products_to_change = products_without_aggregates + products_with_aggregates
|
||||
# nothing to change
|
||||
if not products_to_change:
|
||||
return is_decomposed_by
|
||||
|
||||
# can be either only aggregated or only contained at the same time
|
||||
# some product might not be able to have a container
|
||||
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
|
||||
ifcopenshell.api.run("spatial.unassign_container", self.file, products=possibly_contained_products)
|
||||
# can be either only aggregated or only contained at the same time
|
||||
# some product might not be able to have a container
|
||||
possibly_contained_products = [p for p in products_without_aggregates if hasattr(p, "ContainedInStructure")]
|
||||
ifcopenshell.api.run("spatial.unassign_container", file, products=possibly_contained_products)
|
||||
|
||||
# unassign elements from previous aggregates
|
||||
for decomposes in previous_aggregates_rels:
|
||||
related_objects = set(decomposes.RelatedObjects) - products
|
||||
if related_objects:
|
||||
decomposes.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": decomposes})
|
||||
else:
|
||||
history = decomposes.OwnerHistory
|
||||
self.file.remove(decomposes)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
|
||||
# assign elements to a new aggregate
|
||||
if is_decomposed_by:
|
||||
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": is_decomposed_by})
|
||||
# unassign elements from previous aggregates
|
||||
for decomposes in previous_aggregates_rels:
|
||||
related_objects = set(decomposes.RelatedObjects) - products
|
||||
if related_objects:
|
||||
decomposes.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": decomposes})
|
||||
else:
|
||||
is_decomposed_by = self.file.create_entity(
|
||||
"IfcRelAggregates",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": list(products),
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
history = decomposes.OwnerHistory
|
||||
file.remove(decomposes)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
# assign elements to a new aggregate
|
||||
if is_decomposed_by:
|
||||
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": is_decomposed_by})
|
||||
else:
|
||||
is_decomposed_by = file.create_entity(
|
||||
"IfcRelAggregates",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": list(products),
|
||||
"RelatingObject": relating_object,
|
||||
}
|
||||
)
|
||||
|
||||
# localize placement relative to a new aggregate for affected products
|
||||
for product in products_to_change:
|
||||
placement = getattr(product, "ObjectPlacement", None)
|
||||
if placement and placement.is_a("IfcLocalPlacement"):
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
file,
|
||||
product=product,
|
||||
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
|
||||
is_si=False,
|
||||
)
|
||||
|
||||
# localize placement relative to a new aggregate for affected products
|
||||
for product in products_to_change:
|
||||
placement = getattr(product, "ObjectPlacement", None)
|
||||
if placement and placement.is_a("IfcLocalPlacement"):
|
||||
ifcopenshell.api.run(
|
||||
"geometry.edit_object_placement",
|
||||
self.file,
|
||||
product=product,
|
||||
matrix=ifcopenshell.util.placement.get_local_placement(product.ObjectPlacement),
|
||||
is_si=False,
|
||||
)
|
||||
|
||||
return is_decomposed_by
|
||||
return is_decomposed_by
|
||||
|
||||
@@ -21,60 +21,57 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]):
|
||||
"""Unassigns products from their aggregate
|
||||
def unassign_object(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None:
|
||||
"""Unassigns products from their aggregate
|
||||
|
||||
A product (i.e. a smaller part of a whole) may be aggregated into zero
|
||||
or one larger space or element. This function will remove that
|
||||
aggregation relationship.
|
||||
A product (i.e. a smaller part of a whole) may be aggregated into zero
|
||||
or one larger space or element. This function will remove that
|
||||
aggregation relationship.
|
||||
|
||||
As all physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", using this function will remove the
|
||||
product from that tree. This is a dangerous operation and may result in
|
||||
the product no longer being visible in IFC applications.
|
||||
As all physical IFC model elements must be part of a hierarchical tree
|
||||
called the "spatial decomposition", using this function will remove the
|
||||
product from that tree. This is a dangerous operation and may result in
|
||||
the product no longer being visible in IFC applications.
|
||||
|
||||
If the product is not part of an aggregation relationship, nothing will
|
||||
happen.
|
||||
If the product is not part of an aggregation relationship, nothing will
|
||||
happen.
|
||||
|
||||
:param products: The list of parts of the aggregate, typically of IfcElements or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param products: The list of parts of the aggregate, typically of IfcElements or
|
||||
IfcSpatialStructureElement subclass
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
|
||||
# nothing is returned
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
|
||||
# nothing is returned, relationship is removed
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"products": products}
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
|
||||
subelement1 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
subelement2 = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement1], relating_object=element)
|
||||
ifcopenshell.api.run("aggregate.assign_object", model, products=[subelement2], relating_object=element)
|
||||
# nothing is returned
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement1])
|
||||
# nothing is returned, relationship is removed
|
||||
ifcopenshell.api.run("aggregate.unassign_object", model, products=[subelement2])
|
||||
"""
|
||||
settings = {"products": products}
|
||||
|
||||
def execute(self) -> None:
|
||||
products = set(self.settings["products"])
|
||||
rels = set(
|
||||
rel
|
||||
for product in products
|
||||
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
|
||||
)
|
||||
products = set(settings["products"])
|
||||
rels = set(
|
||||
rel
|
||||
for product in products
|
||||
if (rel := next((rel for rel in product.Decomposes if rel.is_a("IfcRelAggregates")), None))
|
||||
)
|
||||
|
||||
for rel in rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,3 +15,19 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Basic modification of the attributes of an element.
|
||||
|
||||
All IFC entities have attributes. Some of these attributes contain rules about
|
||||
inheritance and what they are allowed to contain. These usecases make sure that
|
||||
any editing complies with these rules.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .edit_attributes import edit_attributes
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"edit_attributes",
|
||||
]
|
||||
|
||||
@@ -17,66 +17,53 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, product=None, attributes=None):
|
||||
"""Edit the attributes of a product
|
||||
def edit_attributes(file: ifcopenshell.file, product: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a product
|
||||
|
||||
All IFC entities have attributes. Normally they can be edited directly,
|
||||
by simply assigning a new value to them. In some scenarios, you may wish
|
||||
to also ensure that ownership history is updated. This function provides
|
||||
that convenience.
|
||||
All IFC entities have attributes. Normally they can be edited directly,
|
||||
by simply assigning a new value to them. In some scenarios, you may wish
|
||||
to also ensure that ownership history is updated. This function provides
|
||||
that convenience.
|
||||
|
||||
:param product: The product you want to edit. This may be any rooted IFC
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param product: The product you want to edit. This may be any rooted IFC
|
||||
entity.
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
ifcopenshell.api.run("attribute.edit_attributes", model,
|
||||
product=element, attributes={"Name": "Waldo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"product": product, "attributes": attributes or {}}
|
||||
element = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
ifcopenshell.api.run("attribute.edit_attributes", model,
|
||||
product=element, attributes={"Name": "Waldo"})
|
||||
"""
|
||||
settings = {"product": product, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["product"], name, value)
|
||||
if hasattr(self.settings["product"], "PredefinedType"):
|
||||
if hasattr(self.settings["product"], "ElementType"):
|
||||
if (
|
||||
self.settings["product"].ElementType is None
|
||||
and self.settings["product"].PredefinedType == "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif (
|
||||
self.settings["product"].ElementType
|
||||
and self.settings["product"].PredefinedType != "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "USERDEFINED"
|
||||
elif hasattr(self.settings["product"], "ObjectType"):
|
||||
relating_type = ifcopenshell.util.element.get_type(self.settings["product"])
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
|
||||
self.settings["product"].ObjectType = None
|
||||
self.settings["product"].PredefinedType = None
|
||||
elif (
|
||||
self.settings["product"].ObjectType is None
|
||||
and self.settings["product"].PredefinedType == "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif (
|
||||
self.settings["product"].ObjectType
|
||||
and self.settings["product"].PredefinedType != "USERDEFINED"
|
||||
):
|
||||
self.settings["product"].PredefinedType = "USERDEFINED"
|
||||
if hasattr(self.settings["product"], "OwnerHistory"):
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": self.settings["product"]})
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["product"], name, value)
|
||||
if hasattr(settings["product"], "PredefinedType"):
|
||||
if hasattr(settings["product"], "ElementType"):
|
||||
if settings["product"].ElementType is None and settings["product"].PredefinedType == "USERDEFINED":
|
||||
settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif settings["product"].ElementType and settings["product"].PredefinedType != "USERDEFINED":
|
||||
settings["product"].PredefinedType = "USERDEFINED"
|
||||
elif hasattr(settings["product"], "ObjectType"):
|
||||
relating_type = ifcopenshell.util.element.get_type(settings["product"])
|
||||
# Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818
|
||||
if relating_type and relating_type.PredefinedType not in ("NOTDEFINED", None):
|
||||
settings["product"].ObjectType = None
|
||||
settings["product"].PredefinedType = None
|
||||
elif settings["product"].ObjectType is None and settings["product"].PredefinedType == "USERDEFINED":
|
||||
settings["product"].PredefinedType = "NOTDEFINED"
|
||||
elif settings["product"].ObjectType and settings["product"].PredefinedType != "USERDEFINED":
|
||||
settings["product"].PredefinedType = "USERDEFINED"
|
||||
if hasattr(settings["product"], "OwnerHistory"):
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": settings["product"]})
|
||||
|
||||
@@ -18,4 +18,22 @@
|
||||
|
||||
"""Boundaries are primarily used for representing virtual interfaces between
|
||||
spaces for energy analysis.
|
||||
|
||||
Boundaries may be associated with spaces or physical elements that enclose
|
||||
spaces such as walls, doors, and windows.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_connection_geometry import assign_connection_geometry
|
||||
from .copy_boundary import copy_boundary
|
||||
from .edit_attributes import edit_attributes
|
||||
from .remove_boundary import remove_boundary
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_connection_geometry",
|
||||
"copy_boundary",
|
||||
"edit_attributes",
|
||||
"remove_boundary",
|
||||
]
|
||||
|
||||
@@ -17,70 +17,82 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def assign_connection_geometry(
|
||||
file: ifcopenshell.file,
|
||||
rel_space_boundary: ifcopenshell.entity_instance,
|
||||
outer_boundary: list[tuple[float, float]],
|
||||
location: tuple[float, float, float],
|
||||
axis: tuple[float, float, float],
|
||||
ref_direction: tuple[float, float, float],
|
||||
inner_boundaries: Optional[list[list[tuple[float, float]]]] = None,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> None:
|
||||
"""Create and assign a connection geometry to a space boundary relationship
|
||||
|
||||
A space boundary may optionally have a plane that represents how that
|
||||
space is adjacent to another space, known as the connection geometry.
|
||||
You may specify this plane in terms of an outer boundary polyline, zero
|
||||
or more inner boundaries (such as for windows), and a positional matrix
|
||||
for the orientation of the plane.
|
||||
|
||||
:param rel_space_boundary: The space boundary relationship to assign the
|
||||
connection geometry to.
|
||||
:type rel_space_boundary: ifcopenshell.entity_instance
|
||||
:param outer_boundary: A list of 2D points representing an open
|
||||
polyline. The last point will connect to the first point. Each
|
||||
point is represented by an interable of 2 floats. The coordinates of
|
||||
the points are relative to the positional matrix arguments.
|
||||
:type outer_boundary: list[tuple[float, float]]
|
||||
:param inner_boundaries: A list of zero or more inner boundaries to use
|
||||
for the plane. Each boundary is represented by an open polyline, as
|
||||
defined by the outer_boundary argument.
|
||||
:type inner_boundaries: list[list[tuple[float, float]]], optional
|
||||
:param location: The local origin of the connection geometry, defined as
|
||||
an XYZ coordinate relative to the placement of the space that is
|
||||
being bounded.
|
||||
:type location: tuple[float, float, float]
|
||||
:param axis: The local X axis of the connection geometry, defined as an
|
||||
XYZ vector relative to the placement of the space that is being
|
||||
bounded.
|
||||
:type axis: tuple[float, float, float]
|
||||
:param ref_direction: The local Z axis of the connection geometry,
|
||||
defined as an XYZ vector relative to the placement of the space that
|
||||
is being bounded. The Y vector is automatically derived using the
|
||||
right hand rule.
|
||||
:type ref_direction: tuple[float, float, float]
|
||||
:param unit_scale: The unit scale as calculated by
|
||||
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
|
||||
will be automatically calculated for you.
|
||||
:type unit_scale: float, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", model,
|
||||
rel_space_boundary=element,
|
||||
outer_boundary=[(0., 0.), (1., 0.), (1., 1.), (0., 1.)],
|
||||
location=[0., 0., 0.], axis=[1., 0., 0.], ref_direction=[0., 0., 1.],
|
||||
)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.rel_space_boundary = rel_space_boundary
|
||||
usecase.outer_boundary = outer_boundary
|
||||
usecase.inner_boundaries = inner_boundaries or ()
|
||||
usecase.location = location
|
||||
usecase.axis = axis
|
||||
usecase.ref_direction = ref_direction
|
||||
usecase.unit_scale = unit_scale
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, rel_space_boundary=None, outer_boundary=None, inner_boundaries=None, location=None, axis=None, ref_direction=None, unit_scale=None):
|
||||
"""Create and assign a connection geometry to a space boundary relationship
|
||||
|
||||
A space boundary may optionally have a plane that represents how that
|
||||
space is adjacent to another space, known as the connection geometry.
|
||||
You may specify this plane in terms of an outer boundary polyline, zero
|
||||
or more inner boundaries (such as for windows), and a positional matrix
|
||||
for the orientation of the plane.
|
||||
|
||||
:param rel_space_boundary: The space boundary relationship to assign the
|
||||
connection geometry to.
|
||||
:type rel_space_boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:param outer_boundary: A list of 2D points representing an open
|
||||
polyline. The last point will connect to the first point. Each
|
||||
point is represented by an interable of 2 floats. The coordinates of
|
||||
the points are relative to the positional matrix arguments.
|
||||
:type outer_boundary: list[list[float]]
|
||||
:param inner_boundaries: A list of zero or more inner boundaries to use
|
||||
for the plane. Each boundary is represented by an open polyline, as
|
||||
defined by the outer_boundary argument.
|
||||
:type inner_boundaries: list[list[list[float]]], optional
|
||||
:param location: The local origin of the connection geometry, defined as
|
||||
an XYZ coordinate relative to the placement of the space that is
|
||||
being bounded.
|
||||
:type location: list[float]
|
||||
:param axis: The local X axis of the connection geometry, defined as an
|
||||
XYZ vector relative to the placement of the space that is being
|
||||
bounded.
|
||||
:type axis: list[float]
|
||||
:param ref_direction: The local Z axis of the connection geometry,
|
||||
defined as an XYZ vector relative to the placement of the space that
|
||||
is being bounded. The Y vector is automatically derived using the
|
||||
right hand rule.
|
||||
:type ref_direction: list[float]
|
||||
:param unit_scale: The unit scale as calculated by
|
||||
ifcopenshell.util.unit.calculate_unit_scale. If not provided, it
|
||||
will be automatically calculated for you.
|
||||
:type unit_scale: float, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
ifcopenshell.api.run("boundary.assign_connection_geometry", model,
|
||||
rel_space_boundary=element,
|
||||
outer_boundary=[(0., 0.), (1., 0.), (1., 1.), (0., 1.)],
|
||||
location=[0., 0., 0.], axis=[1., 0., 0.], ref_direction=[0., 0., 1.],
|
||||
)
|
||||
"""
|
||||
self.file = file
|
||||
self.rel_space_boundary = rel_space_boundary
|
||||
self.outer_boundary = outer_boundary
|
||||
self.inner_boundaries = inner_boundaries or ()
|
||||
self.location = location
|
||||
self.axis = axis
|
||||
self.ref_direction = ref_direction
|
||||
self.unit_scale = unit_scale
|
||||
self.ifc_vertices = []
|
||||
|
||||
def execute(self):
|
||||
if self.unit_scale is None:
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -19,29 +19,26 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, boundary=None):
|
||||
"""Copies a space boundary
|
||||
def copy_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Copies a space boundary
|
||||
|
||||
:param boundary: The IfcRelSpaceBoundary you want to copy.
|
||||
:type boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param boundary: The IfcRelSpaceBoundary you want to copy.
|
||||
:type boundary: ifcopenshell.entity_instance
|
||||
:return: Duplicate of the IfcRelSpaceBoundary
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
|
||||
# And now we have two
|
||||
boundary_copy = ifcopenshell.api.run("boundary.copy_boundary", model, boundary=boundary)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"boundary": boundary}
|
||||
# And now we have two
|
||||
boundary_copy = ifcopenshell.api.run("boundary.copy_boundary", model, boundary=boundary)
|
||||
"""
|
||||
settings = {"boundary": boundary}
|
||||
|
||||
def execute(self):
|
||||
result = ifcopenshell.util.element.copy(self.file, self.settings["boundary"])
|
||||
if result.ConnectionGeometry:
|
||||
result.ConnectionGeometry = ifcopenshell.util.element.copy_deep(self.file, result.ConnectionGeometry)
|
||||
return result
|
||||
result = ifcopenshell.util.element.copy(file, settings["boundary"])
|
||||
if result.ConnectionGeometry:
|
||||
result.ConnectionGeometry = ifcopenshell.util.element.copy_deep(file, result.ConnectionGeometry)
|
||||
return result
|
||||
|
||||
@@ -15,47 +15,53 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, entity=None, relating_space=None, related_building_element=None, parent_boundary=None, corresponding_boundary=None):
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
def edit_attributes(
|
||||
file: ifcopenshell.file,
|
||||
entity: ifcopenshell.entity_instance,
|
||||
relating_space: ifcopenshell.entity_instance,
|
||||
related_building_element: ifcopenshell.entity_instance,
|
||||
parent_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
corresponding_boundary: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""Modify the relationships of a space boundary relationship
|
||||
|
||||
Currently this function is quite minimal and offers no advantage to
|
||||
manual assignment of the space boundary attributes.
|
||||
Currently this function is quite minimal and offers no advantage to
|
||||
manual assignment of the space boundary attributes.
|
||||
|
||||
:param entity: The IfcRelSpaceBoundary to modify
|
||||
:type entity: ifcopenshell.entity_instance.entity_instance
|
||||
:param relating_space: The IfcSpace or IfcExternalSpatialElement that
|
||||
the space boundary is related to.
|
||||
:type relating_space: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_building_element: The IfcElement that defines the
|
||||
boundary, typically an IfcWall.
|
||||
:type relating_space: ifcopenshell.entity_instance.entity_instance
|
||||
:param parent_boundary: A parent IfcRelSpaceBoundary, only provided if
|
||||
this is an inner boundary. This can apply to 1st and 2nd level
|
||||
boundaries.
|
||||
:type parent_boundary: ifcopenshell.entity_instance.entity_instance,
|
||||
optional
|
||||
:param corresponding_boundary: The other IfcRelSpaceBoundary on the
|
||||
other side of the related element. The pair together represents a
|
||||
thermal boundary. This only applies to 2nd level boundaries.
|
||||
:type corresponding_boundary: ifcopenshell.entity_instance.entity_instance,
|
||||
optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.file = file
|
||||
self.entity = entity
|
||||
self.relating_space = relating_space
|
||||
self.related_building_element = related_building_element
|
||||
self.parent_boundary = parent_boundary
|
||||
self.corresponding_boundary = corresponding_boundary
|
||||
:param entity: The IfcRelSpaceBoundary to modify
|
||||
:type entity: ifcopenshell.entity_instance
|
||||
:param relating_space: The IfcSpace or IfcExternalSpatialElement that
|
||||
the space boundary is related to.
|
||||
:type relating_space: ifcopenshell.entity_instance
|
||||
:param related_building_element: The IfcElement that defines the
|
||||
boundary, typically an IfcWall.
|
||||
:type relating_space: ifcopenshell.entity_instance
|
||||
:param parent_boundary: A parent IfcRelSpaceBoundary, only provided if
|
||||
this is an inner boundary. This can apply to 1st and 2nd level
|
||||
boundaries.
|
||||
:type parent_boundary: ifcopenshell.entity_instance,
|
||||
optional
|
||||
:param corresponding_boundary: The other IfcRelSpaceBoundary on the
|
||||
other side of the related element. The pair together represents a
|
||||
thermal boundary. This only applies to 2nd level boundaries.
|
||||
:type corresponding_boundary: ifcopenshell.entity_instance,
|
||||
optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
entity = entity
|
||||
relating_space = relating_space
|
||||
related_building_element = related_building_element
|
||||
parent_boundary = parent_boundary
|
||||
corresponding_boundary = corresponding_boundary
|
||||
|
||||
def execute(self):
|
||||
self.entity.RelatingSpace = self.relating_space
|
||||
self.entity.RelatedBuildingElement = self.related_building_element
|
||||
if hasattr(self.entity, "ParentBoundary"):
|
||||
self.entity.ParentBoundary = self.parent_boundary
|
||||
if hasattr(self.entity, "CorrespondingBoundary"):
|
||||
self.entity.CorrespondingBoundary = self.corresponding_boundary
|
||||
entity.RelatingSpace = relating_space
|
||||
entity.RelatedBuildingElement = related_building_element
|
||||
if hasattr(entity, "ParentBoundary"):
|
||||
entity.ParentBoundary = parent_boundary
|
||||
if hasattr(entity, "CorrespondingBoundary"):
|
||||
entity.CorrespondingBoundary = corresponding_boundary
|
||||
|
||||
@@ -20,36 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, boundary=None):
|
||||
"""Removes a space boundary
|
||||
def remove_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a space boundary
|
||||
|
||||
The relating space or related building element is untouched. Only the
|
||||
boundary and its connection geometry is removed.
|
||||
The relating space or related building element is untouched. Only the
|
||||
boundary and its connection geometry is removed.
|
||||
|
||||
:param boundary: The IfcRelSpaceBoundary you want to remove.
|
||||
:type boundary: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param boundary: The IfcRelSpaceBoundary you want to remove.
|
||||
:type boundary: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
# A boring boundary with no geometry. Note that this boundary is
|
||||
# invalid and does not relate to any space or building element.
|
||||
boundary = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcRelSpaceBoundary")
|
||||
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("boundary.remove_boundary", model, boundary=boundary)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"boundary": boundary}
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("boundary.remove_boundary", model, boundary=boundary)
|
||||
"""
|
||||
settings = {"boundary": boundary}
|
||||
|
||||
def execute(self):
|
||||
geometry = self.settings["boundary"].ConnectionGeometry
|
||||
if geometry:
|
||||
self.settings["boundary"].ConnectionGeometry = None
|
||||
ifcopenshell.util.element.remove_deep2(self.file, geometry)
|
||||
history = self.settings["boundary"].OwnerHistory
|
||||
self.file.remove(self.settings["boundary"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
geometry = settings["boundary"].ConnectionGeometry
|
||||
if geometry:
|
||||
settings["boundary"].ConnectionGeometry = None
|
||||
ifcopenshell.util.element.remove_deep2(file, geometry)
|
||||
history = settings["boundary"].OwnerHistory
|
||||
file.remove(settings["boundary"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,3 +15,33 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Classification systems are a way of categorising objects
|
||||
|
||||
Although IFC itself comes with a built-in classification hierarchy (e.g.
|
||||
IfcWall and its predefined types of PARTITIONING, etc), there are many external
|
||||
or custom classification systems such as Uniclass, Omniclass and more. IFC is
|
||||
able to integrate with any external classification system.
|
||||
|
||||
This API allows you to manage and assign external classification systems and
|
||||
references.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_classification import add_classification
|
||||
from .add_reference import add_reference
|
||||
from .edit_classification import edit_classification
|
||||
from .edit_reference import edit_reference
|
||||
from .remove_classification import remove_classification
|
||||
from .remove_reference import remove_reference
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_classification",
|
||||
"add_reference",
|
||||
"edit_classification",
|
||||
"edit_reference",
|
||||
"remove_classification",
|
||||
"remove_reference",
|
||||
]
|
||||
|
||||
@@ -17,72 +17,78 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.date
|
||||
from typing import Union
|
||||
|
||||
|
||||
def add_classification(
|
||||
file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new classification system to the project
|
||||
|
||||
External classification systems such as Uniclass or Omniclass are
|
||||
ways of categorising elements in the AEC industry, typically
|
||||
standardised or nominated by governments or companies. A system
|
||||
typically contains a series of hierarchical reference codes and labels
|
||||
like Pr_12_23_34.
|
||||
|
||||
Classifications may be applied to many things, not just physical
|
||||
elements, such as doors and windows, spatial elements, tasks, cost
|
||||
items, or even resources.
|
||||
|
||||
Prior to assigning classificaion references, you need to add the name
|
||||
and metadata of the classification system that you will use in your
|
||||
project. Classification systems may be revised over time, so this
|
||||
metadata includes the edition date.
|
||||
|
||||
Common classification systems are provided as an IFC library which may
|
||||
be downloaded from https://github.com/Moult/IfcClassification for your
|
||||
convenience. It is advised to use these to ensure that the
|
||||
classification metadata is standardised.
|
||||
|
||||
Adding a classification system will not add the entire hierarchy of
|
||||
references available in the classification. References need to be added
|
||||
separately. Typically, you'd only add the references that you use in
|
||||
your project, see ifcopenshell.api.classification.add_reference for more
|
||||
information.
|
||||
|
||||
:param classification: If a string is provided, it is assumed to be the
|
||||
name of your classification system. This is necessary if you are
|
||||
creating your own custom classification system. Alternatively, you
|
||||
may provide an entity_instance of an IfcClassification from an IFC
|
||||
classification library. The latter approach is preferred if you are
|
||||
using a commonly known system such as Uniclass, as this will ensure
|
||||
all metadata is added correctly.
|
||||
:type classification: str,ifcopenshell.entity_instance
|
||||
:return: The added IfcClassification element
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding a custom clasification from scratch
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification="MyCustomClassification")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
classification = library.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"classification": classification,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, classification: Union[str, ifcopenshell.entity_instance]):
|
||||
"""Adds a new classification system to the project
|
||||
|
||||
External classification systems such as Uniclass or Omniclass are
|
||||
ways of categorising elements in the AEC industry, typically
|
||||
standardised or nominated by governments or companies. A system
|
||||
typically contains a series of hierarchical reference codes and labels
|
||||
like Pr_12_23_34.
|
||||
|
||||
Classifications may be applied to many things, not just physical
|
||||
elements, such as doors and windows, spatial elements, tasks, cost
|
||||
items, or even resources.
|
||||
|
||||
Prior to assigning classificaion references, you need to add the name
|
||||
and metadata of the classification system that you will use in your
|
||||
project. Classification systems may be revised over time, so this
|
||||
metadata includes the edition date.
|
||||
|
||||
Common classification systems are provided as an IFC library which may
|
||||
be downloaded from https://github.com/Moult/IfcClassification for your
|
||||
convenience. It is advised to use these to ensure that the
|
||||
classification metadata is standardised.
|
||||
|
||||
Adding a classification system will not add the entire hierarchy of
|
||||
references available in the classification. References need to be added
|
||||
separately. Typically, you'd only add the references that you use in
|
||||
your project, see ifcopenshell.api.classification.add_reference for more
|
||||
information.
|
||||
|
||||
:param classification: If a string is provided, it is assumed to be the
|
||||
name of your classification system. This is necessary if you are
|
||||
creating your own custom classification system. Alternatively, you
|
||||
may provide an entity_instance of an IfcClassification from an IFC
|
||||
classification library. The latter approach is preferred if you are
|
||||
using a commonly known system such as Uniclass, as this will ensure
|
||||
all metadata is added correctly.
|
||||
:type classification: str,ifcopenshell.entity_instance.entity_instance
|
||||
:return: The added IfcClassification element
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding a custom clasification from scratch
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification="MyCustomClassification")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
classification = library.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.add_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"classification": classification,
|
||||
}
|
||||
|
||||
def execute(self) -> ifcopenshell.entity_instance:
|
||||
def execute(self):
|
||||
if isinstance(self.settings["classification"], str):
|
||||
classification = self.file.createIfcClassification(Name=self.settings["classification"])
|
||||
self.relate_to_project(classification)
|
||||
@@ -104,7 +110,9 @@ class Usecase:
|
||||
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(edition_date, "IfcCalendarDate")
|
||||
)
|
||||
else:
|
||||
result.EditionDate = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
if edition_date:
|
||||
edition_date = ifcopenshell.util.date.datetime2ifc(edition_date, "IfcDate")
|
||||
result.EditionDate = edition_date
|
||||
|
||||
self.relate_to_project(result)
|
||||
|
||||
|
||||
@@ -18,122 +18,125 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.schema
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
def add_reference(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
reference: Optional[ifcopenshell.entity_instance] = None,
|
||||
identification: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
classification: Optional[ifcopenshell.entity_instance] = None,
|
||||
is_lightweight=True,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Adds a new classification reference and assigns it to the list of products
|
||||
|
||||
A classification reference is a single entry such as "Pr_12_23_34" that
|
||||
is part of an external classification system (such as Uniclass or
|
||||
Omniclass).
|
||||
|
||||
References can be added to almost any object in IFC, including physical
|
||||
objects, object types, properties, tasks, costs, resources, or even
|
||||
resources such as profiles, documents, libraries, and so on.
|
||||
|
||||
Classification references can be added in two ways. Option 1) specify a
|
||||
custom arbitrary reference, where you have to manually specify the
|
||||
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
|
||||
Option 2) add a reference from an IFC classification library. The latter
|
||||
is preferred if you are using a common classification system such as
|
||||
Uniclass, as the library will be prepopulated with all the valid
|
||||
classifications already.
|
||||
|
||||
Objects are allowed to have multiple classification references from
|
||||
multiple classification systems. This means that adding a new reference
|
||||
will not remove existing references.
|
||||
|
||||
References can be inherited from types. This means that if an
|
||||
IfcWallType has a classification reference of Pr_12_23_34, then all
|
||||
IfcWall occurrences of that type automatically get the same
|
||||
classification of Pr_12_23_34. This means that it is more efficient to
|
||||
assign to types where possible. If a classification reference is
|
||||
assigned to both the type and an occurrence, then the assignment at the
|
||||
occurrence will override the type classification.
|
||||
|
||||
:param product: The list of IFC objects, properties, or resources you want to
|
||||
associate the classification reference to.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param reference: The classification reference entity taken from an
|
||||
IFC classification library. If you supply this parameter, you will
|
||||
use option 2.
|
||||
:type reference: ifcopenshell.entity_instance, optional
|
||||
:param identification: If you choose option 1 and do not specify a
|
||||
reference, you may manually specify an identification code. The code
|
||||
is typically a short identifier and may have punctuation to separate
|
||||
the levels of hierarchy in the classificaion (e.g. Pr_12_23_34).
|
||||
:type identification: str, optional
|
||||
:param name: If you choose option 1 and do not specify a reference, you
|
||||
may manually specify a name. The name is typically human readable.
|
||||
:type name: str, optional
|
||||
:param classification: The IfcClassification entity in your IFC model
|
||||
(not the library, if you are doing option 2) that the reference is
|
||||
part of.
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:param is_lightweight: If you are doing option 2, choose whether or not
|
||||
to only add that particular reference (lighweight) or also add all
|
||||
of its parent references in the classification hierarchy (not
|
||||
lighweight). For example, adding a lightweight reference to
|
||||
Pr_12_23_34 will only add Pr_12_23_34, but adding a heavy reference
|
||||
to Pr_12_23_34 will also add Pr_12_23 and Pr_12. These parent
|
||||
references merely help describe the "tree" of classifications, but
|
||||
is generally unnecessary. Using lightweight classifications are
|
||||
recommended and is the default.
|
||||
:type is_lightweight: bool, optional
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: The newly added IfcClassificationReference
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding and assigning a new reference from scratch
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
lib_classification = library.by_type("IfcClassification")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification=lib_classification)
|
||||
reference = [r for r in library.by_type("IfcClassificationReference")
|
||||
if r.Identification == "XYZ"][0]
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
reference=reference)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"reference": reference,
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"classification": classification,
|
||||
"is_lightweight": is_lightweight,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
reference: Optional[ifcopenshell.entity_instance] = None,
|
||||
identification: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
classification: Optional[ifcopenshell.entity_instance] = None,
|
||||
is_lightweight=True,
|
||||
):
|
||||
"""Adds a new classification reference and assigns it to the list of products
|
||||
|
||||
A classification reference is a single entry such as "Pr_12_23_34" that
|
||||
is part of an external classification system (such as Uniclass or
|
||||
Omniclass).
|
||||
|
||||
References can be added to almost any object in IFC, including physical
|
||||
objects, object types, properties, tasks, costs, resources, or even
|
||||
resources such as profiles, documents, libraries, and so on.
|
||||
|
||||
Classification references can be added in two ways. Option 1) specify a
|
||||
custom arbitrary reference, where you have to manually specify the
|
||||
identification (e.g. "Pr_12_23_45") and name (e.g. "Door Products").
|
||||
Option 2) add a reference from an IFC classification library. The latter
|
||||
is preferred if you are using a common classification system such as
|
||||
Uniclass, as the library will be prepopulated with all the valid
|
||||
classifications already.
|
||||
|
||||
Objects are allowed to have multiple classification references from
|
||||
multiple classification systems. This means that adding a new reference
|
||||
will not remove existing references.
|
||||
|
||||
References can be inherited from types. This means that if an
|
||||
IfcWallType has a classification reference of Pr_12_23_34, then all
|
||||
IfcWall occurrences of that type automatically get the same
|
||||
classification of Pr_12_23_34. This means that it is more efficient to
|
||||
assign to types where possible. If a classification reference is
|
||||
assigned to both the type and an occurrence, then the assignment at the
|
||||
occurrence will override the type classification.
|
||||
|
||||
:param product: The list of IFC objects, properties, or resources you want to
|
||||
associate the classification reference to.
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param reference: The classification reference entity taken from an
|
||||
IFC classification library. If you supply this parameter, you will
|
||||
use option 2.
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance, optional
|
||||
:param identification: If you choose option 1 and do not specify a
|
||||
reference, you may manually specify an identification code. The code
|
||||
is typically a short identifier and may have punctuation to separate
|
||||
the levels of hierarchy in the classificaion (e.g. Pr_12_23_34).
|
||||
:type identification: str, optional
|
||||
:param name: If you choose option 1 and do not specify a reference, you
|
||||
may manually specify a name. The name is typically human readable.
|
||||
:type name: str, optional
|
||||
:param classification: The IfcClassification entity in your IFC model
|
||||
(not the library, if you are doing option 2) that the reference is
|
||||
part of.
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:param is_lightweight: If you are doing option 2, choose whether or not
|
||||
to only add that particular reference (lighweight) or also add all
|
||||
of its parent references in the classification hierarchy (not
|
||||
lighweight). For example, adding a lightweight reference to
|
||||
Pr_12_23_34 will only add Pr_12_23_34, but adding a heavy reference
|
||||
to Pr_12_23_34 will also add Pr_12_23 and Pr_12. These parent
|
||||
references merely help describe the "tree" of classifications, but
|
||||
is generally unnecessary. Using lightweight classifications are
|
||||
recommended and is the default.
|
||||
:type is_lightweight: bool, optional
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: The newly added IfcClassificationReference
|
||||
or `None` if `products` was empty list.
|
||||
:rtype: Union[ifcopenshell.entity_instance.entity_instance, None]
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# Option 1: adding and assigning a new reference from scratch
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
|
||||
# Option 2: adding a popular classification from a library
|
||||
library = ifcopenshell.open("/path/to/Uniclass.ifc")
|
||||
lib_classification = library.by_type("IfcClassification")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification=lib_classification)
|
||||
reference = [r for r in library.by_type("IfcClassificationReference")
|
||||
if r.Identification == "XYZ"][0]
|
||||
ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
reference=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"reference": reference,
|
||||
"identification": identification,
|
||||
"name": name,
|
||||
"classification": classification,
|
||||
"is_lightweight": is_lightweight,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def execute(self):
|
||||
if not self.settings["products"]:
|
||||
return
|
||||
|
||||
|
||||
@@ -15,34 +15,35 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, classification=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassification
|
||||
def edit_classification(
|
||||
file: ifcopenshell.file, classification: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassification
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassification, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassification, consult the IFC documentation.
|
||||
|
||||
:param classification: The IfcClassification entity you want to edit
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param classification: The IfcClassification entity you want to edit
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the classification system to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_classification", model,
|
||||
classification=classification, attributes={"Name": "Foo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"classification": classification, "attributes": attributes or {}}
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the classification system to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_classification", model,
|
||||
classification=classification, attributes={"Name": "Foo"})
|
||||
"""
|
||||
settings = {"classification": classification, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["classification"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["classification"], name, value)
|
||||
|
||||
@@ -15,34 +15,35 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, reference=None, attributes=None):
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file, reference: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcClassificationReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassificationReference, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcClassificationReference, consult the IFC documentation.
|
||||
|
||||
:param reference: The IfcClassificationReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param reference: The IfcClassificationReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
reference = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the reference to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_reference", model,
|
||||
reference=reference, attributes={"Name": "Foo"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": reference, "attributes": attributes or {}}
|
||||
reference = model.by_type("IfcClassification")[0]
|
||||
# Change the name of the reference to "Foo"
|
||||
ifcopenshell.api.run("classification.edit_reference", model,
|
||||
reference=reference, attributes={"Name": "Foo"})
|
||||
"""
|
||||
settings = {"reference": reference, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["reference"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["reference"], name, value)
|
||||
|
||||
@@ -20,30 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_classification(file: ifcopenshell.file, classification: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes an IfcClassification from the project and all references
|
||||
|
||||
The classification and all of its relationships, children references,
|
||||
and relationships between objects and child references are completely
|
||||
removed from a project.
|
||||
|
||||
:param classification: The IfcClassification entity you want to remove
|
||||
:type classification: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.remove_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"classification": classification}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, classification=None):
|
||||
"""Removes an IfcClassification from the project and all references
|
||||
|
||||
The classification and all of its relationships, children references,
|
||||
and relationships between objectse and child references are completely
|
||||
removed from a project.
|
||||
|
||||
:param classification: The IfcClassification entity you want to remove
|
||||
:type classification: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
classification = model.by_type("IfcClassification")[0]
|
||||
ifcopenshell.api.run("classification.remove_classification", model,
|
||||
classification=classification)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"classification": classification}
|
||||
|
||||
def execute(self):
|
||||
references = self.get_references(self.settings["classification"])
|
||||
for reference in references:
|
||||
@@ -55,15 +58,20 @@ class Usecase:
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification):
|
||||
if self.file.schema != "IFC2X3":
|
||||
for rel in self.file.by_type("IfcExternalReferenceRelationship"):
|
||||
if not rel.RelatingReference:
|
||||
self.file.remove(rel)
|
||||
|
||||
def get_references(self, classification: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
results = []
|
||||
if not classification.HasReferences:
|
||||
return results
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
if self.file.schema == "IFC2X3":
|
||||
for reference in self.file.by_type("IfcClassificationReference"):
|
||||
if reference.ReferencedSource == classification:
|
||||
results.append(reference)
|
||||
else:
|
||||
for reference in classification.HasReferences:
|
||||
results.append(reference)
|
||||
results.extend(self.get_references(reference))
|
||||
return results
|
||||
|
||||
@@ -21,107 +21,102 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
):
|
||||
"""Removes a classification reference from the list of products
|
||||
def remove_reference(
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
) -> None:
|
||||
"""Removes a classification reference from the list of products
|
||||
|
||||
If the classification reference is no longer associated to any products,
|
||||
the classification reference itself is also removed.
|
||||
If the classification reference is no longer associated to any products,
|
||||
the classification reference itself is also removed.
|
||||
|
||||
:param reference: The IfcClassificationReference entity of the
|
||||
relationship you want to remove.
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:param product: The list fo object entities of the relationship you want to
|
||||
remove.
|
||||
:type product: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param reference: The IfcClassificationReference entity of the
|
||||
relationship you want to remove.
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param product: The list fo object entities of the relationship you want to
|
||||
remove.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
:raises TypeError: If file is IFC2X3 and `products` has non-IfcRoot elements.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
reference = ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
ifcopenshell.api.run("classification.remove_reference", model,
|
||||
reference=reference, products=[wall_type])
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"reference": reference, "products": products}
|
||||
wall_type = model.by_type("IfcWallType")[0]
|
||||
classification = ifcopenshell.api.run("classification.add_classification",
|
||||
model, classification="MyCustomClassification")
|
||||
reference = ifcopenshell.api.run("classification.add_reference", model,
|
||||
products=[wall_type], classification=classification,
|
||||
identification="W_01", name="Interior Walls")
|
||||
ifcopenshell.api.run("classification.remove_reference", model,
|
||||
reference=reference, products=[wall_type])
|
||||
"""
|
||||
settings = {"reference": reference, "products": products}
|
||||
|
||||
def execute(self) -> None:
|
||||
is_ifc2x3 = self.file.schema == "IFC2X3"
|
||||
products = set(self.settings["products"])
|
||||
referenced = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||
products -= products.difference(referenced)
|
||||
is_ifc2x3 = file.schema == "IFC2X3"
|
||||
products = set(settings["products"])
|
||||
referenced = ifcopenshell.util.element.get_referenced_elements(settings["reference"])
|
||||
products -= products.difference(referenced)
|
||||
|
||||
# all products are already unassigned from a reference
|
||||
if not products:
|
||||
return
|
||||
# all products are already unassigned from a reference
|
||||
if not products:
|
||||
return
|
||||
|
||||
rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
non_rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
for product in self.settings["products"]:
|
||||
if product.is_a("IfcRoot"):
|
||||
rooted_products.add(product)
|
||||
rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
non_rooted_products: set[ifcopenshell.entity_instance] = set()
|
||||
for product in settings["products"]:
|
||||
if product.is_a("IfcRoot"):
|
||||
rooted_products.add(product)
|
||||
else:
|
||||
non_rooted_products.add(product)
|
||||
|
||||
if non_rooted_products and is_ifc2x3:
|
||||
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
|
||||
|
||||
if rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in rooted_products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesClassification") and rel.RelatingClassification == settings["reference"]
|
||||
}
|
||||
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
else:
|
||||
non_rooted_products.add(product)
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
if non_rooted_products and is_ifc2x3:
|
||||
raise TypeError(f"Cannot add reference to non-IfcRoot element in IFC2X3: {non_rooted_products}.")
|
||||
if non_rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in non_rooted_products:
|
||||
rels = getattr(product, "HasExternalReferences", None)
|
||||
if rels is None:
|
||||
rels = getattr(product, "HasExternalReference", [])
|
||||
reference_rels.update(rels)
|
||||
|
||||
if rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in rooted_products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == settings["reference"]}
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedResourceObjects = list(related_objects)
|
||||
else:
|
||||
file.remove(rel)
|
||||
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesClassification")
|
||||
and rel.RelatingClassification == self.settings["reference"]
|
||||
}
|
||||
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
|
||||
if non_rooted_products:
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
for product in non_rooted_products:
|
||||
rels = getattr(product, "HasExternalReferences", None)
|
||||
if rels is None:
|
||||
rels = getattr(product, "HasExternalReference", [])
|
||||
reference_rels.update(rels)
|
||||
|
||||
reference_rels = {rel for rel in reference_rels if rel.RelatingReference == self.settings["reference"]}
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedResourceObjects) - non_rooted_products
|
||||
if related_objects:
|
||||
rel.RelatedResourceObjects = list(related_objects)
|
||||
else:
|
||||
self.file.remove(rel)
|
||||
|
||||
# TODO: we only handle lightweight classifications here
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["reference"])
|
||||
if not referenced_elements:
|
||||
self.file.remove(self.settings["reference"])
|
||||
# TODO: we only handle lightweight classifications here
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["reference"])
|
||||
if not referenced_elements:
|
||||
file.remove(settings["reference"])
|
||||
|
||||
@@ -15,3 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Constraints are an advanced feature allowing you to specify parametric
|
||||
limits on properties
|
||||
|
||||
Warning: usage of constraints are mostly untested in real life applications.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_metric import add_metric
|
||||
from .add_metric_reference import add_metric_reference
|
||||
from .add_objective import add_objective
|
||||
from .assign_constraint import assign_constraint
|
||||
from .edit_metric import edit_metric
|
||||
from .edit_objective import edit_objective
|
||||
from .remove_constraint import remove_constraint
|
||||
from .remove_metric import remove_metric
|
||||
from .unassign_constraint import unassign_constraint
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_metric",
|
||||
"add_metric_reference",
|
||||
"add_objective",
|
||||
"assign_constraint",
|
||||
"edit_metric",
|
||||
"edit_objective",
|
||||
"remove_constraint",
|
||||
"remove_metric",
|
||||
"unassign_constraint",
|
||||
]
|
||||
|
||||
@@ -19,44 +19,41 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, objective=None):
|
||||
"""Add a new metric benchmark
|
||||
def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Add a new metric benchmark
|
||||
|
||||
Qualitative constraints may have a series of quantitative benchmarks
|
||||
linked to it known as metrics. Metrics may be parametrically linked to
|
||||
computed model properties or quantities. Metrics need to be satisfied
|
||||
to meet the objective of the constraint.
|
||||
Qualitative constraints may have a series of quantitative benchmarks
|
||||
linked to it known as metrics. Metrics may be parametrically linked to
|
||||
computed model properties or quantities. Metrics need to be satisfied
|
||||
to meet the objective of the constraint.
|
||||
|
||||
:param objective: The IfcObjective that this metric is a benchmark of.
|
||||
:type objective: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcMetric entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:param objective: The IfcObjective that this metric is a benchmark of.
|
||||
:type objective: ifcopenshell.entity_instance
|
||||
:return: The newly created IfcMetric entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"objective": objective,
|
||||
}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
"""
|
||||
settings = {
|
||||
"objective": objective,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
metric = self.file.create_entity(
|
||||
"IfcMetric",
|
||||
**{
|
||||
"Name": "Unnamed",
|
||||
"ConstraintGrade": "NOTDEFINED",
|
||||
"Benchmark": "EQUALTO",
|
||||
}
|
||||
)
|
||||
if self.settings["objective"]:
|
||||
benchmark_values = list(self.settings["objective"].BenchmarkValues or [])
|
||||
benchmark_values.append(metric)
|
||||
self.settings["objective"].BenchmarkValues = benchmark_values
|
||||
return metric
|
||||
metric = file.create_entity(
|
||||
"IfcMetric",
|
||||
**{
|
||||
"Name": "Unnamed",
|
||||
"ConstraintGrade": "NOTDEFINED",
|
||||
"Benchmark": "EQUALTO",
|
||||
},
|
||||
)
|
||||
if settings["objective"]:
|
||||
benchmark_values = list(settings["objective"].BenchmarkValues or [])
|
||||
benchmark_values.append(metric)
|
||||
settings["objective"].BenchmarkValues = benchmark_values
|
||||
return metric
|
||||
|
||||
@@ -18,28 +18,28 @@
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None, reference_path=None):
|
||||
"""
|
||||
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
|
||||
Used to reference a value of an attribute of an instance through a metric objective entity.
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric, "reference_path": reference_path}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["reference_path"]:
|
||||
attributes = self.settings["reference_path"].split(".")
|
||||
references_created = []
|
||||
for i in range(len(attributes)):
|
||||
if i == 0:
|
||||
reference = self.file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
self.settings["metric"].ReferencePath = reference
|
||||
references_created.append(reference)
|
||||
else:
|
||||
reference = self.file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
references_created[i-1].InnerReference = reference
|
||||
references_created.append(reference)
|
||||
return references_created
|
||||
def add_metric_reference(
|
||||
file: ifcopenshell.file, metric: ifcopenshell.entity_instance, reference_path: str
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
Adds a chain of references to a metric. The reference path is a string of the form "attribute.attribute.attribute"
|
||||
Used to reference a value of an attribute of an instance through a metric objective entity.
|
||||
"""
|
||||
settings = {"metric": metric, "reference_path": reference_path}
|
||||
|
||||
references_created = []
|
||||
if settings["reference_path"]:
|
||||
attributes = settings["reference_path"].split(".")
|
||||
for i in range(len(attributes)):
|
||||
if i == 0:
|
||||
reference = file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
settings["metric"].ReferencePath = reference
|
||||
references_created.append(reference)
|
||||
else:
|
||||
reference = file.create_entity("IfcReference")
|
||||
reference.AttributeIdentifier = attributes[i]
|
||||
references_created[i - 1].InnerReference = reference
|
||||
references_created.append(reference)
|
||||
return references_created
|
||||
|
||||
@@ -19,34 +19,31 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file):
|
||||
"""Add a new objective constraint
|
||||
def add_objective(file: ifcopenshell.file) -> ifcopenshell.entity_instance:
|
||||
"""Add a new objective constraint
|
||||
|
||||
Parametric constraints may be defined by the user. The constraint is defined
|
||||
by first creating an objective describing the purpose of the constraint and
|
||||
whether it is a hard or soft constraint. Later on, metrics may be added to
|
||||
check whether the constraint has been met by connecting it to properties and
|
||||
quantities. See ifcopenshell.api.constraint.add_metric for more information.
|
||||
Parametric constraints may be defined by the user. The constraint is defined
|
||||
by first creating an objective describing the purpose of the constraint and
|
||||
whether it is a hard or soft constraint. Later on, metrics may be added to
|
||||
check whether the constraint has been met by connecting it to properties and
|
||||
quantities. See ifcopenshell.api.constraint.add_metric for more information.
|
||||
|
||||
:return: The newly created IfcObjective entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcObjective entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# Create a new objective for code compliance requirements
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
objective.ConstraintGrade = "ADVISORY"
|
||||
objective.ObjectiveQualifier = "CODECOMPLIANCE"
|
||||
# Note: the objective right now is purely qualitative and for
|
||||
# information purposes. You may wish to add quantiative metrics.
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {}
|
||||
# Create a new objective for code compliance requirements
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
objective.ConstraintGrade = "ADVISORY"
|
||||
objective.ObjectiveQualifier = "CODECOMPLIANCE"
|
||||
# Note: the objective right now is purely qualitative and for
|
||||
# information purposes. You may wish to add quantiative metrics.
|
||||
"""
|
||||
settings = {}
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(
|
||||
"IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"}
|
||||
)
|
||||
return file.create_entity(
|
||||
"IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"}
|
||||
)
|
||||
|
||||
@@ -18,42 +18,45 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Union
|
||||
|
||||
|
||||
def assign_constraint(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a constraint to a list of products
|
||||
|
||||
This assigns a relationship between a product and a constraint, so that
|
||||
when a product's properties and quantities do not match the requirements
|
||||
of the constraint's metrics, results can be flagged.
|
||||
|
||||
It is assumed (but not explicit in the IFC documentation) that
|
||||
constraints are inherited from the type. This way, it is not necessary
|
||||
to create lots of constraint assignments.
|
||||
|
||||
:param products: The list of products the constraint applies to. This is anything
|
||||
which can have properties or quantities.
|
||||
:type products: list[ifcopenshell.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: The new or updated IfcRelAssociatesConstraint relationship
|
||||
or `None` if `products` was an empty list.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns a constraint to a list of products
|
||||
|
||||
This assigns a relationship between a product and a constraint, so that
|
||||
when a product's properties and quantities do not match the requirements
|
||||
of the constraint's metrics, results can be flagged.
|
||||
|
||||
It is assumed (but not explicit in the IFC documentation) that
|
||||
constraints are inherited from the type. This way, it is not necessary
|
||||
to create lots of constraint assignments.
|
||||
|
||||
:param products: The list of products the constraint applies to. This is anything
|
||||
which can have properties or quantities.
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The new or updated IfcRelAssociatesConstraint relationship
|
||||
or `None` if `products` was an empty list.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def execute(self):
|
||||
products = set(self.settings["products"])
|
||||
if not products:
|
||||
return
|
||||
|
||||
@@ -15,35 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None, attributes=None):
|
||||
"""Edit the attributes of a metric
|
||||
def edit_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edit the attributes of a metric
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcMetric, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcMetric, consult the IFC documentation.
|
||||
|
||||
:param metric: The IfcMetric you want to edit.
|
||||
:type metric: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param metric: The IfcMetric you want to edit.
|
||||
:type metric: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.edit_metric", model,
|
||||
metric=metric, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric, "attributes": attributes or {}}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.edit_metric", model,
|
||||
metric=metric, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
settings = {"metric": metric, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["metric"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["metric"], name, value)
|
||||
|
||||
@@ -15,33 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, objective=None, attributes=None):
|
||||
"""Edit the attributes of a objective
|
||||
def edit_objective(
|
||||
file: ifcopenshell.file, objective: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edit the attributes of a objective
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcObjective, consult the IFC documentation.
|
||||
For more information about the attributes and data types of an
|
||||
IfcObjective, consult the IFC documentation.
|
||||
|
||||
:param objective: The IfcObjective you want to edit.
|
||||
:type objective: ifcopenshell.entity_instance.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict, optional
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param objective: The IfcObjective you want to edit.
|
||||
:type objective: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.edit_objective", model,
|
||||
objective=objective, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"objective": objective, "attributes": attributes or {}}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.edit_objective", model,
|
||||
objective=objective, attributes={"ConstraintGrade": "HARD"})
|
||||
"""
|
||||
settings = {"objective": objective, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["objective"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["objective"], name, value)
|
||||
|
||||
@@ -20,36 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, constraint=None):
|
||||
"""Remove a constraint (typically an objective)
|
||||
def remove_constraint(file: ifcopenshell.file, constraint: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a constraint (typically an objective)
|
||||
|
||||
Removes a constraint definition and all of its associations to any
|
||||
products. Typically this would be an IfcObjective, although technically
|
||||
you can associate IfcMetrics ith products too, though the meaning may be
|
||||
unclear.
|
||||
Removes a constraint definition and all of its associations to any
|
||||
products. Typically this would be an IfcObjective, although technically
|
||||
you can associate IfcMetrics ith products too, though the meaning may be
|
||||
unclear.
|
||||
|
||||
:param constraint: The IfcObjective you want to remove.
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param constraint: The IfcObjective you want to remove.
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.remove_constraint", model,
|
||||
constraint=objective)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"constraint": constraint}
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
ifcopenshell.api.run("constraint.remove_constraint", model,
|
||||
constraint=objective)
|
||||
"""
|
||||
settings = {"constraint": constraint}
|
||||
|
||||
def execute(self):
|
||||
self.file.remove(self.settings["constraint"])
|
||||
for rel in self.file.by_type("IfcRelAssociatesConstraint"):
|
||||
if not rel.RelatingConstraint:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
file.remove(settings["constraint"])
|
||||
for rel in file.by_type("IfcRelAssociatesConstraint"):
|
||||
if not rel.RelatingConstraint:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,33 +15,37 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def remove_metric(file: ifcopenshell.file, metric: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a metric benchmark
|
||||
|
||||
Removes a metric benchmark and all of its associations to any products
|
||||
and objectives.
|
||||
|
||||
:param metric: The IfcMetric you want to remove.
|
||||
:type metric: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.remove_metric", model,
|
||||
metric=metric)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"metric": metric}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, metric=None):
|
||||
"""Remove a metric benchmark
|
||||
|
||||
Removes a metric benchmark and all of its associations to any products
|
||||
and objectives.
|
||||
|
||||
:param metric: The IfcMetric you want to remove.
|
||||
:type metric: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
objective = ifcopenshell.api.run("constraint.add_objective", model)
|
||||
metric = ifcopenshell.api.run("constraint.add_metric", model,
|
||||
objective=objective)
|
||||
ifcopenshell.api.run("constraint.remove_metric", model,
|
||||
metric=metric)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"metric": metric}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["metric"].ReferencePath:
|
||||
reference = self.settings["metric"].ReferencePath
|
||||
|
||||
@@ -21,31 +21,33 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def unassign_constraint(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a constraint from a list of products
|
||||
|
||||
The constraint will not be deleted and is available to be assigned to
|
||||
other products.
|
||||
|
||||
:param products: The list of products the constraint applies to.
|
||||
:type products: list[ifcopenshell.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
constraint: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Unassigns a constraint from a list of products
|
||||
|
||||
The constraint will not be deleted and is available to be assigned to
|
||||
other products.
|
||||
|
||||
:param products: The list of products the constraint applies to.
|
||||
:type products: list[ifcopenshell.entity_instance.entity_instance]
|
||||
:param constraint: The IfcObjective constraint
|
||||
:type constraint: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"constraint": constraint,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
products = set(self.settings["products"])
|
||||
if not products:
|
||||
|
||||
@@ -15,3 +15,25 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Contexts allow you to classify when geometry should be used in different
|
||||
purposes
|
||||
|
||||
For example, a door may have many geometries assigned to it: a 3D body
|
||||
geometry, a clearance zone for disabled access and egress, and a 2D top down
|
||||
plan view representation annotating swing extents. Each geometry is assigned to
|
||||
a context to distinguish its purpose and level of detail.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_context import add_context
|
||||
from .edit_context import edit_context
|
||||
from .remove_context import remove_context
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_context",
|
||||
"edit_context",
|
||||
"remove_context",
|
||||
]
|
||||
|
||||
@@ -16,169 +16,181 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Optional, Literal
|
||||
|
||||
|
||||
def add_context(
|
||||
file: ifcopenshell.file,
|
||||
context_type: Optional[Literal["Model", "Plan"]] = None,
|
||||
context_identifier: Optional[str] = None,
|
||||
target_view: Optional[str] = None,
|
||||
parent: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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, optional
|
||||
:param context_identifier: The identifier of the context, chosen from
|
||||
one of the common identifiers above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type context_identifier: str, optional
|
||||
:param target_view: the target view of the context, chosen from one of
|
||||
the common target views above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type target_view: str, optional
|
||||
:param parent: the parent context. Must be left as None (the default)
|
||||
for contexts, and only set for subcontexts. Note that there are only
|
||||
contexts and subcontexts, a subcontext cannot have any children.
|
||||
:type parent: ifcopenshell.entity_instance, optional
|
||||
:return: the newly created IfcGeometricRepresentationContext or
|
||||
IfcGeometricRepresentationSubContext entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# If we plan to store 3D geometry in our IFC model, we have to setup
|
||||
# a "Model" context.
|
||||
model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
|
||||
# And/Or, if we plan to store 2D geometry, we need a "Plan" context
|
||||
plan = ifcopenshell.api.run("context.add_context", model, context_type="Plan")
|
||||
|
||||
# Now we setup the subcontexts with each of the geometric "purposes"
|
||||
# we plan to store in our model. "Body" is by far the most important
|
||||
# and common context, as most IFC models are assumed to be viewable
|
||||
# in 3D.
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Axis subcontext is important if any "axis-based" parametric
|
||||
# geometry is going to be created. For example, a beam, or column
|
||||
# may be drawn using a single 3D axis line, and for this we need an
|
||||
# Axis subcontext.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Box subcontext is useful for clash detection or shape
|
||||
# analysis, or even lazy-loading of large models.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# It's also important to have a 2D Axis subcontext for things like
|
||||
# walls and claddings which can be drawn using a 2D axis line.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
|
||||
|
||||
# A 2D annotation subcontext for plan views are important for door
|
||||
# swings, window cuts, and symbols for equipment like GPOs, fire
|
||||
# extinguishers, and so on.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
|
||||
|
||||
# You may also create 2D annotation subcontexts for sections and
|
||||
# elevation views.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
|
||||
|
||||
# Let's create a new wall. The wall does not have any geometry yet.
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
|
||||
# Let's use the "3D Body" representation we created earlier to add a
|
||||
# new wall-like body geometry, 5 meters long, 3 meters high, and
|
||||
# 200mm thick
|
||||
representation = ifcopenshell.api.run("geometry.add_wall_representation", model,
|
||||
context=body, length=5, height=3, thickness=0.2)
|
||||
|
||||
# Assign our new body geometry back to our wall
|
||||
ifcopenshell.api.run("geometry.assign_representation", model,
|
||||
product=wall, representation=representation)
|
||||
|
||||
# Place our wall at the origin
|
||||
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context_type": context_type,
|
||||
"parent": parent,
|
||||
"context_identifier": context_identifier,
|
||||
"target_view": target_view,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context_type=None, context_identifier=None, target_view=None, parent=None):
|
||||
"""Adds a new geometric representation context
|
||||
|
||||
In IFC, physical objects may have zero, one, or multiple geometric
|
||||
representations associated with it. For example, a building storey might
|
||||
not have any geometry, but simply be a coordinate in space.
|
||||
Alternatively, a wall might have a 3D body representation in the form of
|
||||
a cuboid. As a final example, a door might also have a 3D body
|
||||
representation of a 3D door panel and door frame, but may additionally
|
||||
have a 2D door plan view representation of the door swing, and even a 2D
|
||||
elevation view of the door, a 3D box representing the disabled clearance
|
||||
zone of the door, a 2D profile representing the profile of the door to
|
||||
cut out in a wall, and so on. In this situation, a door will have
|
||||
multiple geometric representations.
|
||||
|
||||
To distinguish between the different purposes of multiple geometric
|
||||
representations, each geometric representation must belong to a
|
||||
geometric representation "context". There are typically always 2
|
||||
contexts, one for 3D representations and one for 2D representations.
|
||||
These 2 contexts then have subcontexts for things like the 3D body
|
||||
representation, clearance representations, annotation representations,
|
||||
and so on. Each representation of a physical IFC product (e.g. a door)
|
||||
must be assigned to one of these subcontexts. Therefore setting up
|
||||
appropriate contexts is critical prior to authoring any IFC model which
|
||||
contains geometry.
|
||||
|
||||
There are two steps to setting up appropriate subcontexts. First, a 2D
|
||||
and/or 3D context must be added. These must be always called the "Model"
|
||||
context for 3D and the "Plan" context for 2D (even if the 2D geometry is
|
||||
not a plan view). Then, one or more subcontexts are added using either
|
||||
the "Model" or "Plan" as their parent. These subcontexts are further
|
||||
distinguished using an "identifier" and "target view". The "identifier"
|
||||
describes the purpose of the representation, and the "target view"
|
||||
describes the typical diagrammatic presentation that context's geometry
|
||||
should be viewed in. The most common identifiers you might use are:
|
||||
|
||||
- Body: for the actual shape of the object
|
||||
- Box: the bounding box of the object (useful for shape analytics)
|
||||
- Axis: the parametric line determining the shape of the object
|
||||
- Profile: the elevation silhouette of the object, useful for cutting
|
||||
out holes for the object to fit into host elements
|
||||
- Footprint: the plan view silhouette of the object, useful for certain
|
||||
quantity take-off rules
|
||||
- Clearance: the clearance zone of the object
|
||||
- Annotation: symbolic annotations typically used in diagrams or
|
||||
drawings
|
||||
|
||||
The most common "target views" you might use are:
|
||||
|
||||
- MODEL_VIEW: for 3D geometry you might see in a BIM viewer
|
||||
- PLAN_VIEW: for 2D geometry you might see in a plan representation
|
||||
- ELEVATION_VIEW: for 2D geometry you might see in an elevation representation
|
||||
- SECTION_VIEW: for 2D geometry you might see in a section representation
|
||||
- GRAPH_VIEW: for 2D or 3D line or frame or path connectivity diagrams
|
||||
you might use for structural frame analysis, axis-based parametric
|
||||
modeling
|
||||
- SKETCH_VIEW: for viewing abstract high-level representations such as
|
||||
in bubble diagrams of spatial topology
|
||||
|
||||
This may sound like a lot, but after a few typical contexts are set up
|
||||
at the beginning, it becomes easy to navigate and isolate geometry for
|
||||
different purposes. There is also the concept of a target scale, which
|
||||
represents the zoom level detail of geometry, but this is not currently
|
||||
supported by this API. Setting up all these contexts are also optional,
|
||||
and you may only use a single Model context and Body subcontext for
|
||||
simple models, but this simplification sacrifices the ability of more
|
||||
parametric or analytical usecases.
|
||||
|
||||
:param context_type: The type of the context, must be one of "Model" or
|
||||
"Plan" only.
|
||||
:type context_type: str
|
||||
:param context_identifier: The identifier of the context, chosen from
|
||||
one of the common identifiers above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type context_identifier: str, optional
|
||||
:param target_view: the target view of the context, chosen from one of
|
||||
the common target views above or consult the IFC documentation
|
||||
(under the IfcShapeRepresentation page) for more details. Optional
|
||||
for contexts, but mandatory for subcontexts.
|
||||
:type target_view: str, optional
|
||||
:param parent: the parent context. Must be left as None (the default)
|
||||
for contexts, and only set for subcontexts. Note that there are only
|
||||
contexts and subcontexts, a subcontext cannot have any children.
|
||||
:type parent: ifcopenshell.entity_instance.entity_instance, optional
|
||||
:return: the newly created IfcGeometricRepresentationContext or
|
||||
IfcGeometricRepresentationSubContext entity
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, optional
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
# If we plan to store 3D geometry in our IFC model, we have to setup
|
||||
# a "Model" context.
|
||||
model3d = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
|
||||
# And/Or, if we plan to store 2D geometry, we need a "Plan" context
|
||||
plan = ifcopenshell.api.run("context.add_context", model, context_type="Plan")
|
||||
|
||||
# Now we setup the subcontexts with each of the geometric "purposes"
|
||||
# we plan to store in our model. "Body" is by far the most important
|
||||
# and common context, as most IFC models are assumed to be viewable
|
||||
# in 3D.
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Axis subcontext is important if any "axis-based" parametric
|
||||
# geometry is going to be created. For example, a beam, or column
|
||||
# may be drawn using a single 3D axis line, and for this we need an
|
||||
# Axis subcontext.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Axis", target_view="GRAPH_VIEW", parent=model3d)
|
||||
|
||||
# The 3D Box subcontext is useful for clash detection or shape
|
||||
# analysis, or even lazy-loading of large models.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Box", target_view="MODEL_VIEW", parent=model3d)
|
||||
|
||||
# It's also important to have a 2D Axis subcontext for things like
|
||||
# walls and claddings which can be drawn using a 2D axis line.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Axis", target_view="GRAPH_VIEW", parent=plan)
|
||||
|
||||
# A 2D annotation subcontext for plan views are important for door
|
||||
# swings, window cuts, and symbols for equipment like GPOs, fire
|
||||
# extinguishers, and so on.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="PLAN_VIEW", parent=plan)
|
||||
|
||||
# You may also create 2D annotation subcontexts for sections and
|
||||
# elevation views.
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="SECTION_VIEW", parent=plan)
|
||||
ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Plan", context_identifier="Annotation", target_view="ELEVATION_VIEW", parent=plan)
|
||||
|
||||
# Let's create a new wall. The wall does not have any geometry yet.
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
|
||||
# Let's use the "3D Body" representation we created earlier to add a
|
||||
# new wall-like body geometry, 5 meters long, 3 meters high, and
|
||||
# 200mm thick
|
||||
representation = ifcopenshell.api.run("geometry.add_wall_representation", model,
|
||||
context=body, length=5, height=3, thickness=0.2)
|
||||
|
||||
# Assign our new body geometry back to our wall
|
||||
ifcopenshell.api.run("geometry.assign_representation", model,
|
||||
product=wall, representation=representation)
|
||||
|
||||
# Place our wall at the origin
|
||||
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context_type": context_type,
|
||||
"parent": parent,
|
||||
"context_identifier": context_identifier,
|
||||
"target_view": target_view,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["parent"]:
|
||||
if self.settings["context_type"] == "Plan":
|
||||
|
||||
@@ -16,38 +16,38 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context, attributes):
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcGeometricRepresentationContext, consult the IFC documentation.
|
||||
def edit_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
|
||||
"""Edits the attributes of an IfcGeometricRepresentationContext
|
||||
|
||||
: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
|
||||
For more information about the attributes and data types of an
|
||||
IfcGeometricRepresentationContext, consult the IFC documentation.
|
||||
|
||||
Example:
|
||||
:param context: The IfcGeometricRepresentationContext entity you want to edit
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
.. code:: python
|
||||
Example:
|
||||
|
||||
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
|
||||
)
|
||||
.. code:: python
|
||||
|
||||
# Let's fix it!
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": context, "attributes": attributes or {}}
|
||||
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
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["context"], name, value)
|
||||
# Let's fix it!
|
||||
ifcopenshell.api.run("context.edit_context", model,
|
||||
context=body, attributes={"ContextIdentifier": "Body"})
|
||||
"""
|
||||
settings = {"context": context, "attributes": attributes}
|
||||
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["context"], name, value)
|
||||
|
||||
@@ -17,51 +17,50 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, context=None):
|
||||
"""Removes an IfcGeometricRepresentationContext
|
||||
def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes an IfcGeometricRepresentationContext
|
||||
|
||||
Any representation geometry that is assigned to the context is also
|
||||
removed. If a context is removed, then any subcontexts are also removed.
|
||||
Any representation geometry that is assigned to the context is also
|
||||
removed. If a context is removed, then any subcontexts are also removed.
|
||||
|
||||
:param context: The IfcGeometricRepresentationContext entity to remove
|
||||
:type context: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param context: The IfcGeometricRepresentationContext entity to remove
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
model = ifcopenshell.api.run("context.add_context", model, context_type="Model")
|
||||
# Revit had a bug where they incorrectly called the body representation a "Facetation"
|
||||
body = ifcopenshell.api.run("context.add_context", model,
|
||||
context_type="Model", context_identifier="Facetation", target_view="MODEL_VIEW", parent=model
|
||||
)
|
||||
|
||||
# Let's just get rid of it completely
|
||||
ifcopenshell.api.run("context.remove_context", model, context=body)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"context": context}
|
||||
# Let's just get rid of it completely
|
||||
ifcopenshell.api.run("context.remove_context", model, context=body)
|
||||
"""
|
||||
settings = {"context": context}
|
||||
|
||||
def execute(self):
|
||||
for subcontext in self.settings["context"].HasSubContexts:
|
||||
ifcopenshell.api.run("context.remove_context", self.file, context=subcontext)
|
||||
for subcontext in settings["context"].HasSubContexts:
|
||||
ifcopenshell.api.run("context.remove_context", file, context=subcontext)
|
||||
|
||||
if getattr(self.settings["context"], "ParentContext", None):
|
||||
new = self.settings["context"].ParentContext
|
||||
for inverse in self.file.get_inverse(self.settings["context"]):
|
||||
if inverse.is_a("IfcCoordinateOperation"):
|
||||
inverse.SourceCRS = inverse.TargetCRS
|
||||
ifcopenshell.util.element.remove_deep(self.file, inverse)
|
||||
else:
|
||||
ifcopenshell.util.element.replace_attribute(inverse, self.settings["context"], new)
|
||||
self.file.remove(self.settings["context"])
|
||||
else:
|
||||
representations_in_context = self.settings["context"].RepresentationsInContext
|
||||
self.file.remove(self.settings["context"])
|
||||
for element in representations_in_context:
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, representation=element)
|
||||
if getattr(settings["context"], "ParentContext", None):
|
||||
new = settings["context"].ParentContext
|
||||
for inverse in file.get_inverse(settings["context"]):
|
||||
if inverse.is_a("IfcCoordinateOperation"):
|
||||
inverse.SourceCRS = inverse.TargetCRS
|
||||
ifcopenshell.util.element.remove_deep(file, inverse)
|
||||
else:
|
||||
ifcopenshell.util.element.replace_attribute(inverse, settings["context"], new)
|
||||
file.remove(settings["context"])
|
||||
else:
|
||||
representations_in_context = settings["context"].RepresentationsInContext
|
||||
file.remove(settings["context"])
|
||||
for element in representations_in_context:
|
||||
ifcopenshell.api.run("geometry.remove_representation", file, representation=element)
|
||||
|
||||
@@ -15,3 +15,20 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Processes and costs may be controlled by other entities which indicate
|
||||
constraints that determine how they can change
|
||||
|
||||
This is an advanced feature mostly used in 4D/5D
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_control import assign_control
|
||||
from .unassign_control import unassign_control
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_control",
|
||||
"unassign_control",
|
||||
]
|
||||
|
||||
@@ -18,89 +18,89 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_control=None, related_object=None):
|
||||
"""Assigns a planning control or constraint to an object
|
||||
def assign_control(
|
||||
file: ifcopenshell.file,
|
||||
relating_control: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a planning control or constraint to an object
|
||||
|
||||
IFC can describe concepts that control other objects. For example, a
|
||||
planning calendar controls the availability of working days for
|
||||
construction planning. As another example, a cost item might constrain
|
||||
or limit the ability to procure and build a product.
|
||||
IFC can describe concepts that control other objects. For example, a
|
||||
planning calendar controls the availability of working days for
|
||||
construction planning. As another example, a cost item might constrain
|
||||
or limit the ability to procure and build a product.
|
||||
|
||||
This usecase lets you assign controls following the rules of the IFC
|
||||
specification. This is an advanced topic and assumes knowledge of the
|
||||
IFC concepts to determine what is allowed to control what. In the
|
||||
future, this API will likely be deprecated in favour of multiple usecase
|
||||
specific APIs.
|
||||
This usecase lets you assign controls following the rules of the IFC
|
||||
specification. This is an advanced topic and assumes knowledge of the
|
||||
IFC concepts to determine what is allowed to control what. In the
|
||||
future, this API will likely be deprecated in favour of multiple usecase
|
||||
specific APIs.
|
||||
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The newly created IfcRelAssignsToControl. If relationship already
|
||||
existed before and wasn't changed then returns None.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, None
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: The newly created IfcRelAssignsToControl. If relationship already
|
||||
existed before and wasn't changed then returns None.
|
||||
:rtype: ifcopenshell.entity_instance, None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# One common usecase is to assign a calendar to a task
|
||||
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model)
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model)
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=schedule)
|
||||
# One common usecase is to assign a calendar to a task
|
||||
calendar = ifcopenshell.api.run("sequence.add_work_calendar", model)
|
||||
schedule = ifcopenshell.api.run("sequence.add_work_schedule", model)
|
||||
task = ifcopenshell.api.run("sequence.add_task", model,
|
||||
work_schedule=schedule)
|
||||
|
||||
# All subtasks will inherit this calendar, so assigning a single
|
||||
# calendar to the root task effectively defines a "default" calendar
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=calendar, related_object=task)
|
||||
# All subtasks will inherit this calendar, so assigning a single
|
||||
# calendar to the root task effectively defines a "default" calendar
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=calendar, related_object=task)
|
||||
|
||||
# Another common example might be relating a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
# Another common example might be relating a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for assignment in self.settings["related_object"].HasAssignments:
|
||||
if (
|
||||
assignment.is_a("IfcRelAssignsToControl")
|
||||
and assignment.RelatingControl == self.settings["relating_control"]
|
||||
):
|
||||
return
|
||||
|
||||
controls = None
|
||||
if self.settings["relating_control"].Controls:
|
||||
controls = self.settings["relating_control"].Controls[0]
|
||||
|
||||
if controls:
|
||||
if self.settings["related_object"] in controls.RelatedObjects:
|
||||
if settings["related_object"].HasAssignments:
|
||||
for assignment in settings["related_object"].HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl == settings["relating_control"]:
|
||||
return
|
||||
related_objects = set(controls.RelatedObjects)
|
||||
related_objects.add(self.settings["related_object"])
|
||||
controls.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": controls})
|
||||
else:
|
||||
controls = self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingControl": self.settings["relating_control"],
|
||||
},
|
||||
)
|
||||
return controls
|
||||
|
||||
controls = None
|
||||
if settings["relating_control"].Controls:
|
||||
controls = settings["relating_control"].Controls[0]
|
||||
|
||||
if controls:
|
||||
if settings["related_object"] in controls.RelatedObjects:
|
||||
return
|
||||
related_objects = set(controls.RelatedObjects)
|
||||
related_objects.add(settings["related_object"])
|
||||
controls.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": controls})
|
||||
else:
|
||||
controls = file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": [settings["related_object"]],
|
||||
"RelatingControl": settings["relating_control"],
|
||||
},
|
||||
)
|
||||
return controls
|
||||
|
||||
@@ -19,56 +19,58 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_control=None, related_object=None):
|
||||
"""Unassigns a planning control or constraint to an object
|
||||
def unassign_control(
|
||||
file: ifcopenshell.file,
|
||||
relating_control: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Unassigns a planning control or constraint to an object
|
||||
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance.entity_instance
|
||||
:return: If the control still is related to other objects, the
|
||||
IfcRelAssignsToControl is returned, otherwise None.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance, None
|
||||
:param relating_control: The IfcControl entity that is creating the
|
||||
control or constraint
|
||||
:type relating_control: ifcopenshell.entity_instance
|
||||
:param related_object: The IfcObjectDefinition that is being controlled
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: If the control still is related to other objects, the
|
||||
IfcRelAssignsToControl is returned, otherwise None.
|
||||
:rtype: ifcopenshell.entity_instance, None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# Let's relate a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
# Let's relate a cost item and a product
|
||||
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
cost_item = ifcopenshell.api.run("cost.add_cost_item", model,
|
||||
cost_schedule=schedule)
|
||||
ifcopenshell.api.run("control.assign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
|
||||
# And now let's change our mind
|
||||
ifcopenshell.api.run("control.unassign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
# And now let's change our mind
|
||||
ifcopenshell.api.run("control.unassign_control", model,
|
||||
relating_control=cost_item, related_object=wall)
|
||||
"""
|
||||
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
settings = {
|
||||
"relating_control": relating_control,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != self.settings["relating_control"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
for rel in settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != settings["relating_control"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
return rel
|
||||
|
||||
@@ -15,3 +15,56 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manage cost schedules, cost items, cost estimation and parametric quantity
|
||||
take-off
|
||||
|
||||
IFC supports storing cost schedules and detailed cost breakdown structures,
|
||||
including formulas, subtotals, and parametric links to model element
|
||||
quantities.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_cost_item import add_cost_item
|
||||
from .add_cost_item_quantity import add_cost_item_quantity
|
||||
from .add_cost_schedule import add_cost_schedule
|
||||
from .add_cost_value import add_cost_value
|
||||
from .assign_cost_item_quantity import assign_cost_item_quantity
|
||||
from .assign_cost_value import assign_cost_value
|
||||
from .calculate_cost_item_resource_value import calculate_cost_item_resource_value
|
||||
from .copy_cost_item import copy_cost_item
|
||||
from .copy_cost_item_values import copy_cost_item_values
|
||||
from .edit_cost_item import edit_cost_item
|
||||
from .edit_cost_item_quantity import edit_cost_item_quantity
|
||||
from .edit_cost_schedule import edit_cost_schedule
|
||||
from .edit_cost_value import edit_cost_value
|
||||
from .edit_cost_value_formula import edit_cost_value_formula
|
||||
from .remove_cost_item import remove_cost_item
|
||||
from .remove_cost_item_quantity import remove_cost_item_quantity
|
||||
from .remove_cost_schedule import remove_cost_schedule
|
||||
from .remove_cost_value import remove_cost_value
|
||||
from .unassign_cost_item_quantity import unassign_cost_item_quantity
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_cost_item",
|
||||
"add_cost_item_quantity",
|
||||
"add_cost_schedule",
|
||||
"add_cost_value",
|
||||
"assign_cost_item_quantity",
|
||||
"assign_cost_value",
|
||||
"calculate_cost_item_resource_value",
|
||||
"copy_cost_item",
|
||||
"copy_cost_item_values",
|
||||
"edit_cost_item",
|
||||
"edit_cost_item_quantity",
|
||||
"edit_cost_schedule",
|
||||
"edit_cost_value",
|
||||
"edit_cost_value_formula",
|
||||
"remove_cost_item",
|
||||
"remove_cost_item_quantity",
|
||||
"remove_cost_schedule",
|
||||
"remove_cost_value",
|
||||
"unassign_cost_item_quantity",
|
||||
]
|
||||
|
||||
@@ -17,57 +17,62 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_schedule=None, cost_item=None):
|
||||
"""Add a new cost item
|
||||
def add_cost_item(
|
||||
file: ifcopenshell.file,
|
||||
cost_schedule: Optional[ifcopenshell.entity_instance] = None,
|
||||
cost_item: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
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
|
||||
Either `cost_schedule` or `cost_item` must be provided.
|
||||
|
||||
Example:
|
||||
: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, optional.
|
||||
: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, optional
|
||||
:return: The newly created IfcCostItem
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
.. code:: python
|
||||
Example:
|
||||
|
||||
# The very first cost item must be in a cost schedule
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
.. code:: python
|
||||
|
||||
# You may add cost items as top level item in the schedule
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
# The very first cost item must be in a cost schedule
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
|
||||
# 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": cost_schedule, "cost_item": cost_item}
|
||||
# You may add cost items as top level item in the schedule
|
||||
item1 = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
|
||||
def execute(self):
|
||||
cost_item = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcCostItem")
|
||||
# Alternatively you may add them as subitems
|
||||
item2 = ifcopenshell.api.run("cost.add_cost_item", model, cost_item=item1)
|
||||
"""
|
||||
settings = {"cost_schedule": cost_schedule, "cost_item": cost_item}
|
||||
|
||||
if self.settings["cost_schedule"]:
|
||||
self.file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [cost_item],
|
||||
"RelatingControl": self.settings["cost_schedule"],
|
||||
}
|
||||
)
|
||||
elif self.settings["cost_item"]:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", self.file, related_objects=[cost_item], relating_object=self.settings["cost_item"]
|
||||
)
|
||||
return cost_item
|
||||
cost_item = ifcopenshell.api.run("root.create_entity", file, ifc_class="IfcCostItem")
|
||||
|
||||
if settings["cost_schedule"]:
|
||||
file.create_entity(
|
||||
"IfcRelAssignsToControl",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": [cost_item],
|
||||
"RelatingControl": settings["cost_schedule"],
|
||||
},
|
||||
)
|
||||
elif settings["cost_item"]:
|
||||
ifcopenshell.api.run(
|
||||
"nest.assign_object", file, related_objects=[cost_item], relating_object=settings["cost_item"]
|
||||
)
|
||||
return cost_item
|
||||
|
||||
@@ -17,75 +17,79 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None, ifc_class="IfcQuantityCount"):
|
||||
"""Adds a new quantity associated with a cost item
|
||||
def add_cost_item_quantity(
|
||||
file: ifcopenshell.file,
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
ifc_class: ifcopenshell.util.unit.QUANTITY_CLASS = "IfcQuantityCount",
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
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:
|
||||
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
|
||||
- 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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
:param cost_item: The IfcCostItem to add the quantity to
|
||||
:type cost_item: ifcopenshell.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
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
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=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": cost_item, "ifc_class": ifc_class}
|
||||
# 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")
|
||||
"""
|
||||
settings = {"cost_item": cost_item, "ifc_class": ifc_class}
|
||||
|
||||
def execute(self):
|
||||
quantity = self.file.create_entity(self.settings["ifc_class"], Name="Unnamed")
|
||||
quantity = file.create_entity(settings["ifc_class"], Name="Unnamed")
|
||||
# 3 IfcPhysicalSimpleQuantity Value
|
||||
# This is a bold assumption
|
||||
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
|
||||
if settings["ifc_class"] == "IfcQuantityCount":
|
||||
count = 0
|
||||
for rel in settings["cost_item"].Controls:
|
||||
count += len(rel.RelatedObjects)
|
||||
quantity[3] = count
|
||||
else:
|
||||
quantity[3] = 0.0
|
||||
# This is a bold assumption
|
||||
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
|
||||
if self.settings["ifc_class"] == "IfcQuantityCount" and self.settings["cost_item"].Controls:
|
||||
count = 0
|
||||
for rel in self.settings["cost_item"].Controls:
|
||||
count += len(rel.RelatedObjects)
|
||||
quantity[3] = count
|
||||
quantities = list(self.settings["cost_item"].CostQuantities or [])
|
||||
quantities.append(quantity)
|
||||
self.settings["cost_item"].CostQuantities = quantities
|
||||
return quantity
|
||||
quantities = list(settings["cost_item"].CostQuantities or [])
|
||||
quantities.append(quantity)
|
||||
settings["cost_item"].CostQuantities = quantities
|
||||
return quantity
|
||||
|
||||
@@ -19,50 +19,62 @@
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, name=None, predefined_type="NOTDEFINED"):
|
||||
"""Add a new cost schedule
|
||||
def add_cost_schedule(
|
||||
file: ifcopenshell.file, name: Optional[str] = None, predefined_type: str = "NOTDEFINED"
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
: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
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": name, "predefined_type": predefined_type}
|
||||
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)
|
||||
"""
|
||||
settings = {"name": name, "predefined_type": predefined_type}
|
||||
|
||||
def execute(self):
|
||||
cost_schedule = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
ifc_class="IfcCostSchedule",
|
||||
predefined_type=self.settings["predefined_type"],
|
||||
name=self.settings["name"],
|
||||
)
|
||||
cost_schedule = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
file,
|
||||
ifc_class="IfcCostSchedule",
|
||||
predefined_type=settings["predefined_type"],
|
||||
name=settings["name"],
|
||||
)
|
||||
if file.schema == "IFC2X3":
|
||||
cost_schedule.UpdateDate = createIfcDateAndTime(file, datetime.now())
|
||||
else:
|
||||
cost_schedule.UpdateDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime")
|
||||
return cost_schedule
|
||||
return cost_schedule
|
||||
|
||||
|
||||
def createIfcDateAndTime(file: ifcopenshell.file, dt: datetime):
|
||||
ifc_dt = file.create_entity("IfcDateAndTime")
|
||||
ifc_dt.DateComponent = file.create_entity(
|
||||
"IfcCalendarDate", **ifcopenshell.util.date.datetime2ifc(dt, "IfcCalendarDate")
|
||||
)
|
||||
ifc_dt.TimeComponent = file.create_entity("IfcLocalTime", **ifcopenshell.util.date.datetime2ifc(dt, "IfcLocalTime"))
|
||||
return ifc_dt
|
||||
|
||||
@@ -15,97 +15,95 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, parent=None):
|
||||
"""Adds a new value or subvalue to a cost item
|
||||
def add_cost_value(file: ifcopenshell.file, parent: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Adds a new value or subvalue to a cost item
|
||||
|
||||
A cost item's subtotal can be specified in two ways.
|
||||
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 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.
|
||||
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).
|
||||
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.
|
||||
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.
|
||||
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
|
||||
: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
|
||||
:return: The newly created IfcCostValue
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
# We always need a schedule first prior to adding any cost items
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
# 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 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})
|
||||
# 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)
|
||||
# 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": parent}
|
||||
# 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})
|
||||
"""
|
||||
settings = {"parent": parent}
|
||||
|
||||
def execute(self):
|
||||
value = self.file.create_entity("IfcCostValue")
|
||||
if self.settings["parent"].is_a("IfcCostItem"):
|
||||
values = list(self.settings["parent"].CostValues or [])
|
||||
values.append(value)
|
||||
self.settings["parent"].CostValues = values
|
||||
elif self.settings["parent"].is_a("IfcConstructionResource"):
|
||||
values = list(self.settings["parent"].BaseCosts or [])
|
||||
values.append(value)
|
||||
self.settings["parent"].BaseCosts = values
|
||||
elif self.settings["parent"].is_a("IfcCostValue"):
|
||||
values = list(self.settings["parent"].Components or [])
|
||||
values.append(value)
|
||||
self.settings["parent"].Components = values
|
||||
return value
|
||||
value = file.create_entity("IfcCostValue")
|
||||
if settings["parent"].is_a("IfcCostItem"):
|
||||
values = list(settings["parent"].CostValues or [])
|
||||
values.append(value)
|
||||
settings["parent"].CostValues = values
|
||||
elif settings["parent"].is_a("IfcConstructionResource"):
|
||||
values = list(settings["parent"].BaseCosts or [])
|
||||
values.append(value)
|
||||
settings["parent"].BaseCosts = values
|
||||
elif settings["parent"].is_a("IfcCostValue"):
|
||||
values = list(settings["parent"].Components or [])
|
||||
values.append(value)
|
||||
settings["parent"].Components = values
|
||||
return value
|
||||
|
||||
@@ -17,84 +17,97 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api
|
||||
from typing import Any
|
||||
|
||||
|
||||
def assign_cost_item_quantity(
|
||||
file: ifcopenshell.file,
|
||||
cost_item: ifcopenshell.entity_instance,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
prop_name: str = "",
|
||||
) -> None:
|
||||
"""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.
|
||||
|
||||
If cost item has just 1 quantity and it's IfcQuantityCount, API will
|
||||
assume that quantity is used for counting controlled objects
|
||||
and it will recalculate the quantity value at the end of the API call.
|
||||
|
||||
:param cost_item: The IfcCostItem to assign parametric quantities to
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:param products: The IfcObjects to assign parametric quantities to
|
||||
:type products: list[ifcopenshell.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")
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"cost_item": cost_item,
|
||||
"products": products or [],
|
||||
"prop_name": prop_name,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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": cost_item,
|
||||
"products": products or [],
|
||||
"prop_name": prop_name,
|
||||
}
|
||||
file: ifcopenshell.file
|
||||
settings: dict[str, Any]
|
||||
|
||||
def execute(self):
|
||||
if self.settings["prop_name"]:
|
||||
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
|
||||
for product in self.settings["products"]:
|
||||
self.assign_cost_control(
|
||||
related_object=product, cost_item=self.settings["cost_item"]
|
||||
)
|
||||
self.assign_cost_control(related_object=product, cost_item=self.settings["cost_item"])
|
||||
if self.settings["prop_name"]:
|
||||
if (
|
||||
self.settings["cost_item"].CostQuantities
|
||||
and self.settings["cost_item"].CostQuantities[0].Name.lower()
|
||||
!= self.settings["prop_name"].lower()
|
||||
and self.settings["cost_item"].CostQuantities[0].Name.lower() != self.settings["prop_name"].lower()
|
||||
) or not product.is_a("IfcObject"):
|
||||
continue
|
||||
self.add_quantity_from_related_object(product)
|
||||
@@ -103,7 +116,9 @@ class Usecase:
|
||||
else:
|
||||
self.update_cost_item_count()
|
||||
|
||||
def assign_cost_control(self, related_object, cost_item):
|
||||
def assign_cost_control(
|
||||
self, related_object: ifcopenshell.entity_instance, cost_item: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
return ifcopenshell.api.run(
|
||||
"control.assign_control",
|
||||
self.file,
|
||||
@@ -111,19 +126,16 @@ class Usecase:
|
||||
relating_control=cost_item,
|
||||
)
|
||||
|
||||
def add_quantity_from_related_object(self, element):
|
||||
def add_quantity_from_related_object(self, element: ifcopenshell.entity_instance) -> None:
|
||||
for relationship in element.IsDefinedBy:
|
||||
if relationship.is_a("IfcRelDefinesByProperties"):
|
||||
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
|
||||
|
||||
def add_quantity_from_qto(self, qto):
|
||||
def add_quantity_from_qto(self, qto: ifcopenshell.entity_instance) -> None:
|
||||
if not qto.is_a("IfcElementQuantity"):
|
||||
return
|
||||
for prop in qto.Quantities:
|
||||
if (
|
||||
prop.is_a("IfcPhysicalSimpleQuantity")
|
||||
and prop.Name.lower() == self.settings["prop_name"].lower()
|
||||
):
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
|
||||
self.quantities.add(prop)
|
||||
|
||||
def update_cost_item_count(self):
|
||||
|
||||
@@ -19,60 +19,59 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None, cost_rate=None):
|
||||
"""Assigns a cost value to a cost item from a schedule of rates
|
||||
def assign_cost_value(
|
||||
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, cost_rate: ifcopenshell.entity_instance
|
||||
) -> 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.
|
||||
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.
|
||||
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
|
||||
:param cost_item: The IfcCostItem that you want to copy the values to
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:param cost_rate: The IfcCostItem that you want to copy the values from
|
||||
:type cost_rate: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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})
|
||||
# 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)
|
||||
# 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": cost_item, "cost_rate": cost_rate}
|
||||
# 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)
|
||||
"""
|
||||
settings = {"cost_item": cost_item, "cost_rate": cost_rate}
|
||||
|
||||
def execute(self):
|
||||
if self.settings["cost_item"].CostValues:
|
||||
[
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_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
|
||||
if settings["cost_item"].CostValues:
|
||||
[
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_value",
|
||||
file,
|
||||
parent=settings["cost_item"],
|
||||
cost_value=cost_value,
|
||||
)
|
||||
for cost_value in settings["cost_item"].CostValues
|
||||
]
|
||||
# This is an assumption, and not part of the official IFC documentation
|
||||
settings["cost_item"].CostValues = settings["cost_rate"].CostValues
|
||||
|
||||
+80
-83
@@ -21,100 +21,97 @@ import ifcopenshell.util.date
|
||||
import ifcopenshell.util.resource
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None):
|
||||
"""Calculates the total cost of all resources associated with a cost item
|
||||
def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance) -> 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.
|
||||
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 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.
|
||||
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
|
||||
:param cost_item: The IfcCostItem to calculate
|
||||
:type cost_item: ifccopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
# 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")
|
||||
# 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})
|
||||
# ... 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})
|
||||
# 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": cost_item}
|
||||
# (42 * 200) + 50000 = 58400 is our calculated cost
|
||||
ifcopenshell.api.run("cost.calculate_cost_item_resource_value", model, cost_item=item)
|
||||
"""
|
||||
settings = {"cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["cost_item"].CostValues or []:
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_value", self.file, parent=self.settings["cost_item"], cost_value=cost_value
|
||||
)
|
||||
for cost_value in settings["cost_item"].CostValues or []:
|
||||
ifcopenshell.api.run("cost.remove_cost_value", file, parent=settings["cost_item"], cost_value=cost_value)
|
||||
|
||||
resources = []
|
||||
for rel in self.settings["cost_item"].Controls or []:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcConstructionResource"):
|
||||
resources.append(related_object)
|
||||
elif related_object.is_a("IfcTask"):
|
||||
for rel2 in related_object.OperatesOn or []:
|
||||
for related_object2 in rel2.RelatedObjects:
|
||||
if related_object2.is_a("IfcConstructionResource"):
|
||||
resources.append(related_object2)
|
||||
resources = []
|
||||
for rel in settings["cost_item"].Controls or []:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcConstructionResource"):
|
||||
resources.append(related_object)
|
||||
elif related_object.is_a("IfcTask"):
|
||||
for rel2 in related_object.OperatesOn or []:
|
||||
for related_object2 in rel2.RelatedObjects:
|
||||
if related_object2.is_a("IfcConstructionResource"):
|
||||
resources.append(related_object2)
|
||||
|
||||
for resource in resources:
|
||||
cost, unit = ifcopenshell.util.resource.get_cost(resource)
|
||||
if not cost:
|
||||
cost, unit = ifcopenshell.util.resource.get_parent_cost(resource) # Concept to standardise - Not defined in schema, but this makes manual scheduling of resources 10x faster and less duplicate data.
|
||||
quantity = ifcopenshell.util.resource.get_quantity(resource)
|
||||
if not cost or not quantity:
|
||||
continue
|
||||
if unit and "day" in unit:
|
||||
quantity = quantity / 8 # Assume 8 hour working day - TODO implement resource calendar
|
||||
quantity = round(quantity, 2)
|
||||
formula = "{}*{}".format(cost, quantity)
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", self.file, parent=self.settings["cost_item"])
|
||||
cost_value.Name = resource.Name
|
||||
ifcopenshell.api.run("cost.edit_cost_value_formula", self.file, cost_value=cost_value, formula=formula)
|
||||
for resource in resources:
|
||||
cost, unit = ifcopenshell.util.resource.get_cost(resource)
|
||||
if not cost:
|
||||
cost, unit = ifcopenshell.util.resource.get_parent_cost(
|
||||
resource
|
||||
) # Concept to standardise - Not defined in schema, but this makes manual scheduling of resources 10x faster and less duplicate data.
|
||||
quantity = ifcopenshell.util.resource.get_quantity(resource)
|
||||
if not cost or not quantity:
|
||||
continue
|
||||
if unit and "day" in unit:
|
||||
quantity = quantity / 8 # Assume 8 hour working day - TODO implement resource calendar
|
||||
quantity = round(quantity, 2)
|
||||
formula = "{}*{}".format(cost, quantity)
|
||||
cost_value = ifcopenshell.api.run("cost.add_cost_value", file, parent=settings["cost_item"])
|
||||
cost_value.Name = resource.Name
|
||||
ifcopenshell.api.run("cost.edit_cost_value_formula", file, cost_value=cost_value, formula=formula)
|
||||
|
||||
@@ -19,40 +19,48 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
def copy_cost_item(
|
||||
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance
|
||||
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]:
|
||||
# TODO: currently it never returns list of duplicated cost items
|
||||
# though it is stated in the docs
|
||||
"""Copies all cost items and related relationships
|
||||
|
||||
The following relationships are also duplicated:
|
||||
|
||||
* The copy will have the same attributes and property sets as the original cost item
|
||||
* The copy will be assigned to the parent cost schedule
|
||||
* The copy will have duplicated nested cost items
|
||||
|
||||
:param cost_item: The cost item to be duplicated
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:return: The duplicated cost item or the list of duplicated cost items if the latter has children
|
||||
:rtype: ifcopenshell.entity_instance or list[ifcopenshell.entity_instance]
|
||||
|
||||
Example:
|
||||
.. code:: python
|
||||
|
||||
# We have a cost item
|
||||
cost_item = CostItem(name="Design new feature", deadline="2023-03-01")
|
||||
|
||||
# And now we have two
|
||||
duplicated_cost_item = project.duplicate_cost_item(cost_item)
|
||||
|
||||
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"cost_item": cost_item}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None):
|
||||
"""Copies all cost items and related relationships
|
||||
|
||||
The following relationships are also duplicated:
|
||||
|
||||
* The copy will have the same attributes and property sets as the original cost item
|
||||
* The copy will be assigned to the parent cost schedule
|
||||
* The copy will have duplicated nested cost items
|
||||
|
||||
:param cost_item: The cost item to be duplicated
|
||||
:type cost_item: ifcopenshell.entity_instance.entity_instance
|
||||
:return: The duplicated cost item or the list of duplicated cost items if the latter has children
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance or list of ifcopenshell.entity_instance.entity_instance
|
||||
|
||||
Example:
|
||||
.. code:: python
|
||||
|
||||
# We have a cost item
|
||||
cost_item = CostItem(name="Design new feature", deadline="2023-03-01")
|
||||
|
||||
# And now we have two
|
||||
duplicated_cost_item = project.duplicate_cost_item(cost_item)
|
||||
|
||||
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
self.new_cost_items = []
|
||||
self.duplicate_cost_item(self.settings["cost_item"])
|
||||
return self.duplicate_cost_item(self.settings["cost_item"])
|
||||
|
||||
def duplicate_cost_item(self, cost_item):
|
||||
new_cost_item = ifcopenshell.util.element.copy_deep(self.file, cost_item)
|
||||
|
||||
@@ -20,45 +20,44 @@ import ifcopenshell.util.element
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, source=None, destination=None):
|
||||
"""Copies all cost values from one cost item to another
|
||||
def copy_cost_item_values(
|
||||
file: ifcopenshell.file, source: ifcopenshell.entity_instance, destination: ifcopenshell.entity_instance
|
||||
) -> 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.
|
||||
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
|
||||
:param source: The IfcCostItem to copy cost values from
|
||||
:type source: ifcopenshell.entity_instance
|
||||
:param destination: The IfcCostItem to copy cost values from
|
||||
:type destination: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
# 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})
|
||||
# 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": source, "destination": destination}
|
||||
# Let's copy the value from one item to another
|
||||
ifcopenshell.api.run("cost.copy_cost_item_values", model, source=item1, destination=item2)
|
||||
"""
|
||||
settings = {"source": source, "destination": destination}
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["destination"].CostValues or []:
|
||||
ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=cost_value)
|
||||
copied_cost_values = []
|
||||
for cost_value in self.settings["source"].CostValues or []:
|
||||
copied_cost_values.append(ifcopenshell.util.element.copy_deep(self.file, cost_value))
|
||||
self.settings["destination"].CostValues = copied_cost_values
|
||||
for cost_value in settings["destination"].CostValues or []:
|
||||
ifcopenshell.api.run("cost.remove_cost_item_value", file, cost_value=cost_value)
|
||||
copied_cost_values = []
|
||||
for cost_value in settings["source"].CostValues or []:
|
||||
copied_cost_values.append(ifcopenshell.util.element.copy_deep(file, cost_value))
|
||||
settings["destination"].CostValues = copied_cost_values
|
||||
|
||||
@@ -15,33 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostItem
|
||||
def edit_cost_item(
|
||||
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcCostItem
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostItem, consult the IFC documentation.
|
||||
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
|
||||
:param cost_item: The IfcCostItem entity you want to edit
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
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": cost_item, "attributes": attributes or {}}
|
||||
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"})
|
||||
"""
|
||||
settings = {"cost_item": cost_item, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["cost_item"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["cost_item"], name, value)
|
||||
|
||||
@@ -15,41 +15,42 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, physical_quantity=None, attributes=None):
|
||||
"""Edits the attributes of an IfcPhysicalQuantity
|
||||
def edit_cost_item_quantity(
|
||||
file: ifcopenshell.file, physical_quantity: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcPhysicalQuantity
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcPhysicalQuantity, consult the IFC documentation.
|
||||
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
|
||||
:param physical_quantity: The IfcPhysicalQuantity entity you want to edit
|
||||
:type physical_quantity: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
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": physical_quantity, "attributes": attributes or {}}
|
||||
# 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})
|
||||
"""
|
||||
settings = {"physical_quantity": physical_quantity, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["physical_quantity"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["physical_quantity"], name, value)
|
||||
|
||||
@@ -15,34 +15,35 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_schedule=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostSchedule
|
||||
def edit_cost_schedule(
|
||||
file: ifcopenshell.file, cost_schedule: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcCostSchedule
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostSchedule, consult the IFC documentation.
|
||||
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
|
||||
:param cost_schedule: The IfcCostSchedule entity you want to edit
|
||||
:type cost_schedule: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
ifcopenshell.api.run("cost.edit_cost_schedule", model,
|
||||
cost_schedule=schedule, attributes={"Name": "Foo"})
|
||||
"""
|
||||
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": cost_schedule, "attributes": attributes or {}}
|
||||
settings = {"cost_schedule": cost_schedule, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["cost_schedule"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["cost_schedule"], name, value)
|
||||
|
||||
@@ -19,50 +19,50 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_value=None, attributes=None):
|
||||
"""Edits the attributes of an IfcCostValue
|
||||
def edit_cost_value(
|
||||
file: ifcopenshell.file, cost_value: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcCostValue
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcCostValue, consult the IFC documentation.
|
||||
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
|
||||
:param cost_value: The IfcCostValue entity you want to edit
|
||||
:type cost_value: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
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": cost_value, "attributes": attributes or {}}
|
||||
# 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})
|
||||
"""
|
||||
settings = {"cost_value": cost_value, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if name == "AppliedValue" and value is not None:
|
||||
# TODO: support all applied value select types
|
||||
value = self.file.createIfcMonetaryMeasure(value)
|
||||
elif name == "UnitBasis":
|
||||
old_unit_basis = self.settings["cost_value"].UnitBasis
|
||||
if value:
|
||||
value_component = self.file.create_entity(
|
||||
ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType),
|
||||
value["ValueComponent"],
|
||||
)
|
||||
value = self.file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
|
||||
if old_unit_basis and len(self.file.get_inverse(old_unit_basis)) == 0:
|
||||
ifcopenshell.util.element.remove_deep(self.file, old_unit_basis)
|
||||
setattr(self.settings["cost_value"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
if name == "AppliedValue" and value is not None:
|
||||
# TODO: support all applied value select types
|
||||
value = file.createIfcMonetaryMeasure(value)
|
||||
elif name == "UnitBasis":
|
||||
old_unit_basis = settings["cost_value"].UnitBasis
|
||||
if value:
|
||||
value_component = file.create_entity(
|
||||
ifcopenshell.util.unit.get_unit_measure_class(value["UnitComponent"].UnitType),
|
||||
value["ValueComponent"],
|
||||
)
|
||||
value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
|
||||
if old_unit_basis and len(file.get_inverse(old_unit_basis)) == 0:
|
||||
ifcopenshell.util.element.remove_deep(file, old_unit_basis)
|
||||
setattr(settings["cost_value"], name, value)
|
||||
|
||||
@@ -17,42 +17,46 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def edit_cost_value_formula(file: ifcopenshell.file, cost_value: ifcopenshell.entity_instance, formula: str) -> 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
|
||||
: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")
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"cost_value": cost_value, "formula": formula or {}}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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": cost_value, "formula": formula or {}}
|
||||
|
||||
def execute(self):
|
||||
try:
|
||||
data = ifcopenshell.util.cost.unserialise_cost_value(self.settings["formula"], self.settings["cost_value"])
|
||||
|
||||
@@ -21,48 +21,45 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None):
|
||||
"""Removes a cost item
|
||||
def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance) -> 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.
|
||||
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
|
||||
:param cost_item: The IfcCostItem entity you want to remove
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": cost_item}
|
||||
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)
|
||||
"""
|
||||
settings = {"cost_item": cost_item}
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
for inverse in self.file.get_inverse(self.settings["cost_item"]):
|
||||
if inverse.is_a("IfcRelNests"):
|
||||
if inverse.RelatingObject == self.settings["cost_item"]:
|
||||
for related_object in inverse.RelatedObjects:
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=related_object)
|
||||
elif inverse.RelatedObjects == (self.settings["cost_item"],):
|
||||
history = inverse.OwnerHistory
|
||||
self.file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
# TODO: do a deep purge
|
||||
for inverse in file.get_inverse(settings["cost_item"]):
|
||||
if inverse.is_a("IfcRelNests"):
|
||||
if inverse.RelatingObject == settings["cost_item"]:
|
||||
for related_object in inverse.RelatedObjects:
|
||||
ifcopenshell.api.run("cost.remove_cost_item", file, cost_item=related_object)
|
||||
elif inverse.RelatedObjects == (settings["cost_item"],):
|
||||
history = inverse.OwnerHistory
|
||||
self.file.remove(inverse)
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
history = self.settings["cost_item"].OwnerHistory
|
||||
self.file.remove(self.settings["cost_item"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
history = settings["cost_item"].OwnerHistory
|
||||
file.remove(settings["cost_item"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,42 +15,42 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_item=None, physical_quantity=None):
|
||||
"""Removes a quantity assigned to a cost item
|
||||
def remove_cost_item_quantity(
|
||||
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, physical_quantity: ifcopenshell.entity_instance
|
||||
) -> 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.
|
||||
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
|
||||
:param cost_item: The IfcCostItem that the quantity is assigned to
|
||||
:type cost_item: ifcopenshell.entity_instance
|
||||
:param physical_quantity: The IfcPhysicalQuantity to remove
|
||||
:type physical_quantity: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": cost_item, "physical_quantity": physical_quantity}
|
||||
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)
|
||||
"""
|
||||
settings = {"cost_item": cost_item, "physical_quantity": physical_quantity}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["physical_quantity"])) == 1:
|
||||
self.file.remove(self.settings["physical_quantity"])
|
||||
return
|
||||
quantities = list(self.settings["cost_item"].CostQuantities or [])
|
||||
quantities.remove(self.settings["physical_quantity"])
|
||||
self.settings["cost_item"].CostQuantities = quantities
|
||||
if len(file.get_inverse(settings["physical_quantity"])) == 1:
|
||||
file.remove(settings["physical_quantity"])
|
||||
return
|
||||
quantities = list(settings["cost_item"].CostQuantities or [])
|
||||
quantities.remove(settings["physical_quantity"])
|
||||
settings["cost_item"].CostQuantities = quantities
|
||||
|
||||
@@ -21,41 +21,36 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, cost_schedule=None):
|
||||
"""Removes a cost schedule
|
||||
def remove_cost_schedule(file: ifcopenshell.file, cost_schedule: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a cost schedule
|
||||
|
||||
All associated relationships with the cost schedule are also removed,
|
||||
including all cost items.
|
||||
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
|
||||
:param cost_schedule: The IfcCostSchedule entity you want to remove
|
||||
:type cost_schedule: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": cost_schedule}
|
||||
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)
|
||||
"""
|
||||
settings = {"cost_schedule": cost_schedule}
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
for inverse in self.file.get_inverse(self.settings["cost_schedule"]):
|
||||
if inverse.is_a("IfcRelAssignsToControl"):
|
||||
[
|
||||
ifcopenshell.api.run(
|
||||
"cost.remove_cost_item", self.file, cost_item=related_object
|
||||
)
|
||||
for related_object in inverse.RelatedObjects
|
||||
if related_object.is_a("IfcCostItem")
|
||||
]
|
||||
history = self.settings["cost_schedule"].OwnerHistory
|
||||
self.file.remove(self.settings["cost_schedule"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
# TODO: do a deep purge
|
||||
for inverse in file.get_inverse(settings["cost_schedule"]):
|
||||
if inverse.is_a("IfcRelAssignsToControl"):
|
||||
[
|
||||
ifcopenshell.api.run("cost.remove_cost_item", file, cost_item=related_object)
|
||||
for related_object in inverse.RelatedObjects
|
||||
if related_object.is_a("IfcCostItem")
|
||||
]
|
||||
history = settings["cost_schedule"].OwnerHistory
|
||||
file.remove(settings["cost_schedule"])
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,53 +15,53 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, parent=None, cost_value=None):
|
||||
"""Removes a cost value
|
||||
def remove_cost_value(
|
||||
file: ifcopenshell.file, parent: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance
|
||||
) -> 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)
|
||||
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
|
||||
:param parent: The IfcCostItem, IfcConstructionResource, or IfcCostValue
|
||||
that the IfcCostValue is assigned to.
|
||||
:type parent: ifcopenshell.entity_instance
|
||||
:param cost_value: The IfcCostValue that you want to remove
|
||||
:type parent: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
schedule = ifcopenshell.api.run("cost.add_cost_schedule", model)
|
||||
item = ifcopenshell.api.run("cost.add_cost_item", model, cost_schedule=schedule)
|
||||
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})
|
||||
# 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": parent, "cost_value": cost_value}
|
||||
ifcopenshell.api.run("cost.remove_cost_value", model, parent=item, cost_value=value)
|
||||
"""
|
||||
settings = {"parent": parent, "cost_value": cost_value}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["cost_value"])) == 1:
|
||||
self.file.remove(self.settings["cost_value"])
|
||||
# TODO deep purge
|
||||
elif self.settings["parent"].is_a("IfcCostItem"):
|
||||
values = list(self.settings["parent"].CostValues)
|
||||
values.remove(self.settings["cost_value"])
|
||||
self.settings["parent"].CostValues = values if values else None
|
||||
elif self.settings["parent"].is_a("IfcConstructionResource"):
|
||||
values = list(self.settings["parent"].BaseCosts)
|
||||
values.remove(self.settings["cost_value"])
|
||||
self.settings["parent"].BaseCosts = values if values else None
|
||||
elif self.settings["parent"].is_a("IfcCostValue"):
|
||||
components = list(self.settings["parent"].Components)
|
||||
components.remove(self.settings["cost_value"])
|
||||
self.settings["parent"].Components = components if components else None
|
||||
if len(file.get_inverse(settings["cost_value"])) == 1:
|
||||
file.remove(settings["cost_value"])
|
||||
# TODO deep purge
|
||||
elif settings["parent"].is_a("IfcCostItem"):
|
||||
values = list(settings["parent"].CostValues)
|
||||
values.remove(settings["cost_value"])
|
||||
settings["parent"].CostValues = values if values else None
|
||||
elif settings["parent"].is_a("IfcConstructionResource"):
|
||||
values = list(settings["parent"].BaseCosts)
|
||||
values.remove(settings["cost_value"])
|
||||
settings["parent"].BaseCosts = values if values else None
|
||||
elif settings["parent"].is_a("IfcCostValue"):
|
||||
components = list(settings["parent"].Components)
|
||||
components.remove(settings["cost_value"])
|
||||
settings["parent"].Components = components if components else None
|
||||
|
||||
@@ -19,56 +19,61 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
def unassign_cost_item_quantity(
|
||||
file: ifcopenshell.file, cost_item: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
|
||||
) -> 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
|
||||
:param products: A list of IfcProducts that may have parametrically
|
||||
connected quantities to the cost item
|
||||
:type products: list[ifcopenshell.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])
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"cost_item": cost_item, "products": products or []}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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": cost_item, "products": products or []}
|
||||
|
||||
def execute(self):
|
||||
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
|
||||
for quantity in self.settings["cost_item"].CostQuantities or []:
|
||||
|
||||
@@ -15,3 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Reference external project documents and associate them to model elements
|
||||
|
||||
Some project information (drawings, specifications, certificates, reports, etc)
|
||||
may be stored in external documents (locally or in a CDE). IFC lets you store a
|
||||
register of documents with metadata and associate them with elements (both
|
||||
physical and non-physical).
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_information import add_information
|
||||
from .add_reference import add_reference
|
||||
from .assign_document import assign_document
|
||||
from .edit_information import edit_information
|
||||
from .edit_reference import edit_reference
|
||||
from .remove_information import remove_information
|
||||
from .remove_reference import remove_reference
|
||||
from .unassign_document import unassign_document
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_information",
|
||||
"add_reference",
|
||||
"assign_document",
|
||||
"edit_information",
|
||||
"edit_reference",
|
||||
"remove_information",
|
||||
"remove_reference",
|
||||
"unassign_document",
|
||||
]
|
||||
|
||||
@@ -17,71 +17,65 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, parent=None):
|
||||
"""Adds a new document information to the project
|
||||
def add_information(
|
||||
file: ifcopenshell.file, parent: Optional[ifcopenshell.entity_instance] = None
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
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.
|
||||
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
|
||||
:param parent: The parent document, if necessary.
|
||||
:type parent: ifcopenshell.entity_instance, optional
|
||||
:return: The newly created IfcDocumentInformation entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": parent}
|
||||
|
||||
def execute(self):
|
||||
id_attribute = "DocumentId" if self.file.schema == "IFC2X3" else "Identification"
|
||||
information = self.file.create_entity(
|
||||
"IfcDocumentInformation", **{id_attribute: "X", "Name": "Unnamed"}
|
||||
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"})
|
||||
"""
|
||||
id_attribute = "DocumentId" if file.schema == "IFC2X3" else "Identification"
|
||||
information = file.create_entity("IfcDocumentInformation", **{id_attribute: "X", "Name": "Unnamed"})
|
||||
if not parent and file.by_type("IfcProject"):
|
||||
parent = file.by_type("IfcProject")[0]
|
||||
if parent.is_a("IfcProject") or parent.is_a("IfcContext"):
|
||||
file.create_entity(
|
||||
"IfcRelAssociatesDocument",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
RelatingDocument=information,
|
||||
RelatedObjects=[parent],
|
||||
)
|
||||
parent = self.settings["parent"]
|
||||
if not parent and self.file.by_type("IfcProject"):
|
||||
parent = self.file.by_type("IfcProject")[0]
|
||||
if parent.is_a("IfcProject") or parent.is_a("IfcContext"):
|
||||
self.file.create_entity(
|
||||
"IfcRelAssociatesDocument",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
RelatingDocument=information,
|
||||
RelatedObjects=[parent],
|
||||
elif parent.is_a("IfcDocumentInformation"):
|
||||
if parent.IsPointer:
|
||||
rel = parent.IsPointer[0]
|
||||
documents = set(rel.RelatedDocuments)
|
||||
documents.add(information)
|
||||
rel.RelatedDocuments = list(documents)
|
||||
else:
|
||||
file.create_entity(
|
||||
"IfcDocumentInformationRelationship", RelatingDocument=parent, RelatedDocuments=[information]
|
||||
)
|
||||
elif parent.is_a("IfcDocumentInformation"):
|
||||
if parent.IsPointer:
|
||||
rel = parent.IsPointer[0]
|
||||
documents = set(rel.RelatedDocuments)
|
||||
documents.add(information)
|
||||
rel.RelatedDocuments = list(documents)
|
||||
else:
|
||||
self.file.create_entity(
|
||||
"IfcDocumentInformationRelationship",
|
||||
RelatingDocument=parent,
|
||||
RelatedDocuments=[information]
|
||||
)
|
||||
return information
|
||||
return information
|
||||
|
||||
@@ -19,62 +19,57 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, information: ifcopenshell.entity_instance):
|
||||
"""Creates a new reference to a document to assign to products
|
||||
def add_reference(file: ifcopenshell.file, information: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""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.
|
||||
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.
|
||||
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
|
||||
:param information: The IfcDocumentInformation that the reference will
|
||||
be created for
|
||||
:type information: ifcopenshell.entity_instance
|
||||
:return: The newly created IfcDocumentReference entity
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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"})
|
||||
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)
|
||||
# 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": information}
|
||||
# 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"})
|
||||
"""
|
||||
settings = {"information": information}
|
||||
|
||||
def execute(self) -> ifcopenshell.entity_instance:
|
||||
if self.file.schema == "IFC2X3":
|
||||
reference = self.file.create_entity("IfcDocumentReference", ItemReference="X")
|
||||
if self.settings["information"]:
|
||||
references = list(self.settings["information"].DocumentReferences or [])
|
||||
references.append(reference)
|
||||
self.settings["information"].DocumentReferences = references
|
||||
return reference
|
||||
return self.file.create_entity(
|
||||
"IfcDocumentReference", ReferencedDocument=self.settings["information"], Identification="X"
|
||||
)
|
||||
if file.schema == "IFC2X3":
|
||||
reference = file.create_entity("IfcDocumentReference", ItemReference="X")
|
||||
if settings["information"]:
|
||||
references = list(settings["information"].DocumentReferences or [])
|
||||
references.append(reference)
|
||||
settings["information"].DocumentReferences = references
|
||||
return reference
|
||||
return file.create_entity("IfcDocumentReference", ReferencedDocument=settings["information"], Identification="X")
|
||||
|
||||
@@ -18,97 +18,90 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from typing import Union
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
document: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Assigns a document to a list of products
|
||||
def assign_document(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
document: ifcopenshell.entity_instance,
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Assigns a document to a list of products
|
||||
|
||||
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).
|
||||
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 list of objects to associate the document to. This could be
|
||||
almost any sensible object in IFC.
|
||||
:type product: list[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
|
||||
or `None` if `products` was an empty list or all products were
|
||||
already assigned to the `document`.
|
||||
:rtype: ifcopenshell.entity_instance.entity_instance
|
||||
:param product: The list of objects to associate the document to. This could be
|
||||
almost any sensible object in IFC.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param document: The IfcDocumentReference to associate to, or
|
||||
alternatively an IfcDocumentInformation, though this is not
|
||||
recommended.
|
||||
:type document: ifcopenshell.entity_instance
|
||||
:return: The IfcRelAssociatesDocument relationship
|
||||
or `None` if `products` was an empty list or all products were
|
||||
already assigned to the `document`.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
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, products=[storey], document=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"document": document,
|
||||
}
|
||||
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
|
||||
ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
|
||||
"""
|
||||
settings = {
|
||||
"products": products,
|
||||
"document": document,
|
||||
}
|
||||
|
||||
def execute(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
|
||||
# NOTE: reuses code from `library.assign_reference`
|
||||
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
|
||||
# NOTE: reuses code from `library.assign_reference`
|
||||
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(self.settings["document"])
|
||||
products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
|
||||
products = products - referenced_elements
|
||||
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["document"])
|
||||
products: set[ifcopenshell.entity_instance] = set(settings["products"])
|
||||
products = products - referenced_elements
|
||||
|
||||
if not products:
|
||||
return
|
||||
if not products:
|
||||
return
|
||||
|
||||
if self.file.schema == "IFC2X3":
|
||||
rel = next(
|
||||
(
|
||||
r
|
||||
for r in self.file.by_type("IfcRelAssociatesDocument")
|
||||
if r.RelatingDocument == self.settings["document"]
|
||||
),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
ifc_class = self.settings["document"].is_a()
|
||||
if ifc_class == "IfcDocumentReference":
|
||||
rel = next(iter(self.settings["document"].DocumentRefForObjects), None)
|
||||
elif ifc_class == "IfcDocumentInformation":
|
||||
rel = next(iter(self.settings["document"].DocumentInfoForObjects), None)
|
||||
if file.schema == "IFC2X3":
|
||||
rel = next(
|
||||
(r for r in file.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == settings["document"]),
|
||||
None,
|
||||
)
|
||||
else:
|
||||
ifc_class = settings["document"].is_a()
|
||||
if ifc_class == "IfcDocumentReference":
|
||||
rel = next(iter(settings["document"].DocumentRefForObjects), None)
|
||||
elif ifc_class == "IfcDocumentInformation":
|
||||
rel = next(iter(settings["document"].DocumentInfoForObjects), None)
|
||||
|
||||
if not rel:
|
||||
return self.file.create_entity(
|
||||
"IfcRelAssociatesDocument",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
RelatedObjects=list(products),
|
||||
RelatingDocument=self.settings["document"],
|
||||
)
|
||||
if not rel:
|
||||
return file.create_entity(
|
||||
"IfcRelAssociatesDocument",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
RelatedObjects=list(products),
|
||||
RelatingDocument=settings["document"],
|
||||
)
|
||||
|
||||
related_objects = set(rel.RelatedObjects) | products
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, element=rel)
|
||||
return rel
|
||||
related_objects = set(rel.RelatedObjects) | products
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, element=rel)
|
||||
return rel
|
||||
|
||||
@@ -15,35 +15,38 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, information=None, attributes=None):
|
||||
"""Edits the attributes of an IfcDocumentInformation
|
||||
def edit_information(
|
||||
file: ifcopenshell.file,
|
||||
information: ifcopenshell.entity_instance,
|
||||
attributes: dict[str, Any],
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcDocumentInformation
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcDocumentInformation, consult the IFC documentation.
|
||||
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
|
||||
:param reference: The IfcDocumentInformation entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
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": information, "attributes": attributes or {}}
|
||||
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"})
|
||||
"""
|
||||
settings = {"information": information, "attributes": attributes}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["information"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["information"], name, value)
|
||||
|
||||
@@ -15,38 +15,41 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, reference=None, attributes=None):
|
||||
"""Edits the attributes of an IfcDocumentReference
|
||||
def edit_reference(
|
||||
file: ifcopenshell.file,
|
||||
reference: ifcopenshell.entity_instance,
|
||||
attributes: dict[str, Any],
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcDocumentReference
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcDocumentReference, consult the IFC documentation.
|
||||
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
|
||||
:param reference: The IfcDocumentReference entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
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": reference, "attributes": attributes or {}}
|
||||
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"})
|
||||
"""
|
||||
settings = {"reference": reference, "attributes": attributes}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["reference"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["reference"], name, value)
|
||||
|
||||
@@ -22,45 +22,51 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, information=None):
|
||||
"""Removes a document information
|
||||
def remove_information(file: ifcopenshell.file, information: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a document information
|
||||
|
||||
All references and associations are also removed.
|
||||
All references and associations are also removed.
|
||||
|
||||
:param information: The IfcDocumentInformation to remove
|
||||
:type information: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param information: The IfcDocumentInformation to remove
|
||||
:type information: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": information}
|
||||
# Add a document
|
||||
document = ifcopenshell.api.run("document.add_information", model)
|
||||
# ... and remove it!
|
||||
ifcopenshell.api.run("document.remove_information", model, information=document)
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
for reference in self.settings["information"].HasDocumentReferences or []:
|
||||
ifcopenshell.api.run("document.remove_reference", self.file, reference=reference)
|
||||
if file.schema == "IFC2X3":
|
||||
references = information.DocumentReferences or []
|
||||
else:
|
||||
references = information.HasDocumentReferences
|
||||
|
||||
for rel in self.settings["information"].IsPointer or []:
|
||||
for information in rel.RelatedDocuments:
|
||||
ifcopenshell.api.run("document.remove_information", self.file, information=information)
|
||||
for reference in references:
|
||||
ifcopenshell.api.run("document.remove_reference", file, reference=reference)
|
||||
|
||||
for rel in self.settings["information"].IsPointedTo or []:
|
||||
if rel.RelatedDocuments == (self.settings["information"],):
|
||||
# This relationship is non-rooted
|
||||
self.file.remove(rel)
|
||||
for rel in information.IsPointer or []:
|
||||
for info in rel.RelatedDocuments:
|
||||
ifcopenshell.api.run("document.remove_information", file, information=info)
|
||||
|
||||
for rel in self.settings["information"].DocumentInfoForObjects or []:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
self.file.remove(self.settings["information"])
|
||||
for rel in information.IsPointedTo or []:
|
||||
if rel.RelatedDocuments == (information,):
|
||||
# This relationship is non-rooted
|
||||
file.remove(rel)
|
||||
|
||||
if file.schema == "IFC2X3":
|
||||
rels = [r for r in file.by_type("IfcRelAssociatesDocument") if r.RelatingDocument == information]
|
||||
else:
|
||||
rels = information.DocumentInfoForObjects
|
||||
|
||||
for rel in rels:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
file.remove(information)
|
||||
|
||||
@@ -20,32 +20,33 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, reference: ifcopenshell.entity_instance):
|
||||
"""Remove a document reference
|
||||
def remove_reference(file: ifcopenshell.file, reference: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a document reference
|
||||
|
||||
All associations with objects are removed.
|
||||
All associations with objects are removed.
|
||||
|
||||
:param reference: The IfcDocumentReference to remove
|
||||
:type reference: ifcopenshell.entity_instance.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
:param reference: The IfcDocumentReference to remove
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": reference}
|
||||
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)
|
||||
"""
|
||||
|
||||
def execute(self) -> None:
|
||||
for rel in self.settings["reference"].DocumentRefForObjects or []:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
self.file.remove(self.settings["reference"])
|
||||
if file.schema == "IFC2X3":
|
||||
rels = [r for r in file.get_inverse(reference) if r.is_a("IfcRelAssociatesDocument")]
|
||||
else:
|
||||
rels = reference.DocumentRefForObjects
|
||||
|
||||
for rel in rels:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
file.remove(reference)
|
||||
|
||||
@@ -21,69 +21,65 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
document: ifcopenshell.entity_instance,
|
||||
):
|
||||
"""Unassigns a document and an association to the list of products
|
||||
def unassign_document(
|
||||
file: ifcopenshell.file,
|
||||
products: list[ifcopenshell.entity_instance],
|
||||
document: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
"""Unassigns a document and an association to the list of products
|
||||
|
||||
:param product: The list of objects that the document reference or information is
|
||||
related to.
|
||||
:type product: list[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
|
||||
:param product: The list of objects that the document reference or information is
|
||||
related to.
|
||||
:type product: list[ifcopenshell.entity_instance]
|
||||
:param document: The IfcDocumentReference (typically) or in rare cases
|
||||
the IfcDocumentInformation that is associated with the product
|
||||
:type document: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
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, products=[storey], document=reference)
|
||||
# Let's imagine storey represents an IfcBuildingStorey for the ground floor
|
||||
ifcopenshell.api.run("document.assign_document", model, products=[storey], document=reference)
|
||||
|
||||
# Now let's change our mind and remove the association
|
||||
ifcopenshell.api.run("document.unassign_document", model, products=[storey], document=reference)
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"products": products,
|
||||
"document": document,
|
||||
}
|
||||
# Now let's change our mind and remove the association
|
||||
ifcopenshell.api.run("document.unassign_document", model, products=[storey], document=reference)
|
||||
"""
|
||||
settings = {
|
||||
"products": products,
|
||||
"document": document,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
|
||||
# NOTE: reuses code from `library.un assign_reference`
|
||||
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
|
||||
# NOTE: reuses code from `library.un assign_reference`
|
||||
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products = set(self.settings["products"])
|
||||
for product in products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
reference_rels: set[ifcopenshell.entity_instance] = set()
|
||||
products = set(settings["products"])
|
||||
for product in products:
|
||||
reference_rels.update(product.HasAssociations)
|
||||
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == self.settings["document"]
|
||||
}
|
||||
reference_rels = {
|
||||
rel
|
||||
for rel in reference_rels
|
||||
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == settings["document"]
|
||||
}
|
||||
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for rel in reference_rels:
|
||||
related_objects = set(rel.RelatedObjects) - products
|
||||
if related_objects:
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
else:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -15,3 +15,22 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Create relationships necessary for smart annotations for drawings
|
||||
|
||||
Drawings may be generated from modeled elements and annotations. These
|
||||
annotations may have relationships which indicate smart data being populated.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .assign_product import assign_product
|
||||
from .edit_text_literal import edit_text_literal
|
||||
from .unassign_product import unassign_product
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"assign_product",
|
||||
"edit_text_literal",
|
||||
"unassign_product",
|
||||
]
|
||||
|
||||
@@ -18,97 +18,99 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_product=None, related_object=None):
|
||||
"""Associates a product and an object, typically for annotation
|
||||
def assign_product(
|
||||
file: ifcopenshell.file,
|
||||
relating_product: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Associates a product and an object, typically for annotation
|
||||
|
||||
Warning: this is an experimental API.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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
|
||||
:param relating_product: The IfcProduct the object is related to
|
||||
:type relating_product: ifcopenshell.entity_instance
|
||||
:param related_object: The object (typically IfcAnnotation) that the
|
||||
product is related to
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: The created IfcRelAssignsToProduct relationship
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
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)
|
||||
"""
|
||||
settings = {
|
||||
"relating_product": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
is_grid_axis = self.settings["relating_product"].is_a("IfcGridAxis")
|
||||
is_grid_axis = settings["relating_product"].is_a("IfcGridAxis")
|
||||
|
||||
if is_grid_axis:
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for rel in self.settings["related_object"].HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct") and rel.Name == self.settings["relating_product"].AxisTag:
|
||||
return
|
||||
elif self.settings["related_object"].HasAssignments:
|
||||
for rel in self.settings["related_object"].HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct == self.settings["relating_product"]:
|
||||
if is_grid_axis:
|
||||
if settings["related_object"].HasAssignments:
|
||||
for rel in settings["related_object"].HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct") and rel.Name == settings["relating_product"].AxisTag:
|
||||
return
|
||||
elif settings["related_object"].HasAssignments:
|
||||
for rel in settings["related_object"].HasAssignments:
|
||||
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct == settings["relating_product"]:
|
||||
return
|
||||
|
||||
referenced_by = None
|
||||
referenced_by = None
|
||||
|
||||
if is_grid_axis:
|
||||
axis = self.settings["relating_product"]
|
||||
grid = None
|
||||
for attribute in ("PartOfW", "PartOfV", "PartOfU"):
|
||||
if getattr(axis, attribute, None):
|
||||
grid = getattr(axis, attribute)[0]
|
||||
self.settings["relating_product"] = grid
|
||||
for rel in grid.ReferencedBy:
|
||||
if rel.Name == axis.AxisTag:
|
||||
referenced_by = rel
|
||||
break
|
||||
elif self.settings["relating_product"].ReferencedBy:
|
||||
referenced_by = self.settings["relating_product"].ReferencedBy[0]
|
||||
if is_grid_axis:
|
||||
axis = settings["relating_product"]
|
||||
grid = None
|
||||
for attribute in ("PartOfW", "PartOfV", "PartOfU"):
|
||||
if getattr(axis, attribute, None):
|
||||
grid = getattr(axis, attribute)[0]
|
||||
settings["relating_product"] = grid
|
||||
for rel in grid.ReferencedBy:
|
||||
if rel.Name == axis.AxisTag:
|
||||
referenced_by = rel
|
||||
break
|
||||
elif settings["relating_product"].ReferencedBy:
|
||||
referenced_by = settings["relating_product"].ReferencedBy[0]
|
||||
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": referenced_by})
|
||||
else:
|
||||
referenced_by = self.file.create_entity(
|
||||
"IfcRelAssignsToProduct",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingProduct": self.settings["relating_product"],
|
||||
}
|
||||
)
|
||||
if referenced_by:
|
||||
related_objects = list(referenced_by.RelatedObjects)
|
||||
related_objects.append(settings["related_object"])
|
||||
referenced_by.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": referenced_by})
|
||||
else:
|
||||
referenced_by = file.create_entity(
|
||||
"IfcRelAssignsToProduct",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
"RelatedObjects": [settings["related_object"]],
|
||||
"RelatingProduct": settings["relating_product"],
|
||||
},
|
||||
)
|
||||
|
||||
if is_grid_axis:
|
||||
referenced_by.Name = axis.AxisTag
|
||||
return referenced_by
|
||||
if is_grid_axis:
|
||||
referenced_by.Name = axis.AxisTag
|
||||
return referenced_by
|
||||
|
||||
@@ -15,33 +15,34 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, text_literal=None, attributes=None):
|
||||
"""Edits the attributes of an IfcTextLiteral
|
||||
def edit_text_literal(
|
||||
file: ifcopenshell.file, text_literal: ifcopenshell.entity_instance, attributes: dict[str, Any]
|
||||
) -> None:
|
||||
"""Edits the attributes of an IfcTextLiteral
|
||||
|
||||
For more information about the attributes and data types of an
|
||||
IfcTextLiteral, consult the IFC documentation.
|
||||
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
|
||||
:param reference: The IfcTextLiteral entity you want to edit
|
||||
:type reference: ifcopenshell.entity_instance
|
||||
:param attributes: a dictionary of attribute names and values.
|
||||
:type attributes: dict
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. code:: python
|
||||
|
||||
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": text_literal, "attributes": attributes or {}}
|
||||
text = model.createIfcTextLiteral()
|
||||
ifcopenshell.api.run("drawing.edit_text_literal", model,
|
||||
text_literal=text, attributes={"Literal": "MY ANNOTATION"})
|
||||
"""
|
||||
settings = {"text_literal": text_literal, "attributes": attributes or {}}
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["text_literal"], name, value)
|
||||
for name, value in settings["attributes"].items():
|
||||
setattr(settings["text_literal"], name, value)
|
||||
|
||||
@@ -21,54 +21,54 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, relating_product=None, related_object=None):
|
||||
"""Unassigns a product and an object (typically an annotation)
|
||||
def unassign_product(
|
||||
file: ifcopenshell.file,
|
||||
relating_product: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> 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.
|
||||
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
|
||||
:param relating_product: The IfcProduct the object is related to
|
||||
:type relating_product: ifcopenshell.entity_instance
|
||||
:param related_object: The object (typically IfcAnnotation) that the
|
||||
product is related to
|
||||
:type related_object: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
.. 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)
|
||||
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": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
# Let's change our mind and remove the relationship
|
||||
ifcopenshell.api.run("drawing.unassign_product", model,
|
||||
relating_product=furniture, related_object=annotation)
|
||||
"""
|
||||
settings = {
|
||||
"relating_product": relating_product,
|
||||
"related_object": related_object,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != self.settings["relating_product"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
for rel in settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != settings["relating_product"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
history = rel.OwnerHistory
|
||||
file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
return
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", file, **{"element": rel})
|
||||
|
||||
@@ -15,3 +15,74 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Create geometric representations and assign them to elements
|
||||
|
||||
These functions support both the creation of arbitrary geometry as well as
|
||||
geometry that follows parametric rules (e.g. layered geometry or profiled
|
||||
geometry extrusions).
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_axis_representation import add_axis_representation
|
||||
from .add_boolean import add_boolean
|
||||
try:
|
||||
from .add_door_representation import add_door_representation
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: geometry.add_door_representation - {e}")
|
||||
from .add_footprint_representation import add_footprint_representation
|
||||
from .add_mesh_representation import add_mesh_representation
|
||||
from .add_profile_representation import add_profile_representation
|
||||
try:
|
||||
from .add_railing_representation import add_railing_representation
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: geometry.add_railing_representation - {e}")
|
||||
|
||||
try:
|
||||
from .add_representation import add_representation
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: geometry.add_representation - {e}")
|
||||
from .add_slab_representation import add_slab_representation
|
||||
from .add_wall_representation import add_wall_representation
|
||||
try:
|
||||
from .add_window_representation import add_window_representation
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: geometry.add_window_representation - {e}")
|
||||
from .assign_representation import assign_representation
|
||||
from .connect_element import connect_element
|
||||
from .connect_path import connect_path
|
||||
from .create_2pt_wall import create_2pt_wall
|
||||
from .disconnect_element import disconnect_element
|
||||
from .disconnect_path import disconnect_path
|
||||
from .edit_object_placement import edit_object_placement
|
||||
from .map_representation import map_representation
|
||||
from .remove_boolean import remove_boolean
|
||||
from .remove_representation import remove_representation
|
||||
from .unassign_representation import unassign_representation
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_axis_representation",
|
||||
"add_boolean",
|
||||
"add_door_representation",
|
||||
"add_footprint_representation",
|
||||
"add_mesh_representation",
|
||||
"add_profile_representation",
|
||||
"add_railing_representation",
|
||||
"add_representation",
|
||||
"add_slab_representation",
|
||||
"add_wall_representation",
|
||||
"add_window_representation",
|
||||
"assign_representation",
|
||||
"connect_element",
|
||||
"connect_path",
|
||||
"create_2pt_wall",
|
||||
"disconnect_element",
|
||||
"disconnect_path",
|
||||
"edit_object_placement",
|
||||
"map_representation",
|
||||
"remove_boolean",
|
||||
"remove_representation",
|
||||
"unassign_representation",
|
||||
]
|
||||
|
||||
@@ -17,63 +17,71 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Union
|
||||
|
||||
COORD = Union[tuple[float, float], tuple[float, float, float]]
|
||||
|
||||
|
||||
def add_axis_representation(
|
||||
file: ifcopenshell.file, context: ifcopenshell.entity_instance, axis: tuple[COORD, COORD]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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
|
||||
: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
|
||||
|
||||
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)])
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"axis": axis or [],
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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": context,
|
||||
"axis": axis or [],
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
is_2d = len(self.settings["axis"][0]) == 2
|
||||
@@ -82,9 +90,13 @@ class Usecase:
|
||||
curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points])
|
||||
else:
|
||||
if is_2d:
|
||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList2D(points), None, False)
|
||||
curve = self.file.createIfcIndexedPolyCurve(
|
||||
self.file.createIfcCartesianPointList2D(points), None, False
|
||||
)
|
||||
else:
|
||||
curve = self.file.createIfcIndexedPolyCurve(self.file.createIfcCartesianPointList3D(points), None, False)
|
||||
curve = self.file.createIfcIndexedPolyCurve(
|
||||
self.file.createIfcCartesianPointList3D(points), None, False
|
||||
)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
|
||||
@@ -16,28 +16,55 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import ifcopenshell.util.unit
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from typing import Optional, TYPE_CHECKING, Literal
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bpy.types
|
||||
|
||||
|
||||
NPArrayOfFloats = npt.NDArray[np.float64]
|
||||
|
||||
|
||||
def add_boolean(
|
||||
file: ifcopenshell.file,
|
||||
representation: ifcopenshell.entity_instance,
|
||||
# A matrix to define a clipping Ifchalfspacesolid.
|
||||
# The XY plane is the clipping boundary and +Z is removed.
|
||||
operator: str = "DIFFERENCE",
|
||||
# IfcHalfSpaceSolid, Mesh
|
||||
type: Literal["IfcHalfSpaceSolid", "Mesh"] = "IfcHalfSpaceSolid",
|
||||
matrix: Optional[NPArrayOfFloats] = None,
|
||||
# A Blender OBJ to define the voided OBJ for a "Mesh" type
|
||||
blender_obj: Optional[bpy.types.Object] = None,
|
||||
# A Blender OBJ to define the void OBJ for a "Mesh" type
|
||||
blender_void: Optional[bpy.types.Object] = None,
|
||||
should_force_faceted_brep: bool = False,
|
||||
should_force_triangulation: bool = False,
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""For `type` values:
|
||||
- "IfcHalfSpaceSolid" - `matrix` is not optional.
|
||||
- "Mesh" - `blender_obj` and `blender_void` are not optional
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"representation": representation,
|
||||
"operator": operator,
|
||||
"type": type,
|
||||
"matrix": matrix,
|
||||
"blender_obj": blender_obj,
|
||||
"blender_void": blender_void,
|
||||
"should_force_faceted_brep": should_force_faceted_brep,
|
||||
"should_force_triangulation": should_force_triangulation,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"representation": None,
|
||||
"operator": "DIFFERENCE",
|
||||
# IfcHalfSpaceSolid, Mesh
|
||||
"type": "IfcHalfSpaceSolid",
|
||||
# The XY plane is the clipping boundary and +Z is removed.
|
||||
"matrix": None, # A matrix to define a clipping Ifchalfspacesolid.
|
||||
"blender_obj": None, # A Blender OBJ to define the voided OBJ for a "Mesh" type
|
||||
"blender_void": None, # A Blender OBJ to define the void OBJ for a "Mesh" type
|
||||
"should_force_faceted_brep": False,
|
||||
"should_force_triangulation": False,
|
||||
}
|
||||
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)
|
||||
if self.settings["type"] == "IfcHalfSpaceSolid":
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import collections.abc
|
||||
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
|
||||
from math import cos, radians
|
||||
|
||||
import collections
|
||||
from typing import Any, Optional, Literal, Union
|
||||
import dataclasses
|
||||
|
||||
|
||||
SUPPORTED_DOOR_TYPES = (
|
||||
@@ -38,9 +40,14 @@ SUPPORTED_DOOR_TYPES = (
|
||||
)
|
||||
|
||||
|
||||
def mm(x: float) -> float:
|
||||
"""mm to meters shortcut for readability"""
|
||||
return x / 1000
|
||||
|
||||
|
||||
def create_ifc_door_lining(
|
||||
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
|
||||
):
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)`
|
||||
|
||||
`thickness` can be also defined just as 1 float value.
|
||||
@@ -63,91 +70,222 @@ def create_ifc_door_lining(
|
||||
|
||||
points = [p.xz for p in points]
|
||||
door_lining = builder.polyline(points, closed=True)
|
||||
door_lining = builder.extrude(
|
||||
door_lining,
|
||||
size.y,
|
||||
**builder.extrude_kwargs("Y")
|
||||
)
|
||||
door_lining = builder.extrude(door_lining, size.y, **builder.extrude_kwargs("Y"))
|
||||
builder.translate(door_lining, position)
|
||||
|
||||
return door_lining
|
||||
|
||||
|
||||
def create_ifc_box(builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()):
|
||||
def create_ifc_box(
|
||||
builder: ShapeBuilder, size: Vector, position: Vector = V(0, 0, 0).freeze()
|
||||
) -> ifcopenshell.entity_instance:
|
||||
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
|
||||
# we use dataclass as we need default values for arguments
|
||||
# it's okay to use slots since we don't need dynamic attributes
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class DoorLiningProperties:
|
||||
LiningDepth: Optional[float] = None
|
||||
"""Optional, defaults to 50mm."""
|
||||
|
||||
LiningThickness: Optional[float] = None
|
||||
"""Optional, defaults to 50mm."""
|
||||
|
||||
LiningOffset: Optional[float] = None
|
||||
"""Offset from the outer side of the wall (by Y-axis). Optional, defaults to 0.0."""
|
||||
|
||||
LiningToPanelOffsetX: Optional[float] = None
|
||||
"""Offset from the wall. Optional, defaults to 25mm."""
|
||||
|
||||
LiningToPanelOffsetY: Optional[float] = None
|
||||
"""Offset from the X-axis (unlike windows). Optional, defaults to 25mm."""
|
||||
|
||||
TransomThickness: Optional[float] = None
|
||||
"""Vertical distance between door and window panels. Optional, defaults to 0.0."""
|
||||
|
||||
TransomOffset: Optional[float] = None
|
||||
"""Distance from the bottom door opening
|
||||
to the beginning of the transom
|
||||
unlike windows TransomOffset which goes to the center of the transom.
|
||||
Optional, defaults 1.525m."""
|
||||
|
||||
ShapeAspectStyle: None = None
|
||||
"""Optional. Deprecated argument."""
|
||||
|
||||
CasingDepth: Optional[float] = None
|
||||
"""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. Optional, defaults to 5mm."""
|
||||
|
||||
CasingThickness: Optional[float] = None
|
||||
"""Casing thickness by Z-axis. Optional, defaults to 75mm."""
|
||||
|
||||
ThresholdDepth: Optional[float] = None
|
||||
"""Threshold covers the bottom side of the opening. Optional, defaults to 100mm."""
|
||||
|
||||
ThresholdThickness: Optional[float] = None
|
||||
"""Theshold thickness by Z-axis. Optional, defaults to 25mm."""
|
||||
|
||||
ThresholdOffset: Optional[float] = None
|
||||
"""Threshold offset by Y-axis. Optional, defaults to 0.0."""
|
||||
|
||||
def initialize_properties(self, unit_scale: float) -> None:
|
||||
# in meters
|
||||
# fmt: off
|
||||
default_values: dict[str, float] = dict(
|
||||
LiningDepth = mm(50),
|
||||
LiningThickness = mm(50),
|
||||
LiningOffset = 0.0,
|
||||
LiningToPanelOffsetX = mm(25),
|
||||
LiningToPanelOffsetY = mm(25),
|
||||
TransomThickness = 0.0,
|
||||
TransomOffset = mm(1525),
|
||||
CasingDepth = mm(5),
|
||||
CasingThickness = mm(75),
|
||||
ThresholdDepth = mm(100),
|
||||
ThresholdThickness = mm(25),
|
||||
ThresholdOffset = 0.0,
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
si_conversion = 1 / unit_scale
|
||||
for attr, default_value in default_values.items():
|
||||
if getattr(self, attr) is not None:
|
||||
continue
|
||||
setattr(self, attr, default_value * si_conversion)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class DoorPanelProperties:
|
||||
PanelDepth: Optional[float] = None
|
||||
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
|
||||
|
||||
PanelWidth: float = 1.0
|
||||
"""Ratio to the clear door opening. Optional, defaults to 1.0."""
|
||||
|
||||
FrameDepth: Optional[float] = None
|
||||
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
|
||||
|
||||
FrameThickness: Optional[float] = None
|
||||
"""Frame thickness by X axis. Optional, defaults to 35 mm."""
|
||||
|
||||
PanelPosition: None = None
|
||||
"""Optional, value is never used"""
|
||||
|
||||
PanelOperation: None = None
|
||||
"""Optional, value is never used.
|
||||
Defines the basic ways to describe how door panels operate."""
|
||||
|
||||
ShapeAspectStyle: None = None
|
||||
"""Optional. Deprecated argument."""
|
||||
|
||||
def initialize_properties(self, unit_scale: float) -> None:
|
||||
# in meters
|
||||
# fmt: off
|
||||
default_values: dict[str, float] = dict(
|
||||
PanelDepth = mm(35),
|
||||
FrameDepth = mm(35),
|
||||
FrameThickness = mm(35),
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
si_conversion = 1 / unit_scale
|
||||
for attr, default_value in default_values.items():
|
||||
if getattr(self, attr) is not None:
|
||||
continue
|
||||
setattr(self, attr, default_value * si_conversion)
|
||||
|
||||
|
||||
def add_door_representation(
|
||||
file: ifcopenshell.file,
|
||||
*, # keywords only as this API implementation is probably not final
|
||||
context: ifcopenshell.entity_instance,
|
||||
overall_height: Optional[float] = None,
|
||||
overall_width: Optional[float] = None,
|
||||
# door type
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorTypeOperationEnum.htm
|
||||
operation_type: Literal[
|
||||
"SINGLE_SWING_LEFT",
|
||||
"SINGLE_SWING_RIGHT",
|
||||
"DOUBLE_SWING_RIGHT",
|
||||
"DOUBLE_SWING_LEFT",
|
||||
"DOUBLE_DOOR_SINGLE_SWING",
|
||||
"DOUBLE_DOOR_DOUBLE_SWING",
|
||||
"SLIDING_TO_LEFT",
|
||||
"SLIDING_TO_RIGHT",
|
||||
"DOUBLE_DOOR_SLIDING",
|
||||
] = "SINGLE_SWING_LEFT",
|
||||
lining_properties: Optional[Union[DoorLiningProperties, dict[str, Any]]] = None,
|
||||
panel_properties: Optional[Union[DoorPanelProperties, dict[str, Any]]] = None,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""units in usecase_settings expected to be in ifc project units
|
||||
|
||||
:param context: IfcGeometricRepresentationContext for the representation.
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:param overall_height: Overall door height. Defaults to 2m.
|
||||
:type overall_height: float, optional
|
||||
:param overall_width: Overall door width. Defaults to 0.9m.
|
||||
:type overall_width: float, optional
|
||||
:param operation_type: Type of the door. Defaults to SINGLE_SWING_LEFT.
|
||||
:type operation_type: str, optional
|
||||
:param lining_properties: DoorLiningProperties or a dictionary to create one.
|
||||
See DoorLiningProperties description for details.
|
||||
:type lining_properties: Union[DoorLiningProperties, dict[str, Any]]]
|
||||
:param panel_properties: DoorPanelProperties or a dictionary to create one.
|
||||
See DoorPanelProperties description for details.
|
||||
:type panel_properties: Union[DoorPanelProperties, dict[str, Any]]]
|
||||
: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: IfcShapeRepresentation for a door.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.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/IfcDoorLiningProperties.htm
|
||||
# http://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcDoorPanelProperties.htm
|
||||
# define unit_scale first as it's going to be used setting default arguments
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
|
||||
settings: dict[str, Any] = {"unit_scale": unit_scale}
|
||||
|
||||
if lining_properties is None:
|
||||
lining_properties = DoorLiningProperties()
|
||||
elif not isinstance(lining_properties, DoorLiningProperties):
|
||||
lining_properties = DoorLiningProperties(**lining_properties)
|
||||
lining_properties.initialize_properties(unit_scale)
|
||||
lining_properties = dataclasses.asdict(lining_properties)
|
||||
|
||||
if panel_properties is None:
|
||||
panel_properties = DoorPanelProperties()
|
||||
elif not isinstance(panel_properties, DoorPanelProperties):
|
||||
panel_properties = DoorPanelProperties(**panel_properties)
|
||||
panel_properties.initialize_properties(unit_scale)
|
||||
panel_properties = dataclasses.asdict(panel_properties)
|
||||
|
||||
settings.update(
|
||||
{
|
||||
"context": context,
|
||||
"overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(2.0),
|
||||
"overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.9),
|
||||
"operation_type": operation_type,
|
||||
"lining_properties": lining_properties,
|
||||
"panel_properties": panel_properties,
|
||||
}
|
||||
)
|
||||
usecase.settings = settings
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
builder = ShapeBuilder(self.file)
|
||||
overall_height = self.settings["overall_height"]
|
||||
|
||||
@@ -19,20 +19,21 @@
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"curves": [], # A list of IFC curves to include in the curve set
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def add_footprint_representation(
|
||||
file,
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
# A list of IFC curves to include in the curve set
|
||||
curves: list[ifcopenshell.entity_instance],
|
||||
) -> ifcopenshell.entity_instance:
|
||||
settings = {
|
||||
"context": context,
|
||||
"curves": curves,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"GeometricCurveSet",
|
||||
[self.file.createIfcGeometricCurveSet(self.settings["curves"])],
|
||||
)
|
||||
return file.createIfcShapeRepresentation(
|
||||
settings["context"],
|
||||
settings["context"].ContextIdentifier,
|
||||
"GeometricCurveSet",
|
||||
[file.createIfcGeometricCurveSet(settings["curves"])],
|
||||
)
|
||||
|
||||
@@ -17,27 +17,47 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from typing import Optional
|
||||
|
||||
COORD_3D = tuple[float, float, float]
|
||||
|
||||
|
||||
def add_mesh_representation(
|
||||
file: ifcopenshell.file,
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
# Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
|
||||
# A list of coordinates
|
||||
# ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
|
||||
vertices: list[COORD_3D],
|
||||
# A list of edges, represented by vertex index pairs
|
||||
# ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
|
||||
edges: list[tuple[int, int]],
|
||||
# A list of polygons, represented by vertex indices
|
||||
# ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
|
||||
faces: list[list[int]],
|
||||
# Optionally apply a vector offset to all coordinates
|
||||
cooridnate_offset: Optional[COORD_3D] = None,
|
||||
# A scale factor to apply for all vectors in case the unit is different
|
||||
unit_scale: Optional[float] = None,
|
||||
# Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
|
||||
force_faceted_brep: bool = False,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"vertices": vertices,
|
||||
"edges": edges,
|
||||
"faces": faces,
|
||||
"coordinate_offset": cooridnate_offset,
|
||||
"unit_scale": unit_scale,
|
||||
"force_faceted_brep": force_faceted_brep,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
# Vertices, edges, and faces are given in the form of: [item1, item2, item3, ...]
|
||||
# ... where itemN = [(0., 0., 0.), (1., 1., 1.), (x, y, z), ...]
|
||||
"vertices": None, # A list of coordinates
|
||||
# ... where itemN = [(0, 1), (1, 2), (v1, v2), ...]
|
||||
"edges": None, # A list of edges, represented by vertex index pairs
|
||||
# ... where itemN = [(0, 1, 2), (5, 4, 2, 3), (v1, v2, v3, ... vN), ...]
|
||||
"faces": None, # A list of polygons, represented by vertex indices
|
||||
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
|
||||
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
|
||||
"force_faceted_brep": False, # Force using IfcFacetedBreps instead of IfcPolygonalFaceSets
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["unit_scale"] is None:
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -19,24 +19,39 @@
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.data import Clipping
|
||||
from typing import Any, Union, Optional, Literal
|
||||
|
||||
VECTOR_3D = tuple[float, float, float]
|
||||
|
||||
|
||||
def add_profile_representation(
|
||||
file: ifcopenshell.file,
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
profile: ifcopenshell.entity_instance,
|
||||
# in meters
|
||||
depth: float = 1.0,
|
||||
cardinal_point: Literal[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] = 5,
|
||||
# A list of planes that define clipping half space solids
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
|
||||
placement_zx_axes: tuple[Union[VECTOR_3D, None], Union[VECTOR_3D, None]] = (None, None),
|
||||
) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"profile": profile,
|
||||
"depth": depth,
|
||||
"cardinal_point": cardinal_point,
|
||||
"clippings": clippings if clippings is not None else [],
|
||||
"placement_zx_axes": placement_zx_axes,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"profile": None,
|
||||
"depth": 1.0,
|
||||
"cardinal_point": 5,
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
"placement_zx_axes": (None, None),
|
||||
}
|
||||
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)
|
||||
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
|
||||
|
||||
@@ -22,48 +22,105 @@ from itertools import chain
|
||||
from mathutils import Vector, Matrix
|
||||
import collections
|
||||
import mathutils
|
||||
from pprint import pprint
|
||||
from math import pi, cos, sin, tan, radians
|
||||
from typing import Literal, Optional, Any
|
||||
|
||||
|
||||
def mm(x):
|
||||
def mm(x: float) -> float:
|
||||
"""mm to meters shortcut for readability"""
|
||||
return x / 1000
|
||||
|
||||
|
||||
def add_railing_representation(
|
||||
file: ifcopenshell.file,
|
||||
*, # keywords only as this API implementation is probably not final
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
railing_type: Literal["WALL_MOUNTED_HANDRAIL"] = "WALL_MOUNTED_HANDRAIL",
|
||||
railing_path: list[Vector],
|
||||
use_manual_supports: bool = False,
|
||||
support_spacing: Optional[float] = None,
|
||||
railing_diameter: Optional[float] = None,
|
||||
clear_width: Optional[float] = None,
|
||||
terminal_type: Literal[
|
||||
"180",
|
||||
"TO_END_POST",
|
||||
"TO_WALL",
|
||||
"TO_FLOOR",
|
||||
"TO_END_POST_AND_FLOOR",
|
||||
] = "180",
|
||||
height: Optional[float] = None,
|
||||
looped_path: bool = False,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
Units are expected to be in IFC project units.
|
||||
|
||||
:param context: IfcGeometricRepresentationContext for the representation.
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:param railing_type: Type of the railing. Defaults to "WALL_MOUNTED_HANDRAIL".
|
||||
:type railing_type: Literal["WALL_MOUNTED_HANDRAIL"], optional
|
||||
:param railing_path: A list of points coordinates for the railing path,
|
||||
coordinates are expected to be at the top of the railing, not at the center.
|
||||
If not provided, default path [(0, 0, 1), (1, 0, 1), (2, 0, 1)] (in meters) will be used
|
||||
:type railing_path: list[Vector], optional.
|
||||
:param use_manual_supports: If enabled, supports are added on every vertex on the edges of the railing path.
|
||||
If disabled, supports are added automatically based on the support spacing. Default to False.
|
||||
:type use_manual_supports: bool, optional
|
||||
:param support_spacing: Distance between supports if automatic supports are used. Defaults to 1m.
|
||||
:type support_spacing: float, optional
|
||||
:param railing_diameter: Railing diameter. Defaults to 50mm.
|
||||
:type railing_diameter: float, optional
|
||||
:param clear_width: Clear width between the railing and the wall. Defaults to 40mm.
|
||||
:type clear_width: float, optional
|
||||
:param terminal_type: type of the cap. Defaults to "180".
|
||||
:type terminal_type: Literal["180","TO_END_POST","TO_WALL","TO_FLOOR","TO_END_POST_AND_FLOOR"], optional
|
||||
:param height: defaults to 1m
|
||||
:type height: float, optional
|
||||
:param looped_path: Whether to end the railing on the first point of `railing_path`. Defaults to False.
|
||||
:type looped_path: bool, optional
|
||||
: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: IfcShapeRepresentation for a railing.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
# define unit_scale first as it's going to be used setting default arguments
|
||||
settings: dict[str, Any] = {
|
||||
"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale,
|
||||
}
|
||||
settings.update(
|
||||
{
|
||||
"context": context,
|
||||
"railing_type": railing_path,
|
||||
"railing_path": (
|
||||
railing_path
|
||||
if railing_path is not None
|
||||
else usecase.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)])
|
||||
),
|
||||
"use_manual_supports": use_manual_supports,
|
||||
"support_spacing": support_spacing if support_spacing is not None else usecase.convert_si_to_unit(mm(1000)),
|
||||
"railing_diameter": (
|
||||
railing_diameter if railing_diameter is not None else usecase.convert_si_to_unit(mm(50))
|
||||
),
|
||||
"clear_width": clear_width if clear_width is not None else usecase.convert_si_to_unit(mm(40)),
|
||||
"terminal_type": terminal_type,
|
||||
"height": height if height is not None else usecase.convert_si_to_unit(mm(1000)),
|
||||
"looped_path": looped_path,
|
||||
}
|
||||
)
|
||||
usecase.settings = settings
|
||||
|
||||
if railing_type != "WALL_MOUNTED_HANDRAIL":
|
||||
raise Exception('Only "WALL_MOUNTED_HANDRAIL" railing_type is supported at the moment.')
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
"""
|
||||
units in settings expected to be in ifc project units
|
||||
|
||||
`railing_path` is a list of point coordinates for the railing path,
|
||||
coordinates are expected to be at the top of the railing, not at the center
|
||||
|
||||
`railing_path` is expected to be a list of Vector objects
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"unit_scale": ifcopenshell.util.unit.calculate_unit_scale(self.file)}
|
||||
self.settings.update(
|
||||
{
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"railing_type": "WALL_MOUNTED_HANDRAIL",
|
||||
"railing_path": self.path_si_to_units([V(0, 0, 1), V(1, 0, 1), V(2, 0, 1)]),
|
||||
"use_manual_supports": False,
|
||||
"support_spacing": self.convert_si_to_unit(mm(1000)),
|
||||
"railing_diameter": self.convert_si_to_unit(mm(50)),
|
||||
"clear_width": self.convert_si_to_unit(mm(40)),
|
||||
"terminal_type": "180",
|
||||
"height": self.convert_si_to_unit(mm(1000)),
|
||||
"looped_path": False,
|
||||
}
|
||||
)
|
||||
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
if self.settings["railing_type"] != "WALL_MOUNTED_HANDRAIL":
|
||||
raise Exception('Only "WALL_MOUNTED_HANDRAIL" railing_type is supported at the moment.')
|
||||
|
||||
def execute(self):
|
||||
arc_points = []
|
||||
items_3d = []
|
||||
|
||||
@@ -15,48 +15,91 @@
|
||||
#
|
||||
# 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 bpy
|
||||
from __future__ import annotations
|
||||
import bpy.types
|
||||
import math
|
||||
import bmesh
|
||||
import ifcopenshell.util.unit
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
from typing import Union, Optional, Literal, Any, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
|
||||
Z_AXIS = Vector((0, 0, 1))
|
||||
X_AXIS = Vector((1, 0, 0))
|
||||
EPSILON = 1e-6
|
||||
|
||||
|
||||
def add_representation(
|
||||
file: ifcopenshell.file,
|
||||
*, # keywords only as this API implementation is probably not final
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
# This is (currently) a Blender object, hence this depends on Blender now
|
||||
blender_object: bpy.types.Object,
|
||||
# This is (currently) a Blender data object, hence this depends on Blender now
|
||||
geometry: Union[bpy.types.Mesh, bpy.types.Curve],
|
||||
# Optionally apply a vector offset to all coordinates
|
||||
coordinate_offset: Optional[Vector] = None,
|
||||
# How many representation items to create
|
||||
total_items: int = 1,
|
||||
# A scale factor to apply for all vectors in case the unit is different
|
||||
unit_scale: Optional[float] = None,
|
||||
# If we should force faceted breps for meshes
|
||||
should_force_faceted_brep: bool = False,
|
||||
# If we should force triangulation for meshes
|
||||
should_force_triangulation: bool = False,
|
||||
# If UV coordinates should also be generated
|
||||
should_generate_uvs: bool = False,
|
||||
# Whether to cast a mesh into a particular class
|
||||
ifc_representation_class: Optional[
|
||||
Literal[
|
||||
"IfcExtrudedAreaSolid/IfcRectangleProfileDef",
|
||||
"IfcExtrudedAreaSolid/IfcCircleProfileDef",
|
||||
"IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef",
|
||||
"IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids",
|
||||
"IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage",
|
||||
"IfcGeometricCurveSet/IfcTextLiteral",
|
||||
"IfcTextLiteral",
|
||||
]
|
||||
] = None,
|
||||
# The material profile set if the extrusion requires it
|
||||
profile_set_usage: Optional[ifcopenshell.entity_instance] = None,
|
||||
# The text literal if the representation requires it
|
||||
text_literal: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
# lazy import Helper to avoid circular import
|
||||
if "Helper" not in globals():
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
globals()["Helper"] = Helper
|
||||
|
||||
usecase = Usecase()
|
||||
# TODO: This usecase currently depends on Blender's data model
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"blender_object": blender_object,
|
||||
"geometry": geometry,
|
||||
"coordinate_offset": coordinate_offset,
|
||||
"total_items": total_items,
|
||||
"unit_scale": unit_scale,
|
||||
"should_force_faceted_brep": should_force_faceted_brep,
|
||||
"should_force_triangulation": should_force_triangulation,
|
||||
"should_generate_uvs": should_generate_uvs,
|
||||
"ifc_representation_class": ifc_representation_class,
|
||||
"profile_set_usage": profile_set_usage,
|
||||
"text_literal": text_literal,
|
||||
}
|
||||
usecase.ifc_vertices = []
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
# TODO: This usecase currently depends on Blender's data model
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"blender_object": None, # This is (currently) a Blender object, hence this depends on Blender now
|
||||
"geometry": None, # This is (currently) a Blender data object, hence this depends on Blender now
|
||||
"coordinate_offset": None, # Optionally apply a vector offset to all coordinates
|
||||
"total_items": 1, # How many representation items to create
|
||||
"unit_scale": None, # A scale factor to apply for all vectors in case the unit is different
|
||||
"should_force_faceted_brep": False, # If we should force faceted breps for meshes
|
||||
"should_force_triangulation": False, # If we should force triangulation for meshes
|
||||
"should_generate_uvs": False, # If UV coordinates should also be generated
|
||||
# Possible IFC representation classes:
|
||||
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcCircleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
|
||||
# IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
|
||||
# IfcGeometricCurveSet/IfcTextLiteral
|
||||
# IfcTextLiteral
|
||||
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
|
||||
"profile_set_usage": None, # The material profile set if the extrusion requires it
|
||||
"text_literal": None, # The text literal if the representation requires it
|
||||
}
|
||||
self.ifc_vertices = []
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
file: ifcopenshell.file
|
||||
settings: dict[str, Any]
|
||||
|
||||
def execute(self):
|
||||
self.is_manifold = None
|
||||
@@ -345,12 +388,10 @@ class Usecase:
|
||||
)
|
||||
|
||||
def create_curve3d_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Curve3D",
|
||||
self.create_curves(),
|
||||
)
|
||||
if curves := self.create_curves():
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"], self.settings["context"].ContextIdentifier, "Curve3D", curves
|
||||
)
|
||||
|
||||
def create_curve2d_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
@@ -374,12 +415,14 @@ class Usecase:
|
||||
return items
|
||||
|
||||
def create_plane(self, polygon):
|
||||
return self.file.createIfcPlane(Position=self.file.createIfcAxis2Placement3D(
|
||||
Location=self.file.createIfcCartesianPoint(polygon.center),
|
||||
Axis=self.file.createIfcDirection(polygon.normal),
|
||||
))
|
||||
return self.file.createIfcPlane(
|
||||
Position=self.file.createIfcAxis2Placement3D(
|
||||
Location=self.file.createIfcCartesianPoint(polygon.center),
|
||||
Axis=self.file.createIfcDirection(polygon.normal),
|
||||
)
|
||||
)
|
||||
|
||||
def create_annotation_fill_areas(self, is_2d=False):
|
||||
def create_annotation_fill_areas(self, is_2d=False) -> list[ifcopenshell.entity_instance]:
|
||||
items = []
|
||||
if self.file.schema != "IFC2X3":
|
||||
points = self.create_cartesian_point_list_from_vertices(self.settings["geometry"].vertices, is_2d=is_2d)
|
||||
@@ -391,7 +434,9 @@ class Usecase:
|
||||
items.append(self.file.createIfcAnnotationFillArea(OuterBoundary=curve))
|
||||
return items
|
||||
|
||||
def create_curve_from_polygon(self, points, polygon, is_2d=False):
|
||||
def create_curve_from_polygon(
|
||||
self, points: ifcopenshell.entity_instance, polygon: bpy.types.MeshPolygon, is_2d=False
|
||||
) -> ifcopenshell.entity_instance:
|
||||
indices = list(polygon.vertices)
|
||||
indices.append(indices[0])
|
||||
edge_loop = [self.file.createIfcLineIndex((v1 + 1, v2 + 1)) for v1, v2 in zip(indices, indices[1:])]
|
||||
@@ -414,7 +459,7 @@ class Usecase:
|
||||
results.append(self.file.createIfcSweptDiskSolid(curve, radius))
|
||||
return results
|
||||
|
||||
def is_mesh_curve_consequtive(self, geom_data):
|
||||
def is_mesh_curve_consecutive(self, geom_data):
|
||||
import blenderbim.tool as tool
|
||||
|
||||
bm = tool.Blender.get_bmesh_for_mesh(geom_data)
|
||||
@@ -460,15 +505,14 @@ class Usecase:
|
||||
return False
|
||||
return True
|
||||
|
||||
def create_curves(self, should_exclude_faces=False, is_2d=False):
|
||||
def create_curves(self, should_exclude_faces=False, is_2d=False, ignore_non_loose_edges=False):
|
||||
geom_data = self.settings["geometry"]
|
||||
|
||||
if isinstance(geom_data, bpy.types.Mesh):
|
||||
if self.is_mesh_curve_consequtive(geom_data):
|
||||
if self.is_mesh_curve_consecutive(geom_data):
|
||||
if self.file.schema == "IFC2X3":
|
||||
return self.create_curves_from_mesh_ifc2x3(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
|
||||
else:
|
||||
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
|
||||
return self.create_curves_from_mesh(should_exclude_faces=should_exclude_faces, is_2d=is_2d)
|
||||
|
||||
import blenderbim.tool as tool
|
||||
|
||||
@@ -530,7 +574,9 @@ class Usecase:
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
|
||||
tool.Blender.apply_bmesh(mesh, bm)
|
||||
|
||||
def create_curves_from_mesh_ifc2x3(self, should_exclude_faces=False, is_2d=False):
|
||||
def create_curves_from_mesh_ifc2x3(
|
||||
self, should_exclude_faces=False, is_2d=False
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
geom_data = self.settings["geometry"].copy()
|
||||
self.remove_doubles_from_mesh(geom_data)
|
||||
curves = []
|
||||
@@ -568,7 +614,7 @@ class Usecase:
|
||||
curve_object_data = self.settings["geometry"]
|
||||
dim = (lambda v: v.xy) if is_2d else (lambda v: v.xyz)
|
||||
results = []
|
||||
for spline in self.settings["geometry"].splines:
|
||||
for spline in curve_object_data.splines:
|
||||
points = spline.bezier_points[:] + spline.points[:]
|
||||
if spline.use_cyclic_u:
|
||||
points.append(points[0])
|
||||
@@ -741,9 +787,7 @@ class Usecase:
|
||||
[uv + 1 for uv in polygon.loop_indices]
|
||||
)
|
||||
|
||||
coordinates = self.file.createIfcCartesianPointList3D(
|
||||
[self.convert_si_to_unit(v.co) for v in self.settings["geometry"].vertices]
|
||||
)
|
||||
coordinates = self.create_cartesian_point_list_from_vertices(self.settings["geometry"].vertices)
|
||||
|
||||
if self.settings["should_generate_uvs"]:
|
||||
# Blender supports multiple UV layers. We don't. Too bad.
|
||||
@@ -778,9 +822,7 @@ class Usecase:
|
||||
ifc_raw_items[polygon.material_index % self.settings["total_items"]].append(
|
||||
self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices])
|
||||
)
|
||||
coordinates = self.file.createIfcCartesianPointList3D(
|
||||
[self.convert_si_to_unit(v.co) for v in self.settings["geometry"].vertices]
|
||||
)
|
||||
coordinates = self.create_cartesian_point_list_from_vertices(self.settings["geometry"].vertices)
|
||||
items = [self.file.createIfcPolygonalFaceSet(coordinates, self.is_manifold, i) for i in ifc_raw_items if i]
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
@@ -802,7 +844,12 @@ class Usecase:
|
||||
]
|
||||
)
|
||||
|
||||
def create_cartesian_point(self, x, y, z=None):
|
||||
def create_cartesian_point(self, x, y, z=None, is_model_coords=True):
|
||||
if is_model_coords and self.settings["coordinate_offset"]:
|
||||
x += self.settings["coordinate_offset"][0]
|
||||
y += self.settings["coordinate_offset"][1]
|
||||
if z:
|
||||
z += self.settings["coordinate_offset"][2]
|
||||
x = self.convert_si_to_unit(x)
|
||||
y = self.convert_si_to_unit(y)
|
||||
if z is None:
|
||||
@@ -810,14 +857,18 @@ class Usecase:
|
||||
z = self.convert_si_to_unit(z)
|
||||
return self.file.createIfcCartesianPoint((x, y, z))
|
||||
|
||||
def create_cartesian_point_list_from_vertices(self, vertices, is_2d=False):
|
||||
def create_cartesian_point_list_from_vertices(self, vertices: list[bpy.types.MeshVertex], is_2d=False, is_model_coords=True):
|
||||
if is_model_coords and self.settings["coordinate_offset"]:
|
||||
if is_2d:
|
||||
xy_offset = Vector((self.settings["coordinate_offset"][0:2]))
|
||||
return self.file.createIfcCartesianPointList2D([self.convert_si_to_unit(v.co.xy + xy_offset) for v in vertices])
|
||||
xyz_offset = Vector((self.settings["coordinate_offset"][0:3]))
|
||||
return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co.xyz + xyz_offset) for v in vertices])
|
||||
if is_2d:
|
||||
return self.file.createIfcCartesianPointList2D([self.convert_si_to_unit(v.co.xy) for v in vertices])
|
||||
return self.file.createIfcCartesianPointList3D([self.convert_si_to_unit(v.co) for v in vertices])
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
if self.settings["coordinate_offset"]:
|
||||
return (co / self.settings["unit_scale"]) + self.settings["coordinate_offset"]
|
||||
return co / self.settings["unit_scale"]
|
||||
|
||||
def create_annotation2d_representation(self):
|
||||
|
||||
@@ -17,23 +17,36 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.data import Clipping
|
||||
from math import sin, cos
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
def add_slab_representation(
|
||||
file,
|
||||
# IfcGeometricRepresentationContext
|
||||
context: ifcopenshell.entity_instance,
|
||||
# in meters
|
||||
depth: float = 0.2,
|
||||
# in radians
|
||||
x_angle: float = 0.0,
|
||||
# A list of planes that define clipping half space solids
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"depth": depth,
|
||||
"x_angle": x_angle,
|
||||
"clippings": clippings if clippings is not None else [],
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"depth": 0.2,
|
||||
"x_angle": 0, # Radians
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
}
|
||||
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)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
|
||||
@@ -18,28 +18,43 @@
|
||||
|
||||
import ifcopenshell.util.unit
|
||||
from math import sin, cos
|
||||
from typing import Optional, Union, Any
|
||||
from ifcopenshell.util.data import Clipping
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"context": None, # IfcGeometricRepresentationContext
|
||||
"length": 1.0,
|
||||
"height": 3.0,
|
||||
"offset": 0.0,
|
||||
"thickness": 0.2,
|
||||
# Sloped walls along the wall's X axis, provided in radians
|
||||
"x_angle": 0,
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
"clippings": [], # A list of planes that define clipping half space solids
|
||||
"booleans": [], # Any existing IfcBooleanResults
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def add_wall_representation(
|
||||
file: ifcopenshell.file,
|
||||
context: ifcopenshell.entity_instance, # IfcGeometricRepresentationContext
|
||||
# all lengths are in meters
|
||||
length: float = 1.0,
|
||||
height: float = 3.0,
|
||||
offset: float = 0.0,
|
||||
thickness: float = 0.2,
|
||||
# Sloped walls along the wall's X axis, provided in radians
|
||||
x_angle: float = 0.0,
|
||||
# A list of planes that define clipping half space solids
|
||||
# Planes are defined either by Clipping objects
|
||||
# or by dictionaries of arguments for `Clipping.parse`
|
||||
clippings: Optional[list[Union[Clipping, dict[str, Any]]]] = None,
|
||||
# Any existing IfcBooleanResults
|
||||
booleans: Optional[list[ifcopenshell.entity_instance]] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"context": context,
|
||||
"length": length,
|
||||
"height": height,
|
||||
"offset": offset,
|
||||
"thickness": thickness,
|
||||
"x_angle": x_angle,
|
||||
"clippings": clippings if clippings is not None else [],
|
||||
"booleans": booleans if booleans is not None else [],
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
self.settings["clippings"] = [Clipping.parse(c) for c in self.settings["clippings"]]
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import collections.abc
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder, V
|
||||
from itertools import chain
|
||||
from mathutils import Vector
|
||||
import collections
|
||||
import dataclasses
|
||||
from typing import Any, Optional, Literal, Union
|
||||
|
||||
|
||||
# SCHEMAS describe panels setup
|
||||
@@ -42,6 +45,11 @@ DEFAULT_PANEL_SCHEMAS = {
|
||||
}
|
||||
|
||||
|
||||
def mm(x: float) -> float:
|
||||
"""mm to meters shortcut for readability"""
|
||||
return x / 1000
|
||||
|
||||
|
||||
def create_ifc_window_frame_simple(
|
||||
builder: ShapeBuilder, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()
|
||||
):
|
||||
@@ -56,12 +64,7 @@ def create_ifc_window_frame_simple(
|
||||
th_left, th_up, th_right, th_bottom = thickness
|
||||
|
||||
def get_extruded_profile(profile):
|
||||
return builder.extrude(
|
||||
profile,
|
||||
size.y,
|
||||
position=position,
|
||||
**builder.extrude_kwargs("Y")
|
||||
)
|
||||
return builder.extrude(profile, size.y, position=position, **builder.extrude_kwargs("Y"))
|
||||
|
||||
# if all lining sides are present then we can just use two rectangles
|
||||
# as inner and outer curves of the profile
|
||||
@@ -207,12 +210,7 @@ def create_ifc_window(
|
||||
|
||||
glass_position = frame_position + V(0, frame_size.y / 2 - glass_thickness / 2, 0)
|
||||
glass_rect = builder.deep_copy(frame_extruded_items[0].SweptArea.InnerCurves[0])
|
||||
glass = builder.extrude(
|
||||
glass_rect,
|
||||
glass_thickness,
|
||||
position=glass_position,
|
||||
**builder.extrude_kwargs("Y")
|
||||
)
|
||||
glass = builder.extrude(glass_rect, glass_thickness, position=glass_position, **builder.extrude_kwargs("Y"))
|
||||
|
||||
output_items = [lining_items, frame_extruded_items, [glass]]
|
||||
builder.translate(chain(*output_items), position)
|
||||
@@ -220,73 +218,214 @@ def create_ifc_window(
|
||||
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
|
||||
},
|
||||
],
|
||||
}
|
||||
# we use dataclass as we need default values for arguments
|
||||
# it's okay to use slots since we don't need dynamic attributes
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class WindowLiningProperties:
|
||||
LiningDepth: Optional[float] = None
|
||||
"""Optional, defaults to 50mm."""
|
||||
|
||||
LiningThickness: Optional[float] = None
|
||||
"""Optional, defaults to 50mm."""
|
||||
|
||||
LiningOffset: Optional[float] = None
|
||||
"""Offset to the wall. Optional, defaults to 50mm."""
|
||||
|
||||
LiningToPanelOffsetX: Optional[float] = None
|
||||
"""Offset from the wall. Optional, defaults to 25mm."""
|
||||
|
||||
# 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: Optional[float] = None
|
||||
"""Offset from the lining. Optional, defaults to 25mm."""
|
||||
|
||||
MullionThickness: Optional[float] = None
|
||||
"""Mullion thickness (horizontal distance between panels).
|
||||
|
||||
Applies to windows of types: DoublePanelVertical, TriplePanelBottom, TriplePanelTop,
|
||||
TriplePanelLeft, TriplePanelRight.
|
||||
|
||||
Optional, defaults to 50mm."""
|
||||
|
||||
FirstMullionOffset: Optional[float] = None
|
||||
"""Distance from the first lining to the mullion center. Optional, defaults to 300mm."""
|
||||
|
||||
SecondMullionOffset: Optional[float] = None
|
||||
"""Distance from the first lining to the second mullion center.
|
||||
|
||||
Applies to windows of type: TriplePanelVertical.
|
||||
|
||||
Optional, defaults to 450mm."""
|
||||
|
||||
TransomThickness: Optional[float] = None
|
||||
"""Transom thickness (vertical distance between panels), works similar way to mullions.
|
||||
|
||||
Applies to windows of types:DoublePanelHorizontal, TriplePanelBottom, TriplePanelTop,
|
||||
TriplePanelLeft, TriplePanelRight.
|
||||
|
||||
Optional, defaults to 50mm."""
|
||||
|
||||
FirstTransomOffset: Optional[float] = None
|
||||
"""Optional, defaults to 300mm."""
|
||||
|
||||
SecondTransomOffset: Optional[float] = None
|
||||
"""
|
||||
Applies to windows of type: TriplePanelHorizontal.
|
||||
Optional, defaults to 600mm."""
|
||||
|
||||
ShapeAspectStyle: None = None
|
||||
"""Optional. Deprecated argument."""
|
||||
|
||||
def initialize_properties(self, unit_scale: float) -> None:
|
||||
# in meters
|
||||
# fmt: off
|
||||
default_values: dict[str, float] = dict(
|
||||
LiningDepth = mm(50),
|
||||
LiningThickness = mm(50),
|
||||
LiningOffset = mm(50),
|
||||
LiningToPanelOffsetX = mm(25),
|
||||
LiningToPanelOffsetY = mm(25),
|
||||
MullionThickness = mm(50),
|
||||
FirstMullionOffset = mm(300),
|
||||
SecondMullionOffset = mm(450),
|
||||
TransomThickness = mm(50),
|
||||
FirstTransomOffset = mm(300),
|
||||
SecondTransomOffset = mm(600),
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
self.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[self.settings["partition_type"]]
|
||||
si_conversion = 1 / unit_scale
|
||||
for attr, default_value in default_values.items():
|
||||
if getattr(self, attr) is not None:
|
||||
continue
|
||||
setattr(self, attr, default_value * si_conversion)
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class WindowPanelProperties:
|
||||
FrameDepth: Optional[float] = None
|
||||
"""Frame thickness by Y axis. Optional, defaults to 35 mm."""
|
||||
|
||||
FrameThickness: Optional[float] = None
|
||||
"""Frame thickness by X axis. Optional, defaults to 35 mm."""
|
||||
|
||||
PanelPosition: None = None
|
||||
"""Optional, value is never used"""
|
||||
|
||||
PanelOperation: None = None
|
||||
"""Optional, value is never used.
|
||||
Defines the basic ways to describe how window panels operate."""
|
||||
|
||||
ShapeAspectStyle: None = None
|
||||
"""Optional. Deprecated argument."""
|
||||
|
||||
def initialize_properties(self, unit_scale: float) -> None:
|
||||
# in meters
|
||||
# fmt: off
|
||||
default_values: dict[str, float] = dict(
|
||||
FrameDepth = mm(35),
|
||||
FrameThickness = mm(35),
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
si_conversion = 1 / unit_scale
|
||||
for attr, default_value in default_values.items():
|
||||
if getattr(self, attr) is not None:
|
||||
continue
|
||||
setattr(self, attr, default_value * si_conversion)
|
||||
|
||||
|
||||
def add_window_representation(
|
||||
file: ifcopenshell.file,
|
||||
*, # keywords only as this API implementation is probably not final
|
||||
context: ifcopenshell.entity_instance,
|
||||
overall_height: Optional[float] = None,
|
||||
overall_width: Optional[float] = None,
|
||||
partition_type: Literal[
|
||||
"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",
|
||||
] = "SINGLE_PANEL",
|
||||
lining_properties: Optional[Union[WindowLiningProperties, dict[str, Any]]] = None,
|
||||
panel_properties: Optional[list[Union[WindowPanelProperties, dict[str, Any]]]] = None,
|
||||
unit_scale: Optional[float] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""units in usecase_settings expected to be in ifc project units
|
||||
|
||||
:param context: IfcGeometricRepresentationContext for the representation.
|
||||
:type context: ifcopenshell.entity_instance
|
||||
:param overall_height: Overall window height. Defaults to 0.9m.
|
||||
:type overall_height: float, optional
|
||||
:param overall_width: Overall window width. Defaults to 0.6m.
|
||||
:type overall_width: float, optional
|
||||
:param partition_type: Type of the window. Defaults to SINGLE_PANEL.
|
||||
:type partition_type: str, optional
|
||||
:param lining_properties: WindowLiningProperties or a dictionary to create one.
|
||||
See WindowLiningProperties description for details.
|
||||
:type lining_properties: Union[WindowLiningProperties, dict[str, Any]]]
|
||||
:param panel_properties: A list of WindowPanelProperties or dictionaries to create one.
|
||||
See WindowPanelProperties description for details.
|
||||
:type panel_properties: list[Union[WindowPanelProperties, dict[str, Any]]]]
|
||||
: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: IfcShapeRepresentation for a window.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.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
|
||||
# define unit_scale first as it's going to be used setting default arguments
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file) if unit_scale is None else unit_scale
|
||||
settings: dict[str, Any] = {"unit_scale": unit_scale}
|
||||
|
||||
if lining_properties is None:
|
||||
lining_properties = WindowLiningProperties()
|
||||
elif not isinstance(lining_properties, WindowLiningProperties):
|
||||
lining_properties = WindowLiningProperties(**lining_properties)
|
||||
lining_properties.initialize_properties(unit_scale)
|
||||
lining_properties = dataclasses.asdict(lining_properties)
|
||||
|
||||
if panel_properties is None:
|
||||
panel_properties = [WindowPanelProperties()]
|
||||
|
||||
for i in range(len(panel_properties)):
|
||||
properties = panel_properties[i]
|
||||
if not isinstance(properties, WindowPanelProperties):
|
||||
properties = WindowPanelProperties(**properties)
|
||||
properties.initialize_properties(unit_scale)
|
||||
panel_properties[i] = dataclasses.asdict(properties)
|
||||
|
||||
settings.update(
|
||||
{
|
||||
"context": context,
|
||||
"overall_height": overall_height if overall_height is not None else usecase.convert_si_to_unit(0.9),
|
||||
"overall_width": overall_width if overall_width is not None else usecase.convert_si_to_unit(0.6),
|
||||
"partition_type": partition_type,
|
||||
"lining_properties": lining_properties,
|
||||
"panel_properties": panel_properties,
|
||||
}
|
||||
)
|
||||
|
||||
usecase.settings = settings
|
||||
usecase.settings["panel_schema"] = DEFAULT_PANEL_SCHEMAS[usecase.settings["partition_type"]]
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
builder = ShapeBuilder(self.file)
|
||||
overall_height = self.settings["overall_height"]
|
||||
|
||||
@@ -20,13 +20,16 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "representation": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def assign_representation(
|
||||
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"product": product, "representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcProduct"):
|
||||
product_type = ifcopenshell.util.element.get_type(self.settings["product"])
|
||||
|
||||
@@ -18,47 +18,49 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
"description": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def connect_element(
|
||||
file: ifcopenshell.file,
|
||||
relating_element: ifcopenshell.entity_instance,
|
||||
related_element: ifcopenshell.entity_instance,
|
||||
description: Optional[str] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
settings = {
|
||||
"relating_element": relating_element,
|
||||
"related_element": related_element,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
incompatible_connections = []
|
||||
incompatible_connections = []
|
||||
|
||||
for rel in self.settings["relating_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
for rel in settings["relating_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["related_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
for rel in settings["related_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
self.file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
for rel in self.settings["relating_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["related_element"]:
|
||||
rel.Description = self.settings["description"]
|
||||
return rel
|
||||
for rel in settings["relating_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["related_element"]:
|
||||
rel.Description = settings["description"]
|
||||
return rel
|
||||
|
||||
return self.file.createIfcRelConnectsElements(
|
||||
ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
Description=self.settings["description"],
|
||||
RelatingElement=self.settings["relating_element"],
|
||||
RelatedElement=self.settings["related_element"],
|
||||
)
|
||||
return file.createIfcRelConnectsElements(
|
||||
ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
Description=settings["description"],
|
||||
RelatingElement=settings["relating_element"],
|
||||
RelatedElement=settings["related_element"],
|
||||
)
|
||||
|
||||
@@ -18,79 +18,83 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
"relating_connection": "NOTDEFINED",
|
||||
"related_connection": "NOTDEFINED",
|
||||
"description": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def connect_path(
|
||||
file: ifcopenshell.file,
|
||||
relating_element: ifcopenshell.entity_instance,
|
||||
related_element: ifcopenshell.entity_instance,
|
||||
relating_connection: str = "NOTDEFINED",
|
||||
related_connection: str = "NOTDEFINED",
|
||||
description: Optional[str] = None,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
settings = {
|
||||
"relating_element": relating_element,
|
||||
"related_element": related_element,
|
||||
"relating_connection": relating_connection,
|
||||
"related_connection": related_connection,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
incompatible_connections = []
|
||||
for rel in self.settings["relating_element"].ConnectedTo:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if rel.RelatedElement == self.settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
elif (
|
||||
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatingConnectionType == self.settings["relating_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
incompatible_connections = []
|
||||
for rel in settings["relating_element"].ConnectedTo:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if rel.RelatedElement == settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
elif (
|
||||
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatingConnectionType == settings["relating_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["relating_element"].ConnectedFrom:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if (
|
||||
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatedConnectionType == self.settings["relating_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
for rel in settings["relating_element"].ConnectedFrom:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if (
|
||||
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatedConnectionType == settings["relating_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["related_element"].ConnectedFrom:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if (
|
||||
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatedConnectionType == self.settings["related_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
for rel in settings["related_element"].ConnectedFrom:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if (
|
||||
rel.RelatedConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatedConnectionType == settings["related_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["related_element"].ConnectedTo:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if rel.RelatedElement == self.settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
elif (
|
||||
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatingConnectionType == self.settings["related_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
for rel in settings["related_element"].ConnectedTo:
|
||||
if not rel.is_a("IfcRelConnectsPathElements"):
|
||||
continue
|
||||
if rel.RelatedElement == settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
elif (
|
||||
rel.RelatingConnectionType in ["ATSTART", "ATEND"]
|
||||
and rel.RelatingConnectionType == settings["related_connection"]
|
||||
):
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
self.file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
return self.file.createIfcRelConnectsPathElements(
|
||||
ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
Description=self.settings["description"],
|
||||
RelatingElement=self.settings["relating_element"],
|
||||
RelatedElement=self.settings["related_element"],
|
||||
RelatingConnectionType=self.settings["relating_connection"],
|
||||
RelatedConnectionType=self.settings["related_connection"],
|
||||
RelatingPriorities=[],
|
||||
RelatedPriorities=[],
|
||||
)
|
||||
return file.createIfcRelConnectsPathElements(
|
||||
ifcopenshell.guid.new(),
|
||||
OwnerHistory=ifcopenshell.api.run("owner.create_owner_history", file),
|
||||
Description=settings["description"],
|
||||
RelatingElement=settings["relating_element"],
|
||||
RelatedElement=settings["related_element"],
|
||||
RelatingConnectionType=settings["relating_connection"],
|
||||
RelatedConnectionType=settings["related_connection"],
|
||||
RelatingPriorities=[],
|
||||
RelatedPriorities=[],
|
||||
)
|
||||
|
||||
@@ -21,20 +21,33 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, element=None, context=None, p1=None, p2=None, elevation=None, height=None, thickness=None, is_si=True):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"element": element,
|
||||
"context": context,
|
||||
"p1": p1,
|
||||
"p2": p2,
|
||||
"elevation": elevation,
|
||||
"height": height,
|
||||
"thickness": thickness,
|
||||
"is_si": is_si
|
||||
}
|
||||
def create_2pt_wall(
|
||||
file: ifcopenshell.file,
|
||||
element: ifcopenshell.entity_instance,
|
||||
context: ifcopenshell.entity_instance,
|
||||
p1: tuple[float, float],
|
||||
p2: tuple[float, float],
|
||||
elevation: float,
|
||||
height: float,
|
||||
thickness: float,
|
||||
is_si: bool = True,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"element": element,
|
||||
"context": context,
|
||||
"p1": p1,
|
||||
"p2": p2,
|
||||
"elevation": elevation,
|
||||
"height": height,
|
||||
"thickness": thickness,
|
||||
"is_si": is_si,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
|
||||
@@ -44,9 +57,9 @@ class Usecase:
|
||||
length = float(np.linalg.norm(self.settings["p2"] - self.settings["p1"]))
|
||||
|
||||
if not self.settings["is_si"]:
|
||||
length=self.convert_unit_to_si(length)
|
||||
self.settings["height"]=self.convert_unit_to_si(self.settings["height"])
|
||||
self.settings["thickness"]=self.convert_unit_to_si(self.settings["thickness"])
|
||||
length = self.convert_unit_to_si(length)
|
||||
self.settings["height"] = self.convert_unit_to_si(self.settings["height"])
|
||||
self.settings["thickness"] = self.convert_unit_to_si(self.settings["thickness"])
|
||||
self.settings["p1"][0] = self.convert_unit_to_si(self.settings["p1"][0])
|
||||
self.settings["p1"][1] = self.convert_unit_to_si(self.settings["p1"][1])
|
||||
self.settings["elevation"] = self.convert_unit_to_si(self.settings["elevation"])
|
||||
|
||||
@@ -20,38 +20,36 @@ import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def disconnect_element(
|
||||
file: ifcopenshell.file,
|
||||
relating_element: ifcopenshell.entity_instance,
|
||||
related_element: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
# TODO: arguments relating_element, related_element probably
|
||||
# should be renamed to element1, element2
|
||||
# as api call doesn't really treat them as "relating" and "related"
|
||||
# and just purging all connections between them
|
||||
incompatible_connections = []
|
||||
|
||||
def execute(self):
|
||||
incompatible_connections = []
|
||||
for rel in relating_element.ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == related_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["relating_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
for rel in relating_element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == related_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["relating_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["related_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
for rel in related_element.ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == relating_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["related_element"].ConnectedTo:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == self.settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
for rel in related_element.ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == relating_element:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
for rel in self.settings["related_element"].ConnectedFrom:
|
||||
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == self.settings["relating_element"]:
|
||||
incompatible_connections.append(rel)
|
||||
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
self.file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
if incompatible_connections:
|
||||
for connection in set(incompatible_connections):
|
||||
history = connection.OwnerHistory
|
||||
file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -19,40 +19,40 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_element": None,
|
||||
"related_element": None,
|
||||
"element": None,
|
||||
"connection_type": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def disconnect_path(
|
||||
file: ifcopenshell.file,
|
||||
element: Optional[ifcopenshell.entity_instance] = None,
|
||||
connection_type: Optional[str] = None,
|
||||
relating_element: Optional[ifcopenshell.entity_instance] = None,
|
||||
related_element: Optional[ifcopenshell.entity_instance] = None,
|
||||
) -> None:
|
||||
"""There are two options to use this API method:
|
||||
- provide `element` (connected from) and `connection_type` that should be disconnected.
|
||||
- provide connected elements to disconnect explicitly:
|
||||
`relating_element` (connected from) and `related_element` (connected to)
|
||||
"""
|
||||
if connection_type and element:
|
||||
connections = [
|
||||
r
|
||||
for r in element.ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == connection_type
|
||||
] + [
|
||||
r
|
||||
for r in element.ConnectedFrom
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == connection_type
|
||||
]
|
||||
elif related_element:
|
||||
connections = [
|
||||
r
|
||||
for r in relating_element.ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == related_element
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
if self.settings["connection_type"] and self.settings["element"]:
|
||||
connections = [
|
||||
r
|
||||
for r in self.settings["element"].ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatingConnectionType == self.settings["connection_type"]
|
||||
] + [
|
||||
r
|
||||
for r in self.settings["element"].ConnectedFrom
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedConnectionType == self.settings["connection_type"]
|
||||
]
|
||||
else:
|
||||
connections = [
|
||||
r
|
||||
for r in self.settings["relating_element"].ConnectedTo
|
||||
if r.is_a("IfcRelConnectsPathElements") and r.RelatedElement == self.settings["related_element"]
|
||||
]
|
||||
|
||||
for connection in set(connections):
|
||||
history = connection.OwnerHistory
|
||||
self.file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
for connection in set(connections):
|
||||
history = connection.OwnerHistory
|
||||
file.remove(connection)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
|
||||
@@ -17,19 +17,35 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from typing import Optional, Union
|
||||
|
||||
NPArrayOfFloats = npt.NDArray[np.float64]
|
||||
|
||||
|
||||
def edit_object_placement(
|
||||
file: ifcopenshell.file,
|
||||
product: ifcopenshell.entity_instance,
|
||||
matrix: Optional[NPArrayOfFloats] = None,
|
||||
is_si: bool = True,
|
||||
should_transform_children: bool = False,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"product": product,
|
||||
"matrix": matrix if matrix is not None else np.eye(4),
|
||||
"is_si": is_si,
|
||||
"should_transform_children": should_transform_children,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "matrix": np.eye(4), "is_si": True, "should_transform_children": False}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not hasattr(self.settings["product"], "ObjectPlacement"):
|
||||
return
|
||||
@@ -69,34 +85,37 @@ class Usecase:
|
||||
|
||||
return new_placement
|
||||
|
||||
def convert_matrix_to_si(self, matrix):
|
||||
def convert_matrix_to_si(self, matrix: NPArrayOfFloats):
|
||||
matrix[0][3] *= self.unit_scale
|
||||
matrix[1][3] *= self.unit_scale
|
||||
matrix[2][3] *= self.unit_scale
|
||||
|
||||
def get_placement_rel_to(self):
|
||||
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):
|
||||
relating_object = self.settings["product"].Nests[0].RelatingObject
|
||||
return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif getattr(self.settings["product"], "ContainedIn", None):
|
||||
related_element = self.settings["product"].ContainedIn[0].RelatedElement
|
||||
return related_element.ObjectPlacement if hasattr(related_element, "ObjectPlacement") else None
|
||||
elif getattr(self.settings["product"], "VoidsElements", None):
|
||||
relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement
|
||||
return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif getattr(self.settings["product"], "FillsVoids", None):
|
||||
relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement
|
||||
return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
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_placement_rel_to(self) -> Union[ifcopenshell.entity_instance, None]:
|
||||
product = self.settings["product"]
|
||||
relating_object = None
|
||||
|
||||
def get_children_settings(self, placement):
|
||||
if rels := getattr(product, "Decomposes", None):
|
||||
relating_object = rels[0].RelatingObject
|
||||
elif rels := getattr(product, "Nests", None):
|
||||
relating_object = rels[0].RelatingObject
|
||||
elif rels := getattr(product, "ContainedIn", None):
|
||||
relating_object = rels[0].RelatedElement
|
||||
elif rels := getattr(product, "VoidsElements", None):
|
||||
relating_object = rels[0].RelatingBuildingElement
|
||||
elif rels := getattr(product, "FillsVoids", None):
|
||||
relating_object = rels[0].RelatingOpeningElement
|
||||
elif rels := getattr(product, "ProjectsElements", None):
|
||||
relating_object = rels[0].RelatingElement
|
||||
# TODO: add tests when there will be adherence api
|
||||
elif rels := getattr(product, "AdheresToElement", None):
|
||||
relating_object = rels[0].RelatingElement
|
||||
elif rels := getattr(product, "ContainedInStructure", None):
|
||||
return rels[0].RelatingStructure.ObjectPlacement
|
||||
|
||||
if relating_object:
|
||||
return getattr(relating_object, "ObjectPlacement", None)
|
||||
|
||||
def get_children_settings(self, placement: Union[ifcopenshell.entity_instance, None]) -> list[dict]:
|
||||
if not placement:
|
||||
return []
|
||||
results = []
|
||||
@@ -116,7 +135,9 @@ class Usecase:
|
||||
results.append({"product": obj, "matrix": matrix, "is_si": False, "should_transform_children": True})
|
||||
return results
|
||||
|
||||
def get_relative_placement(self, placement_rel_to):
|
||||
def get_relative_placement(
|
||||
self, placement_rel_to: Union[ifcopenshell.entity_instance, None]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
if placement_rel_to:
|
||||
relating_object_matrix = ifcopenshell.util.placement.get_local_placement(placement_rel_to)
|
||||
relating_object_matrix[0][3] = self.convert_unit_to_si(relating_object_matrix[0][3])
|
||||
@@ -136,19 +157,21 @@ class Usecase:
|
||||
relative_placement_matrix[:, 0][0:3],
|
||||
)
|
||||
|
||||
def create_ifc_axis_2_placement_3d(self, point, up, forward):
|
||||
def create_ifc_axis_2_placement_3d(
|
||||
self, point: NPArrayOfFloats, up: NPArrayOfFloats, forward: NPArrayOfFloats
|
||||
) -> ifcopenshell.entity_instance:
|
||||
return self.file.createIfcAxis2Placement3D(
|
||||
self.create_cartesian_point(point),
|
||||
self.file.createIfcDirection(up.tolist()),
|
||||
self.file.createIfcDirection(forward.tolist()),
|
||||
)
|
||||
|
||||
def create_cartesian_point(self, co):
|
||||
def create_cartesian_point(self, co: NPArrayOfFloats) -> ifcopenshell.entity_instance:
|
||||
co = self.convert_si_to_unit(co)
|
||||
return self.file.createIfcCartesianPoint(co.tolist())
|
||||
|
||||
def convert_si_to_unit(self, co):
|
||||
def convert_si_to_unit(self, co: NPArrayOfFloats) -> NPArrayOfFloats:
|
||||
return co / self.unit_scale
|
||||
|
||||
def convert_unit_to_si(self, co):
|
||||
def convert_unit_to_si(self, co: NPArrayOfFloats) -> NPArrayOfFloats:
|
||||
return co * self.unit_scale
|
||||
|
||||
@@ -16,16 +16,24 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Any
|
||||
|
||||
|
||||
def map_representation(
|
||||
file: ifcopenshell.file, representation: ifcopenshell.entity_instance
|
||||
) -> ifcopenshell.entity_instance:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"representation": None}
|
||||
self.ifc_vertices = []
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
file: ifcopenshell.file
|
||||
settings: dict[str, Any]
|
||||
|
||||
def execute(self):
|
||||
def execute(self) -> ifcopenshell.entity_instance:
|
||||
mapping_source = self.get_mapping_source()
|
||||
|
||||
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
@@ -45,7 +53,7 @@ class Usecase:
|
||||
}
|
||||
)
|
||||
|
||||
def get_mapping_source(self):
|
||||
def get_mapping_source(self) -> ifcopenshell.entity_instance:
|
||||
for inverse in self.file.get_inverse(self.settings["representation"]):
|
||||
if inverse.is_a("IfcRepresentationMap"):
|
||||
return inverse
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"item": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def remove_boolean(file: ifcopenshell.file, item: ifcopenshell.entity_instance) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"item": item}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
item = None
|
||||
for inverse in self.file.get_inverse(self.settings["item"]):
|
||||
|
||||
@@ -19,62 +19,57 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file: ifcopenshell.file, representation: ifcopenshell.entity_instance):
|
||||
"""Remove a representation.
|
||||
def remove_representation(file: ifcopenshell.file, representation: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a representation.
|
||||
|
||||
Also purges representation items and their related elements
|
||||
like IfcStyledItem, tessellated facesets colours and UV map.
|
||||
Also purges representation items and their related elements
|
||||
like IfcStyledItem, tessellated facesets colours and UV map.
|
||||
|
||||
:param representation: IfcRepresentation to remove.
|
||||
Note that it's expected that IfcRepresentation won't be in use
|
||||
before calling this method (in such elements as IfcProductRepresentation, IfcShapeAspect)
|
||||
otherwise representation won't be removed.
|
||||
:type representation: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
self.file = file
|
||||
self.settings = {"representation": representation}
|
||||
:param representation: IfcRepresentation to remove.
|
||||
Note that it's expected that IfcRepresentation won't be in use
|
||||
before calling this method (in such elements as IfcProductRepresentation, IfcShapeAspect)
|
||||
otherwise representation won't be removed.
|
||||
:type representation: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
settings = {"representation": representation}
|
||||
|
||||
def execute(self) -> None:
|
||||
styled_items = set()
|
||||
presentation_layer_assignments = set()
|
||||
textures = set()
|
||||
colours = set()
|
||||
for subelement in self.file.traverse(self.settings["representation"]):
|
||||
if subelement.is_a("IfcRepresentationItem"):
|
||||
[styled_items.add(s) for s in subelement.StyledByItem or []]
|
||||
# IFC2X3 is using LayerAssignments
|
||||
for s in (
|
||||
subelement.LayerAssignment
|
||||
if hasattr(subelement, "LayerAssignment")
|
||||
else subelement.LayerAssignments
|
||||
):
|
||||
presentation_layer_assignments.add(s)
|
||||
# IfcTessellatedFaceSet inverses
|
||||
[textures.add(t) for t in getattr(subelement, "HasTextures", []) or []]
|
||||
[colours.add(t) for t in getattr(subelement, "HasColours", []) or []]
|
||||
elif subelement.is_a("IfcRepresentation"):
|
||||
for layer in subelement.LayerAssignments:
|
||||
presentation_layer_assignments.add(layer)
|
||||
styled_items = set()
|
||||
presentation_layer_assignments = set()
|
||||
textures = set()
|
||||
colours = set()
|
||||
for subelement in file.traverse(settings["representation"]):
|
||||
if subelement.is_a("IfcRepresentationItem"):
|
||||
[styled_items.add(s) for s in subelement.StyledByItem or []]
|
||||
# IFC2X3 is using LayerAssignments
|
||||
for s in (
|
||||
subelement.LayerAssignment if hasattr(subelement, "LayerAssignment") else subelement.LayerAssignments
|
||||
):
|
||||
presentation_layer_assignments.add(s)
|
||||
# IfcTessellatedFaceSet inverses
|
||||
[textures.add(t) for t in getattr(subelement, "HasTextures", []) or []]
|
||||
[colours.add(t) for t in getattr(subelement, "HasColours", []) or []]
|
||||
elif subelement.is_a("IfcRepresentation"):
|
||||
for layer in subelement.LayerAssignments:
|
||||
presentation_layer_assignments.add(layer)
|
||||
|
||||
ifcopenshell.util.element.remove_deep2(
|
||||
self.file,
|
||||
self.settings["representation"],
|
||||
also_consider=list(styled_items | presentation_layer_assignments | colours),
|
||||
do_not_delete=self.file.by_type("IfcGeometricRepresentationContext"),
|
||||
)
|
||||
ifcopenshell.util.element.remove_deep2(
|
||||
file,
|
||||
settings["representation"],
|
||||
also_consider=list(styled_items | presentation_layer_assignments | colours),
|
||||
do_not_delete=file.by_type("IfcGeometricRepresentationContext"),
|
||||
)
|
||||
|
||||
for texture in textures:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, texture)
|
||||
for colour in colours:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, colour)
|
||||
for texture in textures:
|
||||
ifcopenshell.util.element.remove_deep2(file, texture)
|
||||
for colour in colours:
|
||||
ifcopenshell.util.element.remove_deep2(file, colour)
|
||||
|
||||
to_delete = getattr(self.file, "to_delete", ())
|
||||
for element in styled_items:
|
||||
if not element.Item or element.Item in to_delete:
|
||||
self.file.remove(element)
|
||||
for element in presentation_layer_assignments:
|
||||
if all(item in to_delete for item in element.AssignedItems):
|
||||
self.file.remove(element)
|
||||
to_delete = getattr(file, "to_delete", ())
|
||||
for element in styled_items:
|
||||
if not element.Item or element.Item in to_delete:
|
||||
file.remove(element)
|
||||
for element in presentation_layer_assignments:
|
||||
if all(item in to_delete for item in element.AssignedItems):
|
||||
file.remove(element)
|
||||
|
||||
@@ -20,13 +20,16 @@ import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "representation": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
def unassign_representation(
|
||||
file: ifcopenshell.file, product: ifcopenshell.entity_instance, representation: ifcopenshell.entity_instance
|
||||
) -> None:
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {"product": product, "representation": representation}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcProduct"):
|
||||
self.unassign_product_representation(self.settings["product"], self.settings["representation"])
|
||||
@@ -44,17 +47,25 @@ class Usecase:
|
||||
product.Representation.Representations = representations
|
||||
|
||||
def unassign_type_representation(self):
|
||||
|
||||
matching_representation_map = None
|
||||
representation_maps = self.settings["product"].RepresentationMaps or []
|
||||
|
||||
for representation_map in self.settings["product"].RepresentationMaps or []:
|
||||
if representation_map.MappedRepresentation == self.settings["representation"]:
|
||||
self.unassign_products_using_mapped_representation(representation_map)
|
||||
self.remove_representation_map_only(representation_map)
|
||||
matching_representation_map = representation_map
|
||||
break
|
||||
self.settings["product"].RepresentationMaps = self.settings["product"].RepresentationMaps or None
|
||||
|
||||
if matching_representation_map:
|
||||
self.unassign_products_using_mapped_representation(matching_representation_map)
|
||||
self.settings["product"].RepresentationMaps = [
|
||||
rm for rm in self.settings["product"].RepresentationMaps if rm != matching_representation_map
|
||||
] or None
|
||||
self.remove_representation_map_only(matching_representation_map)
|
||||
|
||||
def remove_representation_map_only(self, representation_map):
|
||||
representation_map.MappedRepresentation = self.file.createIfcShapeRepresentation()
|
||||
ifcopenshell.util.element.remove_deep2(self.file, representation_map)
|
||||
self.file.remove(representation_map)
|
||||
|
||||
def unassign_products_using_mapped_representation(self, representation_map):
|
||||
mapped_representations = []
|
||||
|
||||
@@ -15,3 +15,23 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manage georeferencing metadata
|
||||
|
||||
IFC model geometry may have a coordinate reference system (CRS) assigned to it.
|
||||
It may also optionally have a map conversion defined to transform to and from
|
||||
map coordinates and project local engineering coordinates.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
from .add_georeferencing import add_georeferencing
|
||||
from .edit_georeferencing import edit_georeferencing
|
||||
from .remove_georeferencing import remove_georeferencing
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"add_georeferencing",
|
||||
"edit_georeferencing",
|
||||
"remove_georeferencing",
|
||||
]
|
||||
|
||||
@@ -16,49 +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
|
||||
|
||||
class Usecase:
|
||||
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.
|
||||
def add_georeferencing(file: ifcopenshell.file) -> None:
|
||||
"""Add empty georeferencing entities to a model
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
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.
|
||||
|
||||
Example:
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
.. code:: python
|
||||
Example:
|
||||
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
"""
|
||||
self.file = file
|
||||
.. code:: python
|
||||
|
||||
def execute(self):
|
||||
source_crs = None
|
||||
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.ContextType == "Model":
|
||||
source_crs = context
|
||||
break
|
||||
if not source_crs:
|
||||
return
|
||||
projected_crs = self.file.create_entity("IfcProjectedCRS", **{"Name": ""})
|
||||
self.file.create_entity(
|
||||
"IfcMapConversion",
|
||||
**{
|
||||
"SourceCRS": source_crs,
|
||||
"TargetCRS": projected_crs,
|
||||
"Eastings": 0,
|
||||
"Northings": 0,
|
||||
"OrthogonalHeight": 0,
|
||||
}
|
||||
)
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
"""
|
||||
|
||||
source_crs = None
|
||||
for context in file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.ContextType == "Model":
|
||||
source_crs = context
|
||||
break
|
||||
if not source_crs:
|
||||
return
|
||||
projected_crs = file.create_entity("IfcProjectedCRS", **{"Name": ""})
|
||||
file.create_entity(
|
||||
"IfcMapConversion",
|
||||
**{
|
||||
"SourceCRS": source_crs,
|
||||
"TargetCRS": projected_crs,
|
||||
"Eastings": 0,
|
||||
"Northings": 0,
|
||||
"OrthogonalHeight": 0,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -16,78 +16,89 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Optional, Any
|
||||
|
||||
|
||||
def edit_georeferencing(
|
||||
file: ifcopenshell.file,
|
||||
map_conversion: Optional[dict[str, Any]] = None,
|
||||
projected_crs: Optional[dict[str, Any]] = None,
|
||||
true_north: Optional[tuple[float, float]] = None,
|
||||
) -> 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://docs.blenderbim.org/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: tuple[float, float], optional
|
||||
: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!
|
||||
})
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"map_conversion": map_conversion or {},
|
||||
"projected_crs": projected_crs or {},
|
||||
"true_north": true_north or [],
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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://docs.blenderbim.org/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": map_conversion or {},
|
||||
"projected_crs": projected_crs or {},
|
||||
"true_north": true_north or [],
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
map_conversion = self.file.by_type("IfcMapConversion")[0]
|
||||
projected_crs = self.file.by_type("IfcProjectedCRS")[0]
|
||||
@@ -98,7 +109,7 @@ class Usecase:
|
||||
self.set_true_north()
|
||||
|
||||
def set_true_north(self):
|
||||
if self.settings["true_north"] == []:
|
||||
if not self.settings["true_north"]:
|
||||
return
|
||||
for context in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if context.TrueNorth:
|
||||
@@ -108,6 +119,8 @@ class Usecase:
|
||||
context.TrueNorth = self.file.create_entity("IfcDirection")
|
||||
direction = context.TrueNorth
|
||||
if self.settings["true_north"] is None:
|
||||
# TODO: code will never be executed since None value
|
||||
# is substituted by an empty list
|
||||
context.TrueNorth = self.settings["true_north"]
|
||||
elif context.CoordinateSpaceDimension == 2:
|
||||
direction.DirectionRatios = self.settings["true_north"][0:2]
|
||||
|
||||
@@ -16,30 +16,29 @@
|
||||
# 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
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file):
|
||||
"""Remove georeferencing data
|
||||
|
||||
All georeferencing parameters such as projected CRS and map conversion
|
||||
data will be lost.
|
||||
def remove_georeferencing(file: ifcopenshell.file) -> None:
|
||||
"""Remove georeferencing data
|
||||
|
||||
:return: None
|
||||
:rtype: None
|
||||
All georeferencing parameters such as projected CRS and map conversion
|
||||
data will be lost.
|
||||
|
||||
Example:
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
# Let's change our mind
|
||||
ifcopenshell.api.run("georeference.remove_georeferencing", model)
|
||||
"""
|
||||
self.file = file
|
||||
Example:
|
||||
|
||||
def execute(self):
|
||||
map_conversion = self.file.by_type("IfcMapConversion")[0]
|
||||
projected_crs = self.file.by_type("IfcProjectedCRS")[0]
|
||||
if projected_crs.MapUnit and len(self.file.get_inverse(projected_crs.MapUnit)) == 1:
|
||||
# TODO: go deeper for conversion units
|
||||
self.file.remove(projected_crs.MapUnit)
|
||||
self.file.remove(projected_crs)
|
||||
self.file.remove(map_conversion)
|
||||
ifcopenshell.api.run("georeference.add_georeferencing", model)
|
||||
# Let's change our mind
|
||||
ifcopenshell.api.run("georeference.remove_georeferencing", model)
|
||||
"""
|
||||
|
||||
map_conversion = file.by_type("IfcMapConversion")[0]
|
||||
projected_crs = file.by_type("IfcProjectedCRS")[0]
|
||||
if projected_crs.MapUnit and len(file.get_inverse(projected_crs.MapUnit)) == 1:
|
||||
# TODO: go deeper for conversion units
|
||||
file.remove(projected_crs.MapUnit)
|
||||
file.remove(projected_crs)
|
||||
file.remove(map_conversion)
|
||||
|
||||
@@ -15,3 +15,24 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Manages grid and grid axes
|
||||
|
||||
A grid in IFC may contain two or more axes running in two or more directions.
|
||||
"""
|
||||
|
||||
from .. import wrap_usecases
|
||||
try:
|
||||
from .create_axis_curve import create_axis_curve
|
||||
except ModuleNotFoundError as e:
|
||||
print(f"Note: API not available due to missing dependencies: grid.create_axis_curve - {e}")
|
||||
from .create_grid_axis import create_grid_axis
|
||||
from .remove_grid_axis import remove_grid_axis
|
||||
|
||||
wrap_usecases(__path__, __name__)
|
||||
|
||||
__all__ = [
|
||||
"create_axis_curve",
|
||||
"create_grid_axis",
|
||||
"remove_grid_axis",
|
||||
]
|
||||
|
||||
@@ -16,52 +16,60 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import ifcopenshell.util.placement
|
||||
from mathutils import Matrix # For now, we depend on Blender
|
||||
import bpy.types
|
||||
|
||||
|
||||
def create_axis_curve(
|
||||
file: ifcopenshell.file, axis_curve: bpy.types.Object, grid_axis: ifcopenshell.entity_instance
|
||||
) -> 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
|
||||
: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)
|
||||
"""
|
||||
usecase = Usecase()
|
||||
usecase.file = file
|
||||
usecase.settings = {
|
||||
"axis_curve": axis_curve, # A Blender object
|
||||
"grid_axis": grid_axis,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
|
||||
class Usecase:
|
||||
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": axis_curve, # A Blender object
|
||||
"grid_axis": grid_axis,
|
||||
}
|
||||
|
||||
def execute(self):
|
||||
existing_curve = self.settings["grid_axis"].AxisCurve
|
||||
if existing_curve and len(self.file.get_inverse(existing_curve)) == 1:
|
||||
|
||||
@@ -15,71 +15,68 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
import ifcopenshell
|
||||
from typing import Optional, Literal
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, axis_tag=None, same_sense=None, uvw_axes=None, grid=None):
|
||||
"""Adds a new grid axis to a grid
|
||||
def create_grid_axis(
|
||||
file: ifcopenshell.file,
|
||||
grid: ifcopenshell.entity_instance,
|
||||
axis_tag: str = "A",
|
||||
same_sense: bool = True,
|
||||
uvw_axes: Literal["UAxes", "VAxes", "WAxes"] = "UAxes",
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""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).
|
||||
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 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 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.
|
||||
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
|
||||
: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
|
||||
:return: The newly created IfcGridAxis
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
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": axis_tag or "A",
|
||||
"same_sense": same_sense or True,
|
||||
"uvw_axes": uvw_axes or "UAxes", # Choose which axes
|
||||
"grid": grid,
|
||||
}
|
||||
# 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)
|
||||
"""
|
||||
|
||||
def execute(self):
|
||||
element = self.file.create_entity(
|
||||
"IfcGridAxis", **{"AxisTag": self.settings["axis_tag"], "SameSense": self.settings["same_sense"]}
|
||||
)
|
||||
axes = list(getattr(self.settings["grid"], self.settings["uvw_axes"]) or [])
|
||||
axes.append(element)
|
||||
setattr(self.settings["grid"], self.settings["uvw_axes"], axes)
|
||||
return element
|
||||
element = file.create_entity("IfcGridAxis", **{"AxisTag": axis_tag, "SameSense": same_sense})
|
||||
axes = list(getattr(grid, uvw_axes) or [])
|
||||
axes.append(element)
|
||||
setattr(grid, uvw_axes, axes)
|
||||
return element
|
||||
|
||||
@@ -19,36 +19,32 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, axis=None):
|
||||
"""Removes a grid axis from a grid
|
||||
def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance) -> 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
|
||||
:param axis: The IfcGridAxis you want to remove.
|
||||
:type axis: ifcopenshell.entity_instance
|
||||
:return: None
|
||||
:rtype: None
|
||||
|
||||
Example:
|
||||
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)
|
||||
# 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 remove it later
|
||||
axis_2 = ifcopenshell.api.run("grid.create_grid_axis", model,
|
||||
axis_tag="2", uvw_axes="VAxes", grid=grid)
|
||||
# Let's create a third so we can remove 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": axis}
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["axis"].AxisCurve)) == 1:
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["axis"].AxisCurve)
|
||||
self.file.remove(self.settings["axis"].AxisCurve)
|
||||
self.file.remove(self.settings["axis"])
|
||||
# Let's remove it!
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", model, axis=axis_2)
|
||||
"""
|
||||
axis_curve = axis.AxisCurve
|
||||
if len(file.get_inverse(axis_curve)) == 1:
|
||||
ifcopenshell.util.element.remove_deep(file, axis_curve)
|
||||
file.remove(axis_curve)
|
||||
file.remove(axis)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user