mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
Merge remote-tracking branch 'origin/v0.8.0' into datamodel-v1.0
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import math
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
# 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 typing import Literal, Union
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from typing import Union, Literal
|
||||
|
||||
PrimitiveType = Literal["entity", "string", "float", "integer", "boolean", "enum", "binary"]
|
||||
ComplexPrimitiveType = Literal["list", "array", "set"]
|
||||
|
||||
@@ -16,14 +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/>.
|
||||
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.system
|
||||
from typing import Union
|
||||
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def get_references(element: ifcopenshell.entity_instance, should_inherit=True) -> set[ifcopenshell.entity_instance]:
|
||||
"""Gets classification references associated with the element
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
#
|
||||
#
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def get_constraints(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]:
|
||||
"""
|
||||
|
||||
@@ -16,10 +16,12 @@
|
||||
# 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 lark
|
||||
import ifcopenshell
|
||||
from typing import Optional, Union, Literal, Any
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal, Optional, Union
|
||||
|
||||
import lark
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.element
|
||||
@@ -350,8 +352,7 @@ def get_cost_rate(
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark(
|
||||
"""start: formula
|
||||
l = lark.Lark("""start: formula
|
||||
formula: operand (operator operand)*
|
||||
operand: value | category "(" formula ")"
|
||||
value: NUMBER?
|
||||
@@ -388,8 +389,7 @@ class CostValueUnserialiser:
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
start = l.parse(formula)
|
||||
return self.get_formula(start.children[0])
|
||||
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
import numpy as np
|
||||
import ifcopenshell
|
||||
from typing import Any, Union
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
|
||||
|
||||
|
||||
@@ -16,12 +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/>.
|
||||
|
||||
import ifcopenshell
|
||||
import datetime
|
||||
import isodate
|
||||
from re import findall
|
||||
from typing import Any, Literal, Union, overload
|
||||
|
||||
import isodate
|
||||
from dateutil import parser
|
||||
from typing import Literal, Union, Any, overload
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def timedelta2duration(timedelta):
|
||||
|
||||
@@ -16,27 +16,30 @@
|
||||
# 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 copy
|
||||
import json
|
||||
from pathlib import Path
|
||||
import copy
|
||||
from typing import Optional, TypedDict, Union
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.schema
|
||||
from typing import Optional, Literal, Any, Union, TypedDict
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
try:
|
||||
import glob
|
||||
import warnings
|
||||
import requests
|
||||
import urllib.parse
|
||||
from markdown import markdown
|
||||
from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
|
||||
import zipfile
|
||||
from lxml import etree
|
||||
import re
|
||||
import shutil
|
||||
import urllib.parse
|
||||
import warnings
|
||||
import zipfile
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
|
||||
from lxml import etree
|
||||
from markdown import markdown
|
||||
except:
|
||||
pass # Only necessary if you're using it to generate the docs database
|
||||
|
||||
|
||||
@@ -16,14 +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 collections import namedtuple
|
||||
from collections.abc import Callable, Generator, Sequence
|
||||
from typing import Any, Literal, Optional, Union, overload
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
from typing import Any, Callable, Optional, Union, Literal, overload
|
||||
from collections.abc import Generator, Sequence
|
||||
from collections import deque, namedtuple
|
||||
|
||||
|
||||
MATERIAL_TYPE = Literal[
|
||||
"IfcMaterial",
|
||||
@@ -469,7 +469,7 @@ def get_properties(
|
||||
del data["HasProperties"]
|
||||
results[prop_name] = data
|
||||
if verbose:
|
||||
results[prop_name] = {"id": data["id"], "class": data["class"], "value": results[prop_name]}
|
||||
results[prop_name] = {"id": data["id"], "class": data["type"], "value": results[prop_name]}
|
||||
return results
|
||||
|
||||
|
||||
@@ -1234,7 +1234,9 @@ def get_controls(element: ifcopenshell.entity_instance) -> Generator[ifcopenshel
|
||||
yield rel.RelatingControl
|
||||
|
||||
|
||||
def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
def get_parent(
|
||||
element: ifcopenshell.entity_instance, ifc_class: Optional[str] = None
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""Get the parent in the spatial heirarchy
|
||||
|
||||
IFC features a spatial hierarchy tree of all objects. Each spatial element
|
||||
@@ -1251,6 +1253,8 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
|
||||
- Voiding: the opening voids another physical element, such as a hole in a wall
|
||||
|
||||
:param element: Any physical or spatial element in the tree
|
||||
:param ifc_class: Optionally filter the type of parent you're after. For
|
||||
example, you may be after the storey, not a space.
|
||||
:return: Its parent. This must exist for any valid file, or None if we've reached the IfcProject.
|
||||
|
||||
Example:
|
||||
@@ -1260,7 +1264,7 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
|
||||
element = file.by_type("IfcWall")[0]
|
||||
parent = ifcopenshell.util.element.get_parent(element)
|
||||
"""
|
||||
return (
|
||||
parent = (
|
||||
get_container(element, should_get_direct=True)
|
||||
or get_aggregate(element)
|
||||
or get_nest(element)
|
||||
@@ -1268,6 +1272,16 @@ def get_parent(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.enti
|
||||
or get_voided_element(element)
|
||||
)
|
||||
|
||||
if not ifc_class:
|
||||
return parent
|
||||
|
||||
while parent:
|
||||
if parent.is_a(ifc_class):
|
||||
return parent
|
||||
parent = get_parent(parent)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_filled_void(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
|
||||
"""If the element is filling a void, get the void
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import zipfile
|
||||
from typing import IO, Union, TypedDict
|
||||
from typing import IO, TypedDict, Union
|
||||
|
||||
from typing_extensions import NotRequired
|
||||
|
||||
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
# 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 typing import Literal
|
||||
|
||||
from typing_extensions import assert_never
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.attribute
|
||||
from typing import Literal
|
||||
from typing_extensions import assert_never
|
||||
|
||||
# COBie actually uses an exclusion list, but this inclusion list is equivalent.
|
||||
cobie_type_classes = [
|
||||
|
||||
@@ -18,22 +18,22 @@
|
||||
|
||||
RUN_FROM_DEV_REPO = False
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.attribute
|
||||
import glob
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from lxml import etree
|
||||
from itertools import chain
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
from lxml import etree
|
||||
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
|
||||
if not RUN_FROM_DEV_REPO:
|
||||
import zipfile
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
BASE_MODULE_PATH = Path(__file__).parent
|
||||
|
||||
|
||||
@@ -17,12 +17,14 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import math
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from typing import Any, NamedTuple, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
from typing import NamedTuple, Optional, Union
|
||||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
MatrixType = ifcopenshell.util.placement.MatrixType
|
||||
|
||||
@@ -266,6 +268,15 @@ def get_helmert_transformation_parameters(ifc_file: ifcopenshell.file) -> Option
|
||||
return HelmertTransformation(e, n, h, xaa, xao, scale, factor_x, factor_y, factor_z)
|
||||
|
||||
|
||||
def get_crs(ifc_file: ifcopenshell.file) -> dict[str, Any]:
|
||||
"""Get CRS information from an IFC file."""
|
||||
if ifc_file.schema == "IFC2X3":
|
||||
return ifcopenshell.util.element.get_pset(ifc_file.by_type("IfcProject")[0], "ePSet_ProjectedCRS")
|
||||
for context in ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
if operation := context.HasCoordinateOperation:
|
||||
return operation[0].TargetCRS.get_info()
|
||||
|
||||
|
||||
def auto_z2e(ifc_file: ifcopenshell.file, z: float, should_return_in_map_units: bool = True) -> float:
|
||||
"""Convert a Z coordinate to an elevation using model georeferencing data
|
||||
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try:
|
||||
from server import get_resource_path, resource_documentation_builder, process_markdown, R
|
||||
from server import (
|
||||
R,
|
||||
get_resource_path,
|
||||
process_markdown,
|
||||
resource_documentation_builder,
|
||||
)
|
||||
except ModuleNotFoundError as e:
|
||||
print(
|
||||
"ERROR. Failed to import `server.py`.\n"
|
||||
@@ -26,13 +31,15 @@ except ModuleNotFoundError as e:
|
||||
raise e
|
||||
|
||||
import itertools
|
||||
import operator
|
||||
import json
|
||||
import ifcopenshell
|
||||
import operator
|
||||
from collections import Counter
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import Any, Union
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
# Hacky modified functions from server.py to make parser work
|
||||
def get_definition_from_md(resource: str, mdc: str) -> str:
|
||||
|
||||
@@ -26,8 +26,8 @@ try:
|
||||
except ImportError:
|
||||
LARK_AVAILABLE = False
|
||||
|
||||
from typing import Callable, Union
|
||||
import re
|
||||
from typing import Union
|
||||
|
||||
if LARK_AVAILABLE:
|
||||
mvd_grammar = r"""
|
||||
@@ -51,9 +51,9 @@ if LARK_AVAILABLE:
|
||||
|
||||
value: /[A-Za-z0-9 _\.-]+/
|
||||
|
||||
other_keyword: /[^\[\]]+/
|
||||
|
||||
dynamic_option_word: /[^\[\]]+/
|
||||
other_keyword: /[^\[\]]+/
|
||||
|
||||
dynamic_option_word: /[^\[\]]+/
|
||||
|
||||
%import common.WS
|
||||
%ignore WS
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
# 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 collections.abc import Iterable
|
||||
from typing import Literal, Optional
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
import ifcopenshell
|
||||
from typing import Literal, Optional
|
||||
from collections.abc import Iterable
|
||||
|
||||
MatrixType = npt.NDArray[np.float64]
|
||||
"""`npt.NDArray[np.float64]`"""
|
||||
|
||||
@@ -16,15 +16,16 @@
|
||||
# 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 re
|
||||
import pathlib
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import Literal, NamedTuple, Optional, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.type
|
||||
from ifcopenshell import entity_instance
|
||||
from functools import lru_cache
|
||||
from typing import Optional, Literal, NamedTuple, Union
|
||||
from ifcopenshell.entity_instance import entity_instance
|
||||
|
||||
templates: dict[ifcopenshell.util.schema.IFC_SCHEMA, "PsetQto"] = {}
|
||||
|
||||
|
||||
@@ -16,15 +16,16 @@
|
||||
# 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 collections.abc import Generator, Sequence
|
||||
from typing import Literal, Optional, TypedDict, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.shape
|
||||
from typing import Optional, Union, TypedDict, Literal
|
||||
from collections.abc import Generator, Sequence
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.shape
|
||||
|
||||
CONTEXT_TYPE = Literal["Model", "Plan", "NotDefined"]
|
||||
REPRESENTATION_IDENTIFIER = Literal[
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.date
|
||||
from typing import Union, Any
|
||||
from typing import Any, Union
|
||||
|
||||
import ifcopenshell.util.cost
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
|
||||
PRODUCTIVITY_PSET_DATA = Union[dict[str, Any], None]
|
||||
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcConstructionResource.htm#Table-7.3.3.7.1.3.H
|
||||
|
||||
@@ -16,13 +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/>.
|
||||
|
||||
import os
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Any, Literal, Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
from typing import Union, Any, Literal
|
||||
import ifcopenshell.util.attribute
|
||||
|
||||
# This is highly experimental and incomplete, however, it may work for simple datasets.
|
||||
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
"predefined_type": "NOTDEFINED"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminal": [
|
||||
{
|
||||
"name": "Commercial Kitchen Hood"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminalType": [
|
||||
{
|
||||
"name": "Commercial Kitchen Hood"
|
||||
}
|
||||
],
|
||||
"IfcAirTerminalBox": [
|
||||
{
|
||||
"name": "VAV Box"
|
||||
@@ -51,6 +61,16 @@
|
||||
"predefined_type": "DISTRIBUTIONBOARD"
|
||||
}
|
||||
],
|
||||
"IfcFireSuppressionTerminal": [
|
||||
{
|
||||
"name": "Fire Extinguisher"
|
||||
}
|
||||
],
|
||||
"IfcFireSuppressionTerminalType": [
|
||||
{
|
||||
"name": "Fire Extinguisher"
|
||||
}
|
||||
],
|
||||
"IfcFurniture": [
|
||||
{
|
||||
"name": "Casework"
|
||||
|
||||
@@ -25,11 +25,11 @@ Things we do check:
|
||||
- class hierarchy
|
||||
"""
|
||||
|
||||
|
||||
import ast
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
from typing_extensions import assert_never
|
||||
|
||||
|
||||
@@ -57,11 +57,28 @@ def get_function_node_name(node: ast.FunctionDef) -> Union[SubnameType, None]:
|
||||
:return: Function node name as ``SubnameType`` or ``None``, if function wasn't processed and can be skipped.
|
||||
"""
|
||||
node_name = node.name
|
||||
if node_name.startswith("_") and node_name not in ("_is",):
|
||||
is_init = node_name == "__init__"
|
||||
|
||||
if node_name.startswith("_") and node_name not in ("_is",) and not is_init:
|
||||
return None
|
||||
arg_nodes = node.args.args
|
||||
defaults = [None] * (len(arg_nodes) - len(node.args.defaults)) + node.args.defaults
|
||||
args: list[str] = []
|
||||
for arg, default in zip(arg_nodes, defaults):
|
||||
if default is None:
|
||||
args.append(arg.arg)
|
||||
else:
|
||||
args.append(f"{arg.arg}={ast.unparse(default)}")
|
||||
|
||||
if arg := node.args.vararg:
|
||||
args.append(f"*{arg.arg}")
|
||||
|
||||
if arg := node.args.kwarg:
|
||||
args.append(f"**{arg.arg}")
|
||||
|
||||
# Skip non-informative constructors.
|
||||
if is_init and args == ["self"]:
|
||||
return None
|
||||
args = [a.arg for a in node.args.args]
|
||||
if node.args.vararg:
|
||||
args.append("*args")
|
||||
|
||||
node_name = f"def {node.name}"
|
||||
node_name = f"{node_name}({', '.join(args)}): ..."
|
||||
|
||||
@@ -17,16 +17,20 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Iterable
|
||||
from decimal import Decimal
|
||||
from types import EllipsisType
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import lark
|
||||
import numpy as np
|
||||
import ifcopenshell.api.pset
|
||||
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util
|
||||
import ifcopenshell.util.attribute
|
||||
import ifcopenshell.util.classification
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.fm
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.pset
|
||||
@@ -34,18 +38,8 @@ import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.unit
|
||||
from decimal import Decimal
|
||||
from typing import Optional, Any, Union
|
||||
from collections.abc import Iterable
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from types import EllipsisType
|
||||
else:
|
||||
EllipsisType = type(...)
|
||||
|
||||
|
||||
filter_elements_grammar = lark.Lark(
|
||||
"""start: filter_group
|
||||
filter_elements_grammar = lark.Lark("""start: filter_group
|
||||
filter_group: facet_list ("+" facet_list)*
|
||||
facet_list: facet ("," facet)*
|
||||
|
||||
@@ -116,11 +110,9 @@ filter_elements_grammar = lark.Lark(
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
get_element_grammar = lark.Lark(
|
||||
"""start: keys
|
||||
get_element_grammar = lark.Lark("""start: keys
|
||||
|
||||
keys: key ("." key)*
|
||||
key: quoted_string | regex_string | unquoted_string
|
||||
@@ -135,25 +127,37 @@ get_element_grammar = lark.Lark(
|
||||
WS: /[ \\t\\f\\r\\n]/+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
format_grammar = lark.Lark(
|
||||
"""start: function
|
||||
format_grammar = lark.Lark("""start: expression
|
||||
|
||||
function: round | number | int | format_length | lower | upper | title | concat | substr | ESCAPED_STRING | NUMBER
|
||||
?expression: add_sub
|
||||
?add_sub: mul_div
|
||||
| add_sub "+" mul_div -> add
|
||||
| add_sub "-" mul_div -> subtract
|
||||
?mul_div: function
|
||||
| mul_div "*" function -> multiply
|
||||
| mul_div "/" function -> divide
|
||||
|
||||
function: round | number | int | format_length | lower | upper | title | concat | substr | sort | reverse | join | variable | ESCAPED_STRING | SIGNED_NUMBER | "(" expression ")"
|
||||
|
||||
round: "round(" function "," NUMBER ")"
|
||||
number: "number(" function ["," ESCAPED_STRING ["," ESCAPED_STRING]] ")"
|
||||
int: "int(" function ")"
|
||||
variable: "{{" query_path "}}"
|
||||
query_path: /[^}]+/
|
||||
|
||||
round: "round(" expression "," NUMBER ")"
|
||||
number: "number(" expression ["," ESCAPED_STRING ["," ESCAPED_STRING]] ")"
|
||||
int: "int(" expression ")"
|
||||
format_length: metric_length | imperial_length
|
||||
metric_length: "metric_length(" function "," NUMBER "," NUMBER ")"
|
||||
imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING ["," boolean]] ")"
|
||||
lower: "lower(" function ")"
|
||||
upper: "upper(" function ")"
|
||||
title: "title(" function ")"
|
||||
concat: "concat(" function ("," function)* ")"
|
||||
substr: "substr(" function "," SIGNED_INT ["," SIGNED_INT] ")"
|
||||
metric_length: "metric_length(" expression "," NUMBER "," NUMBER ")"
|
||||
imperial_length: "imperial_length(" expression "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING ["," boolean]] ")"
|
||||
lower: "lower(" expression ")"
|
||||
upper: "upper(" expression ")"
|
||||
title: "title(" expression ")"
|
||||
concat: "concat(" expression ("," expression)* ")"
|
||||
substr: "substr(" expression "," SIGNED_INT ["," SIGNED_INT] ")"
|
||||
sort: "sort(" expression ")"
|
||||
reverse: "reverse(" expression ")"
|
||||
join: "join(" ESCAPED_STRING "," expression ")"
|
||||
boolean: TRUE | FALSE
|
||||
|
||||
TRUE: "true" | "True" | "TRUE"
|
||||
@@ -184,14 +188,82 @@ format_grammar = lark.Lark(
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
class FormatTransformer(lark.Transformer):
|
||||
def __init__(self, element=None):
|
||||
"""Initialize transformer with optional element for variable substitution"""
|
||||
super().__init__()
|
||||
self.element = element
|
||||
|
||||
def start(self, args):
|
||||
if isinstance(args[0], (list, tuple)):
|
||||
return ", ".join(args[0])
|
||||
return args[0]
|
||||
|
||||
def expression(self, args):
|
||||
return args[0]
|
||||
|
||||
def variable(self, args):
|
||||
"""Handle variable substitution like {{z}} or {{Pset_Wall.FireRating}}"""
|
||||
if self.element:
|
||||
try:
|
||||
return get_element_value(self.element, args[0])
|
||||
except:
|
||||
pass
|
||||
|
||||
def query_path(self, args):
|
||||
"""Extract the query path from variable"""
|
||||
return str(args[0]).strip()
|
||||
|
||||
def add(self, args):
|
||||
"""Handle addition operation"""
|
||||
left, right = args
|
||||
try:
|
||||
left_val = float(left) if left != "None" and left is not None else 0.0
|
||||
right_val = float(right) if right != "None" and right is not None else 0.0
|
||||
result = left_val + right_val
|
||||
# Return integer if result has no decimal part
|
||||
if result % 1 == 0:
|
||||
return str(int(result))
|
||||
return str(result)
|
||||
except (ValueError, TypeError):
|
||||
# If can't convert to numbers, concatenate as strings
|
||||
return str(left) + str(right)
|
||||
|
||||
def subtract(self, args):
|
||||
"""Handle subtraction operation"""
|
||||
left, right = args
|
||||
left_val = float(left) if left != "None" and left is not None else 0.0
|
||||
right_val = float(right) if right != "None" and right is not None else 0.0
|
||||
result = left_val - right_val
|
||||
if result % 1 == 0:
|
||||
return str(int(result))
|
||||
return str(result)
|
||||
|
||||
def multiply(self, args):
|
||||
"""Handle multiplication operation"""
|
||||
left, right = args
|
||||
left_val = float(left) if left != "None" and left is not None else 0.0
|
||||
right_val = float(right) if right != "None" and right is not None else 0.0
|
||||
result = left_val * right_val
|
||||
if result % 1 == 0:
|
||||
return str(int(result))
|
||||
return str(result)
|
||||
|
||||
def divide(self, args):
|
||||
"""Handle division operation"""
|
||||
left, right = args
|
||||
left_val = float(left) if left != "None" and left is not None else 0.0
|
||||
right_val = float(right) if right != "None" and right is not None else 1.0
|
||||
if right_val == 0:
|
||||
return "inf" # or raise an error, or return "0"
|
||||
result = left_val / right_val
|
||||
if result % 1 == 0:
|
||||
return str(int(result))
|
||||
return str(result)
|
||||
|
||||
def function(self, args):
|
||||
return args[0]
|
||||
|
||||
@@ -211,7 +283,7 @@ class FormatTransformer(lark.Transformer):
|
||||
return str(args[0]).title()
|
||||
|
||||
def concat(self, args):
|
||||
return "".join(args)
|
||||
return "".join(str(arg) for arg in args)
|
||||
|
||||
def substr(self, args):
|
||||
if len(args) == 3:
|
||||
@@ -221,6 +293,15 @@ class FormatTransformer(lark.Transformer):
|
||||
elif len(args) == 2:
|
||||
return str(args[0])[int(args[1]) :]
|
||||
|
||||
def sort(self, args):
|
||||
return sorted(args[0])
|
||||
|
||||
def reverse(self, args):
|
||||
return list(reversed(args[0]))
|
||||
|
||||
def join(self, args):
|
||||
return args[0].join(args[1])
|
||||
|
||||
def boolean(self, args):
|
||||
if not args:
|
||||
return True
|
||||
@@ -241,13 +322,14 @@ class FormatTransformer(lark.Transformer):
|
||||
return str(result)
|
||||
|
||||
def number(self, args):
|
||||
if isinstance(args[0], str):
|
||||
args[0] = float(args[0]) if "." in args[0] else int(args[0])
|
||||
arg_val = args[0]
|
||||
if isinstance(arg_val, str):
|
||||
arg_val = float(arg_val) if "." in arg_val else int(arg_val)
|
||||
if len(args) >= 3 and args[2]:
|
||||
return "{:,}".format(args[0]).replace(".", "*").replace(",", args[2]).replace("*", args[1])
|
||||
return "{:,}".format(arg_val).replace(".", "*").replace(",", args[2]).replace("*", args[1])
|
||||
elif len(args) >= 2 and args[1]:
|
||||
return "{}".format(args[0]).replace(".", args[1])
|
||||
return "{:,}".format(args[0])
|
||||
return "{}".format(arg_val).replace(".", args[1])
|
||||
return "{:,}".format(arg_val)
|
||||
|
||||
def format_length(self, args):
|
||||
return args[0]
|
||||
@@ -287,7 +369,8 @@ class FormatTransformer(lark.Transformer):
|
||||
)
|
||||
|
||||
def int(self, args: list[str]) -> str:
|
||||
return str(int(float(args[0])))
|
||||
value = 0.0 if args[0] == "None" else args[0] or 0.0
|
||||
return str(int(float(value)))
|
||||
|
||||
|
||||
class GetElementTransformer(lark.Transformer):
|
||||
@@ -313,8 +396,18 @@ class GetElementTransformer(lark.Transformer):
|
||||
return args[1:-1].replace("\\", "")
|
||||
|
||||
|
||||
def format(query: str) -> str:
|
||||
return FormatTransformer().transform(format_grammar.parse(query))
|
||||
def format(query: str, element: Optional[ifcopenshell.entity_instance] = None) -> str:
|
||||
"""Format a query string with optional element context for variable substitution.
|
||||
|
||||
:param query: Format query string (can include {{variable}} placeholders)
|
||||
:param element: Optional IFC element for variable substitution
|
||||
:return: Formatted string
|
||||
|
||||
Example:
|
||||
format("{{z}} / 2", element) # Substitutes element's z value
|
||||
format("imperial_length({{z}} / 2, 4)", element) # Uses z in calculation
|
||||
"""
|
||||
return FormatTransformer(element).transform(format_grammar.parse(query))
|
||||
|
||||
|
||||
def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any:
|
||||
@@ -347,13 +440,13 @@ def _get_element_value(element: ifcopenshell.entity_instance, keys: list[str]) -
|
||||
elif key == "container":
|
||||
value = ifcopenshell.util.element.get_container(value)
|
||||
elif key == "space":
|
||||
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace")
|
||||
value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSpace")
|
||||
elif key == "storey":
|
||||
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey")
|
||||
value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuildingStorey")
|
||||
elif key == "building":
|
||||
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding")
|
||||
value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcBuilding")
|
||||
elif key == "site":
|
||||
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite")
|
||||
value = ifcopenshell.util.element.get_parent(value, ifc_class="IfcSite")
|
||||
elif key == "parent":
|
||||
value = ifcopenshell.util.element.get_parent(value)
|
||||
elif key in ("types", "occurrences"):
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
from collections.abc import Generator
|
||||
from functools import cache
|
||||
from math import floor
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.element
|
||||
from math import floor
|
||||
from functools import cache
|
||||
from typing import Union, Literal, Optional
|
||||
from collections.abc import Generator
|
||||
|
||||
|
||||
DURATION_TYPE = Literal["ELAPSEDTIME", "WORKTIME", "NOTDEFINED"]
|
||||
RECURRENCE_TYPE = Literal[
|
||||
|
||||
@@ -16,26 +16,36 @@
|
||||
# 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 shapely
|
||||
import shapely.ops
|
||||
from __future__ import annotations
|
||||
|
||||
from math import cos, radians
|
||||
from typing import TYPE_CHECKING, Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
import shapely
|
||||
import shapely.ops
|
||||
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
from ifcopenshell.util.shape_builder import VectorType
|
||||
from math import radians, cos
|
||||
from ifcopenshell.geom import ShapeElementType
|
||||
from typing import Optional, Literal, Union
|
||||
|
||||
tol = 1e-6
|
||||
AXIS_LITERAL = Literal["X", "Y", "Z"]
|
||||
VECTOR_3D = tuple[float, float, float]
|
||||
if TYPE_CHECKING:
|
||||
|
||||
import ifcopenshell.ifcopenshell_wrapper as W
|
||||
from ifcopenshell.geom import ShapeElementType
|
||||
from ifcopenshell.util.shape_builder import VectorType
|
||||
|
||||
AXIS_LITERAL = Literal["X", "Y", "Z"]
|
||||
|
||||
VECTOR_3D = tuple[float, float, float]
|
||||
|
||||
# Used only for typing, but reused by `shape.py` users.
|
||||
MatrixType = npt.NDArray[np.float64]
|
||||
"""`npt.NDArray[np.float64]`"""
|
||||
|
||||
tol = 1e-6
|
||||
|
||||
# NOTE: See IfcGeomRepresentation.h for W.Triangulation buffer types.
|
||||
|
||||
# NOTE: For functions that return a single scalar ensure to use .item() to
|
||||
|
||||
@@ -17,20 +17,21 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
from collections.abc import Sequence
|
||||
from itertools import chain
|
||||
from math import atan, cos, degrees, pi, radians, sin, sqrt, tan
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
import collections
|
||||
import collections.abc
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.placement
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.unit
|
||||
from math import cos, sin, pi, tan, radians, degrees, atan, sqrt
|
||||
from typing import Union, Optional, Literal, Any, TYPE_CHECKING
|
||||
from collections.abc import Sequence
|
||||
from itertools import chain
|
||||
|
||||
PRECISION = 1.0e-5
|
||||
|
||||
@@ -39,7 +40,7 @@ if TYPE_CHECKING:
|
||||
# NOTE: mathutils is never used at runtime in ifcopenshell,
|
||||
# only for type checking to ensure methods are compatible with
|
||||
# Blender vectors.
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector # pyright: ignore[reportMissingImports] # ty:ignore[unresolved-import]
|
||||
|
||||
# Support both numpy arrays and python sequences as inputs.
|
||||
VectorType = Union[Sequence[float], Vector, np.ndarray]
|
||||
@@ -312,7 +313,7 @@ class ShapeBuilder:
|
||||
Generate an IfcIndexedPolyCurve based on the provided points.
|
||||
|
||||
:param points: List of 2d or 3d points
|
||||
:param closed: Whether polyline should be closed. Default is `False`
|
||||
:param closed: Whether polyline should be closed.
|
||||
:param position_offset: offset to be applied to all points
|
||||
:param arc_points: Indices of the middle points for arcs. For creating an arc segment,
|
||||
provide 3 points: `arc_start`, `arc_middle` and `arc_end` to `points` and add the `arc_middle`
|
||||
@@ -415,8 +416,9 @@ class ShapeBuilder:
|
||||
3 2
|
||||
0 1
|
||||
|
||||
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
||||
:param position: rectangle position, default to `None`.
|
||||
:param size: rectangle size, could be either 2d or 3d.
|
||||
Use 0 for one of 3d dimensions to create 2d rectangle in 3d space.
|
||||
:param position: rectangle position.
|
||||
if `position` not specified zero-vector will be used
|
||||
:return: list of rectangle coords
|
||||
"""
|
||||
@@ -441,9 +443,11 @@ class ShapeBuilder:
|
||||
"""
|
||||
Generate a rectangle polyline.
|
||||
|
||||
:param size: rectangle size, could be either 2d or 3d, defaults to `(1,1)`
|
||||
:param position: rectangle position, default to `None`.
|
||||
if `position` not specified zero-vector will be used
|
||||
:param size: rectangle.
|
||||
:param position: rectangle position.
|
||||
|
||||
See ``get_rectangle_coords`` for more information.
|
||||
|
||||
:return: IfcIndexedPolyCurve
|
||||
"""
|
||||
return self.polyline(self.get_rectangle_coords(size, position), closed=True)
|
||||
@@ -513,11 +517,18 @@ class ShapeBuilder:
|
||||
trim_points_mask: Sequence[int],
|
||||
position_offset: Optional[VectorType] = None,
|
||||
) -> np.ndarray:
|
||||
"""Handy way to get edge points of the ellipse like shape of a given radiuses.
|
||||
"""Get cardinal-point coordinates of an ellipse by index mask.
|
||||
|
||||
Mask points are numerated from 0 to 3 ccw starting from (x_axis_radius/2; 0).
|
||||
The four cardinal points are numbered 0–3 counter-clockwise starting from the
|
||||
positive X axis: 0 → ``(x, 0)``, 1 → ``(0, y)``, 2 → ``(-x, 0)``, 3 → ``(0, -y)``.
|
||||
|
||||
Example: mask (0, 1, 2, 3) will return points (x, 0), (0, y), (-x, 0), (0, -y)
|
||||
Example: mask ``(0, 1, 2, 3)`` returns all four points in order.
|
||||
|
||||
:param x_axis_radius: Radius (semi-axis length) along the X axis.
|
||||
:param y_axis_radius: Radius (semi-axis length) along the Y axis.
|
||||
:param trim_points_mask: Sequence of cardinal-point indices (0–3) to select.
|
||||
:param position_offset: Optional 2D offset added to all returned points.
|
||||
:return: Numpy array of the selected 2D points.
|
||||
"""
|
||||
points = np.array(
|
||||
(
|
||||
@@ -542,15 +553,23 @@ class ShapeBuilder:
|
||||
ref_x_direction: VectorType = (1.0, 0.0),
|
||||
trim_points_mask: Sequence[int] = (),
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
Ellipse trimming points should be specified in counter clockwise order.
|
||||
"""Create an IfcEllipse, optionally trimmed to an arc.
|
||||
|
||||
For example, if you need to get the part of the ellipse ABOVE y-axis, you need to use mask (0,2). Below y-axis - (2,0)
|
||||
If neither ``trim_points`` nor ``trim_points_mask`` is provided, a full IfcEllipse is returned.
|
||||
Trimming points must be given in counter-clockwise order. For example, to get the arc
|
||||
above the Y-axis use mask ``(0, 2)``; below the Y-axis use ``(2, 0)``.
|
||||
|
||||
For more information about trim_points_mask check builder.get_trim_points_from_mask
|
||||
A trimmed result (IfcTrimmedCurve) includes a closing segment between the trim points,
|
||||
making it suitable for use as a profile in :meth:`extrude`.
|
||||
|
||||
Notion: trimmed ellipse also contains polyline between trim points, meaning IfcTrimmedCurve could be used
|
||||
for further extrusion.
|
||||
:param x_axis_radius: Semi-axis length along the local X axis.
|
||||
:param y_axis_radius: Semi-axis length along the local Y axis.
|
||||
:param position: 2D centre of the ellipse.
|
||||
:param trim_points: Explicit pair of 2D trim points. Takes precedence over ``trim_points_mask``.
|
||||
:param ref_x_direction: Direction of the local X axis.
|
||||
:param trim_points_mask: Pair of cardinal-point indices (0–3) used when ``trim_points`` is empty.
|
||||
See :meth:`get_trim_points_from_mask` for index definitions.
|
||||
:return: IfcEllipse (untrimmed) or IfcTrimmedCurve (trimmed).
|
||||
"""
|
||||
ifc_position = self.create_axis2_placement_2d(position, ref_x_direction)
|
||||
ifc_ellipse = self.file.createIfcEllipse(
|
||||
@@ -681,6 +700,14 @@ class ShapeBuilder:
|
||||
pivot_point: VectorType = (0.0, 0.0),
|
||||
counter_clockwise: bool = False,
|
||||
) -> np.ndarray:
|
||||
"""Rotate a single 2D point around a pivot.
|
||||
|
||||
:param point_2d: The 2D point to rotate.
|
||||
:param angle: Rotation angle, in degrees. Defaults to 90.
|
||||
:param pivot_point: The point to rotate around.
|
||||
:param counter_clockwise: If True, rotate counter-clockwise. Defaults to clockwise.
|
||||
:return: Rotated 2D point as a numpy array.
|
||||
"""
|
||||
angle_rad = radians(angle) * (1 if counter_clockwise else -1)
|
||||
relative_point = np.array(point_2d) - pivot_point
|
||||
relative_point = np_rotation_matrix(angle_rad, 2) @ relative_point
|
||||
@@ -748,7 +775,16 @@ class ShapeBuilder:
|
||||
mirror_axes: VectorType = (1.0, 1.0),
|
||||
mirror_point: VectorType = (0.0, 0.0),
|
||||
) -> np.ndarray:
|
||||
"""mirror_axes - along which axes mirror will be applied"""
|
||||
"""Mirror a single 2D point across the specified axes.
|
||||
|
||||
:param point_2d: The 2D point to mirror.
|
||||
:param mirror_axes: Indicates which axes to mirror across. A positive value in a
|
||||
component means that axis is mirrored (negated relative to ``mirror_point``).
|
||||
Example: ``(1, 0)`` mirrors across the Y-axis (negates X only),
|
||||
``(1, 1)`` mirrors across both axes.
|
||||
:param mirror_point: Origin of the mirror operation.
|
||||
:return: Mirrored 2D point as a numpy array.
|
||||
"""
|
||||
mirror_axes: np.ndarray = np.where(np.array(mirror_axes) > 0, -1, 1)
|
||||
mirror_point: np.ndarray = np.array(mirror_point)
|
||||
relative_point = point_2d - mirror_point
|
||||
@@ -784,7 +820,7 @@ class ShapeBuilder:
|
||||
"""
|
||||
Create IfcAxis2Placement3D from numpy matrix.
|
||||
|
||||
:param matrix: 4x4 transformation matrix, defaults to `np.eye(4)`
|
||||
:param matrix: 4x4 transformation matrix, defaults to ``np.eye(4)``
|
||||
:return: IfcAxis2Placement3D
|
||||
"""
|
||||
if matrix is None:
|
||||
@@ -794,7 +830,13 @@ class ShapeBuilder:
|
||||
def create_axis2_placement_2d(
|
||||
self, position: VectorType = (0.0, 0.0), x_direction: Optional[VectorType] = None
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Create IfcAxis2Placement2D."""
|
||||
"""Create IfcAxis2Placement2D.
|
||||
|
||||
:param position: 2D origin of the placement.
|
||||
:param x_direction: Direction of the local X axis. If not provided, defaults to
|
||||
the global X axis ``(1, 0)``.
|
||||
:return: IfcAxis2Placement2D
|
||||
"""
|
||||
ref_direction = (
|
||||
self.file.create_entity("IfcDirection", ifc_safe_vector_type(x_direction)) if x_direction else None
|
||||
)
|
||||
@@ -965,8 +1007,8 @@ class ShapeBuilder:
|
||||
|
||||
def sphere(self, radius: float = 1.0, center: VectorType = (0.0, 0.0, 0.0)) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
:param radius: radius of the sphere, defaults to 1.0
|
||||
:param center: sphere position, defaults to `(0.0, 0.0, 0.0)`
|
||||
:param radius: radius of the sphere.
|
||||
:param center: sphere position.
|
||||
|
||||
:return: IfcSphere
|
||||
"""
|
||||
@@ -996,7 +1038,7 @@ class ShapeBuilder:
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""
|
||||
:param plane: The IfcPlane representing the half space.
|
||||
:param agreement_flag: False if +Z represents the void
|
||||
:param agreement_flag: If False (default), the plane normal points toward the **removed** material (the void). The kept region is on the opposite side from the normal.
|
||||
:return: IfcHalfSpaceSolid
|
||||
"""
|
||||
return self.file.createIfcHalfSpaceSolid(plane, AgreementFlag=agreement_flag)
|
||||
@@ -1049,7 +1091,14 @@ class ShapeBuilder:
|
||||
def create_swept_disk_solid(
|
||||
self, path_curve: ifcopenshell.entity_instance, radius: float
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Create IfcSweptDiskSolid from `path_curve` (must be 3D) and `radius`"""
|
||||
"""Create an IfcSweptDiskSolid — a circular cross-section swept along a 3D path.
|
||||
|
||||
Useful for modelling round pipes, conduits, and cables.
|
||||
|
||||
:param path_curve: A 3D curve entity defining the centreline path. Must have ``Dim == 3``.
|
||||
:param radius: Radius of the circular disk cross-section.
|
||||
:return: IfcSweptDiskSolid
|
||||
"""
|
||||
if path_curve.Dim != 3:
|
||||
raise Exception(
|
||||
f"Path curve for IfcSweptDiskSolid should be 3D to be valid, currently it has {path_curve.Dim} dimensions.\n"
|
||||
@@ -1067,10 +1116,22 @@ class ShapeBuilder:
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Create IFC representation for the specified context and items.
|
||||
|
||||
**All items must belong to the same geometry category.** IFC prohibits
|
||||
mixing incompatible item types in one representation (e.g.
|
||||
``IfcExtrudedAreaSolid`` with ``IfcBlock``, or solids with curves).
|
||||
When ``representation_type`` is omitted the type is inferred via
|
||||
:func:`ifcopenshell.util.representation.guess_type`; if the items are
|
||||
heterogeneous ``guess_type`` returns ``None`` and the representation is
|
||||
written with no ``RepresentationType``, which fails IFC validation.
|
||||
Avoid mixing swept-solid primitives (``IfcExtrudedAreaSolid``,
|
||||
``IfcRevolvedAreaSolid``) with CSG primitives (``IfcBlock``,
|
||||
``IfcSphere``, etc.) or any other category in a single call.
|
||||
|
||||
:param context: IfcGeometricRepresentationSubContext
|
||||
:param items: could be a list or single curve/IfcExtrudedAreaSolid
|
||||
:param representation_type: Explicitly specified RepresentationType, defaults to `None`.
|
||||
If not provided it will be guessed from the items types
|
||||
:param items: A single item or list of items, all of the same geometry
|
||||
category (e.g. all ``IfcExtrudedAreaSolid``, all ``IfcIndexedPolyCurve``)
|
||||
:param representation_type: Explicitly specified RepresentationType.
|
||||
If not provided it will be guessed from the items types.
|
||||
:return: IfcShapeRepresentation
|
||||
"""
|
||||
if not isinstance(items, collections.abc.Iterable):
|
||||
@@ -1092,18 +1153,26 @@ class ShapeBuilder:
|
||||
)
|
||||
|
||||
def deep_copy(self, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Create a deep copy of an IFC element and all its referenced entities.
|
||||
|
||||
:param element: The IFC entity to copy.
|
||||
:return: A new independent copy of the element.
|
||||
"""
|
||||
return ifcopenshell.util.element.copy_deep(self.file, element)
|
||||
|
||||
# UTILITIES
|
||||
def extrude_kwargs(self, axis: Literal["Y", "X", "Z"]) -> dict[str, tuple[float, float, float]]:
|
||||
"""Shortcut to get kwargs for `ShapeBuilder.extrude` to extrude by some axis.
|
||||
"""Shortcut to get kwargs for :meth:`extrude` to extrude along a principal axis.
|
||||
|
||||
It assumes you have 2D profile in:
|
||||
XZ plane for Y axis extrusion, \n
|
||||
YZ plane for X axis extrusion, \n
|
||||
XY plane for Z axis extrusion, \n
|
||||
Assumes the 2D profile lies in the plane perpendicular to the extrusion axis:
|
||||
XZ plane for Y-axis extrusion, YZ plane for X-axis extrusion, XY plane for Z-axis extrusion.
|
||||
|
||||
Extruding by X/Y using other kwargs might break ValidExtrusionDirection."""
|
||||
Extruding along X or Y with other kwargs may violate the IFC ValidExtrusionDirection constraint.
|
||||
|
||||
:param axis: The extrusion axis: ``'X'``, ``'Y'``, or ``'Z'``.
|
||||
:return: A dict with keys ``position_x_axis``, ``position_z_axis``, and ``extrusion_vector``
|
||||
suitable for passing as ``**kwargs`` to :meth:`extrude`.
|
||||
"""
|
||||
|
||||
if axis == "Y":
|
||||
return {
|
||||
@@ -1127,13 +1196,16 @@ class ShapeBuilder:
|
||||
def rotate_extrusion_kwargs_by_z(
|
||||
self, kwargs: dict[str, Any], angle: float, counter_clockwise: bool = False
|
||||
) -> dict[str, VectorType]:
|
||||
"""shortcut to rotate extrusion kwargs by z axis
|
||||
"""Rotate extrusion kwargs around the Z axis.
|
||||
|
||||
`kwargs` expected to have `position_x_axis` and `position_z_axis` keys
|
||||
A shortcut to rotate the ``position_x_axis`` and ``position_z_axis`` values returned by
|
||||
:meth:`extrude_kwargs` around the Z axis before passing them to :meth:`extrude`.
|
||||
|
||||
`angle` is a rotation value in radians
|
||||
|
||||
by default rotation is clockwise, to make it counter clockwise use `counter_clockwise` flag
|
||||
:param kwargs: A dict with ``position_x_axis`` and ``position_z_axis`` keys,
|
||||
as returned by :meth:`extrude_kwargs`. The original dict is not mutated.
|
||||
:param angle: Rotation angle, in radians.
|
||||
:param counter_clockwise: If True, rotate counter-clockwise. Defaults to clockwise.
|
||||
:return: A new dict with ``position_x_axis`` and ``position_z_axis`` rotated around Z.
|
||||
"""
|
||||
rot = np_rotation_matrix(-angle, 3, "Z")
|
||||
kwargs = kwargs.copy() # prevent mutation of original kwargs
|
||||
@@ -1142,7 +1214,11 @@ class ShapeBuilder:
|
||||
return kwargs
|
||||
|
||||
def get_polyline_coords(self, polyline: ifcopenshell.entity_instance) -> np.ndarray:
|
||||
"""polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`"""
|
||||
"""Extract the coordinate array from a polyline entity.
|
||||
|
||||
:param polyline: An ``IfcIndexedPolyCurve`` or ``IfcPolyline`` entity.
|
||||
:return: Numpy array of the polyline's point coordinates.
|
||||
"""
|
||||
coords = None
|
||||
if polyline.is_a("IfcIndexedPolyCurve"):
|
||||
coords = np.array(polyline.Points.CoordList)
|
||||
@@ -1153,7 +1229,12 @@ class ShapeBuilder:
|
||||
return coords
|
||||
|
||||
def set_polyline_coords(self, polyline: ifcopenshell.entity_instance, coords: SequenceOfVectors) -> None:
|
||||
"""polyline should be either `IfcIndexedPolyCurve` or `IfcPolyline`"""
|
||||
"""Update the coordinates of a polyline entity in-place.
|
||||
|
||||
:param polyline: An ``IfcIndexedPolyCurve`` or ``IfcPolyline`` entity.
|
||||
:param coords: New sequence of point coordinates. Must contain the same number of
|
||||
points as the original polyline.
|
||||
"""
|
||||
if polyline.is_a("IfcIndexedPolyCurve"):
|
||||
polyline.Points.CoordList = ifc_safe_vector_type(coords)
|
||||
elif polyline.is_a("IfcPolyline"):
|
||||
@@ -1180,8 +1261,8 @@ class ShapeBuilder:
|
||||
:param fillets: list of points from `coords` to base fillet on. Example: (1,)
|
||||
:param fillet_radius: list of fillet radius for each of corresponding point form `fillets`.
|
||||
Example: (5.,) Note: `fillet_radius` could be just 1 float value if it's the same for all fillets.
|
||||
:param closed: boolean whether curve should be closed (whether last point connected to first one). Default: True
|
||||
:param create_ifc_curve: create IfcIndexedPolyCurve or just return the data. Default: False
|
||||
:param closed: boolean whether curve should be closed (whether last point connected to first one).
|
||||
:param create_ifc_curve: create IfcIndexedPolyCurve or just return the data.
|
||||
|
||||
:return: (points, segments, ifc_curve) for the created simple curve
|
||||
if both points in e are equally far from pt, then v1 is returned.
|
||||
@@ -1292,6 +1373,18 @@ class ShapeBuilder:
|
||||
WallThickness: float,
|
||||
FilletRadius: float,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Create a Z-profile (cold-formed steel section) outline curve with lips and fillets.
|
||||
|
||||
All dimensions are in the IFC project's length units.
|
||||
|
||||
:param FirstFlangeWidth: Width of the first (top) flange, measured from the web centreline.
|
||||
:param SecondFlangeWidth: Width of the second (bottom) flange, measured from the web centreline.
|
||||
:param Depth: Total depth of the section (web height).
|
||||
:param Girth: Length of the return lips on each flange.
|
||||
:param WallThickness: Uniform material thickness.
|
||||
:param FilletRadius: Inner bend radius at each corner.
|
||||
:return: IfcIndexedPolyCurve representing the closed Z-profile outline.
|
||||
"""
|
||||
x1 = FirstFlangeWidth
|
||||
x2 = SecondFlangeWidth
|
||||
y = Depth / 2
|
||||
@@ -1333,10 +1426,17 @@ class ShapeBuilder:
|
||||
def create_transition_arc_ifc(
|
||||
self, width: float, height: float, create_ifc_curve: bool = False
|
||||
) -> tuple[SequenceOfVectors, list[list[int]], Union[ifcopenshell.entity_instance, None]]:
|
||||
"""Create an arc in the rectangle with specified width and height.
|
||||
"""Create an arc fitting inside a rectangle of the given width and height.
|
||||
|
||||
If it's not possible to make a complete arc, create an arc with longest radius possible
|
||||
and straight segment in the middle.
|
||||
If a single arc cannot span the full width, the longest possible radius is used and
|
||||
a straight segment is inserted in the middle.
|
||||
|
||||
:param width: Width of the bounding rectangle.
|
||||
:param height: Height of the bounding rectangle (also the maximum arc radius).
|
||||
:param create_ifc_curve: If True, also create and return an ``IfcIndexedPolyCurve``.
|
||||
If False, only return the raw point and segment data.
|
||||
:return: A tuple ``(points, segments, ifc_curve)`` where ``ifc_curve`` is an
|
||||
``IfcIndexedPolyCurve`` when ``create_ifc_curve=True``, otherwise ``None``.
|
||||
"""
|
||||
fillet_size = (width / 2) / height
|
||||
if fillet_size <= 1:
|
||||
@@ -1366,6 +1466,14 @@ class ShapeBuilder:
|
||||
return points, segments, transition_arc
|
||||
|
||||
def mesh(self, points: SequenceOfVectors, faces: Sequence[Sequence[int]]) -> ifcopenshell.entity_instance:
|
||||
"""Create a tessellated mesh from points and face indices.
|
||||
|
||||
Delegates to :meth:`faceted_brep` for IFC2X3, or :meth:`polygonal_face_set` for IFC4 and later.
|
||||
|
||||
:param points: List of 3D coordinates.
|
||||
:param faces: List of faces, each face a sequence of zero-based point indices.
|
||||
:return: IfcFacetedBrep (IFC2X3) or IfcPolygonalFaceSet (IFC4+).
|
||||
"""
|
||||
if self.file.schema == "IFC2X3":
|
||||
return self.faceted_brep(points, faces)
|
||||
return self.polygonal_face_set(points, faces)
|
||||
@@ -1460,10 +1568,10 @@ class ShapeBuilder:
|
||||
|
||||
:param points: list of points, assuming they form consecutive closed polyline.
|
||||
:param magnitude: extrusion magnitude
|
||||
:param extrusion_vector: extrusion direction, by default it's extruding by Z+ axis
|
||||
:param extrusion_vector: extrusion direction.
|
||||
:param offset: offset from the points
|
||||
:param start_cap: if True, create start cap, by default it's True
|
||||
:param end_cap: if True, create end cap, by default it's True
|
||||
:param start_cap: if True, create start cap.
|
||||
:param end_cap: if True, create end cap.
|
||||
:return: IfcPolygonalFaceSet
|
||||
"""
|
||||
|
||||
@@ -1719,11 +1827,20 @@ class ShapeBuilder:
|
||||
angle: float,
|
||||
profile_offset: VectorType = (0.0, 0.0),
|
||||
verbose: bool = True,
|
||||
):
|
||||
"""get the final transition length for two profiles dimensions, angle and XY offset between them,
|
||||
) -> Optional[float]:
|
||||
"""Get the transition length for two profile half-dimensions, an angle, and an XY offset.
|
||||
|
||||
the difference from `calculate_transition` - `get_transition_length` is making sure
|
||||
that length will fit both sides of the transition
|
||||
Unlike :meth:`mep_transition_calculate`, this method checks that the resulting length
|
||||
satisfies the angle constraint from both the start and end profile perspectives.
|
||||
|
||||
:param start_half_dim: Half-dimensions of the start profile as a 3-element array
|
||||
``[half_x, half_y, depth]``. For circular profiles ``half_x == half_y == radius``.
|
||||
:param end_half_dim: Half-dimensions of the end profile in the same format.
|
||||
:param angle: Maximum allowed transition angle, in degrees.
|
||||
:param profile_offset: 2D XY offset between the centrelines of the start and end profiles.
|
||||
:param verbose: If True, print diagnostic values during calculation.
|
||||
:return: Transition length in project length units, or ``None`` if no valid length exists
|
||||
for the given angle and offset.
|
||||
"""
|
||||
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
|
||||
np_X, np_Y = 0, 1
|
||||
@@ -1784,9 +1901,23 @@ class ShapeBuilder:
|
||||
angle: Optional[float] = None,
|
||||
verbose: bool = True,
|
||||
) -> Union[float, None]:
|
||||
"""will return transition length based on the profile dimension differences and offset.
|
||||
"""Calculate MEP transition length from angle, or transition angle from length.
|
||||
|
||||
If `length` is provided will return transition angle"""
|
||||
Low-level calculation kernel used by :meth:`mep_transition_length`. Provide either
|
||||
``angle`` or ``length`` (not both); the other value is computed and returned.
|
||||
|
||||
:param start_half_dim: Half-dimensions of the start profile ``[half_x, half_y, depth]``.
|
||||
:param end_half_dim: Half-dimensions of the end profile ``[half_x, half_y, depth]``.
|
||||
:param offset: 2D XY offset between profile centrelines.
|
||||
:param diff: Pre-computed absolute difference of start and end half-dimensions (XY only).
|
||||
Computed from ``start_half_dim`` and ``end_half_dim`` if not provided.
|
||||
:param end_profile: If True, swap X and Y axes to compute from the end-profile perspective.
|
||||
:param length: Known transition length. If provided, the corresponding angle is returned.
|
||||
:param angle: Known transition angle, in degrees. If provided, the corresponding length is returned.
|
||||
:param verbose: If True, print diagnostic values during calculation.
|
||||
:return: Transition length (if ``angle`` was given) or transition angle in degrees
|
||||
(if ``length`` was given), or ``None`` if the geometry is not feasible.
|
||||
"""
|
||||
|
||||
print = lambda *args, **kwargs: __builtins__["print"](*args, **kwargs) if verbose else None
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ifcopenshell.util.system
|
||||
from typing import Optional, Union, Literal
|
||||
|
||||
group_types: dict[str, tuple[str, ...]] = {
|
||||
"IfcZone": ("IfcZone", "IfcSpace", "IfcSpatialZone"),
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
# 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 os
|
||||
import json
|
||||
import os
|
||||
|
||||
import ifcopenshell.util.schema
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
# 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 collections.abc import Generator
|
||||
from fractions import Fraction
|
||||
from math import pi
|
||||
from typing import Literal, Optional, Union
|
||||
from collections.abc import Generator
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.api.unit
|
||||
|
||||
prefixes = {
|
||||
"EXA": 1e18,
|
||||
@@ -209,6 +208,7 @@ si_conversions = {
|
||||
"pound": 0.454,
|
||||
"ton UK": 1016.0469088,
|
||||
"ton US": 907.18474,
|
||||
"tonne": 1000.0,
|
||||
"lbf": 4.4482216153,
|
||||
"kip": 4448.2216153,
|
||||
"psi": 6894.7572932,
|
||||
@@ -253,6 +253,7 @@ imperial_types = {
|
||||
"pound": "MASSUNIT",
|
||||
"ton UK": "MASSUNIT",
|
||||
"ton US": "MASSUNIT",
|
||||
"tonne": "MASSUNIT",
|
||||
"lbf": "FORCEUNIT",
|
||||
"kip": "FORCEUNIT",
|
||||
"psi": "PRESSUREUNIT",
|
||||
@@ -323,6 +324,7 @@ unit_symbols = {
|
||||
"pound": "lb",
|
||||
"ton UK": "ton",
|
||||
"ton US": "ton",
|
||||
"tonne": "t",
|
||||
"lbf": "lbf",
|
||||
"kip": "kip",
|
||||
"psi": "psi",
|
||||
@@ -866,10 +868,10 @@ def iter_element_and_attributes_per_type(ifc_file: ifcopenshell.file, attr_type_
|
||||
|
||||
def convert_file_length_units(ifc_file: ifcopenshell.file, target_units: str = "METER") -> ifcopenshell.file:
|
||||
"""Converts all units in an IFC file to the specified target units. Returns a new file."""
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
import ifcopenshell.api.georeference
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.geolocation
|
||||
|
||||
prefix = get_prefix(target_units)
|
||||
si_unit = get_unit_name(target_units)
|
||||
|
||||
Reference in New Issue
Block a user