2022-01-19 12:18:33 +11:00
|
|
|
# IfcOpenShell - IFC toolkit and geometry engine
|
|
|
|
|
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
|
|
|
|
#
|
|
|
|
|
# This file is part of IfcOpenShell.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is free software: you can redistribute it and/or modify
|
|
|
|
|
# it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
# (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# IfcOpenShell is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU Lesser General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU Lesser General Public License
|
|
|
|
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2023-03-09 11:07:13 +11:00
|
|
|
import re
|
|
|
|
|
import lark
|
2024-03-19 17:01:50 +05:00
|
|
|
import numpy as np
|
2024-03-18 16:37:43 +05:00
|
|
|
import ifcopenshell.api
|
2020-07-14 19:27:56 +10:00
|
|
|
import ifcopenshell.util
|
2024-05-17 15:19:22 +05:00
|
|
|
import ifcopenshell.util.attribute
|
2021-07-23 16:10:38 +10:00
|
|
|
import ifcopenshell.util.fm
|
2023-08-26 15:15:19 +10:00
|
|
|
import ifcopenshell.util.unit
|
2020-07-14 19:27:56 +10:00
|
|
|
import ifcopenshell.util.element
|
2023-09-07 13:47:54 +10:00
|
|
|
import ifcopenshell.util.placement
|
|
|
|
|
import ifcopenshell.util.geolocation
|
2023-07-20 19:22:15 +10:00
|
|
|
import ifcopenshell.util.classification
|
2024-03-18 16:09:35 +05:00
|
|
|
import ifcopenshell.util.schema
|
2024-03-21 14:17:02 +05:00
|
|
|
import ifcopenshell.util.shape
|
2023-09-08 12:20:52 +10:00
|
|
|
from decimal import Decimal
|
2024-05-17 15:19:22 +05:00
|
|
|
from typing import Optional, Any, Union, Iterable
|
2020-05-17 18:32:56 +10:00
|
|
|
|
2022-07-19 14:51:48 +02:00
|
|
|
|
2023-08-17 23:33:29 +10:00
|
|
|
filter_elements_grammar = lark.Lark(
|
|
|
|
|
"""start: filter_group
|
2023-07-22 22:49:22 +10:00
|
|
|
filter_group: facet_list ("+" facet_list)*
|
|
|
|
|
facet_list: facet ("," facet)*
|
|
|
|
|
|
2024-06-01 15:10:45 +10:00
|
|
|
facet: instance | entity | attribute | type | material | query | classification | location | property | group | parent
|
2023-07-22 22:49:22 +10:00
|
|
|
|
|
|
|
|
instance: not? globalid
|
|
|
|
|
globalid: /[0-3][a-zA-Z0-9_$]{21}/
|
|
|
|
|
entity: not? ifc_class
|
|
|
|
|
attribute: attribute_name comparison value
|
|
|
|
|
type: "type" comparison value
|
|
|
|
|
material: "material" comparison value
|
|
|
|
|
property: pset "." prop comparison value
|
|
|
|
|
classification: "classification" comparison value
|
|
|
|
|
location: "location" comparison value
|
2024-01-09 16:44:40 +11:00
|
|
|
group: "group" comparison value
|
2024-06-01 15:10:45 +10:00
|
|
|
parent: "parent" comparison value
|
2023-08-21 23:44:20 +10:00
|
|
|
query: "query:" keys comparison value
|
2023-07-22 22:49:22 +10:00
|
|
|
|
2023-08-25 17:15:21 +10:00
|
|
|
pset: quoted_string | regex_string | unquoted_string
|
|
|
|
|
prop: quoted_string | regex_string | unquoted_string
|
2023-08-21 23:44:20 +10:00
|
|
|
keys: quoted_string | unquoted_string
|
2023-07-22 22:49:22 +10:00
|
|
|
|
|
|
|
|
attribute_name: /[A-Z]\\w+/
|
|
|
|
|
ifc_class: /Ifc\\w+/
|
|
|
|
|
|
2023-08-25 17:15:21 +10:00
|
|
|
value: special | quoted_string | regex_string | unquoted_string
|
2024-01-13 15:58:57 +11:00
|
|
|
unquoted_string: /[^,.=><*!\\s]+/
|
2023-07-22 22:49:22 +10:00
|
|
|
regex_string: "/" /[^\\/]+/ "/"
|
2023-08-26 16:42:18 +10:00
|
|
|
quoted_string: ESCAPED_STRING
|
2023-07-22 22:49:22 +10:00
|
|
|
|
|
|
|
|
special: null | true | false
|
|
|
|
|
|
2024-01-13 15:58:57 +11:00
|
|
|
comparison: not? equals | morethanequalto | lessthanequalto | morethan | lessthan | not? contains
|
2023-07-22 22:49:22 +10:00
|
|
|
not: "!"
|
|
|
|
|
equals: "="
|
2023-12-28 22:03:00 -03:00
|
|
|
morethanequalto: ">="
|
|
|
|
|
lessthanequalto: "<="
|
|
|
|
|
morethan: ">"
|
|
|
|
|
lessthan: "<"
|
2024-01-13 15:58:57 +11:00
|
|
|
contains: "*="
|
2023-07-22 22:49:22 +10:00
|
|
|
null: "NULL"
|
|
|
|
|
true: "TRUE"
|
|
|
|
|
false: "FALSE"
|
|
|
|
|
|
|
|
|
|
// Embed common.lark for packaging
|
|
|
|
|
DIGIT: "0".."9"
|
|
|
|
|
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
|
|
|
|
INT: DIGIT+
|
|
|
|
|
SIGNED_INT: ["+"|"-"] INT
|
|
|
|
|
DECIMAL: INT "." INT? | "." INT
|
|
|
|
|
_EXP: ("e"|"E") SIGNED_INT
|
|
|
|
|
FLOAT: INT _EXP | DECIMAL _EXP?
|
|
|
|
|
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
|
|
|
|
NUMBER: FLOAT | INT
|
|
|
|
|
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
|
|
|
|
_STRING_INNER: /.*?/
|
|
|
|
|
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
|
|
|
|
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
|
|
|
|
LCASE_LETTER: "a".."z"
|
|
|
|
|
UCASE_LETTER: "A".."Z"
|
|
|
|
|
LETTER: UCASE_LETTER | LCASE_LETTER
|
|
|
|
|
WORD: LETTER+
|
|
|
|
|
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
|
|
|
|
WS_INLINE: (" "|/\\t/)+
|
|
|
|
|
WS: /[ \\t\\f\\r\\n]/+
|
|
|
|
|
CR : /\\r/
|
|
|
|
|
LF : /\\n/
|
|
|
|
|
NEWLINE: (CR? LF)+
|
|
|
|
|
|
|
|
|
|
%ignore WS // Disregard spaces in text
|
2023-08-17 23:33:29 +10:00
|
|
|
"""
|
|
|
|
|
)
|
2023-07-23 15:20:14 +10:00
|
|
|
|
2023-08-17 23:33:29 +10:00
|
|
|
get_element_grammar = lark.Lark(
|
2023-08-26 16:42:18 +10:00
|
|
|
"""start: keys
|
|
|
|
|
|
|
|
|
|
keys: key ("." key)*
|
|
|
|
|
key: quoted_string | regex_string | unquoted_string
|
|
|
|
|
unquoted_string: /[^.=\\/\\s]+/
|
|
|
|
|
regex_string: "/" /[^\\/]+/ "/"
|
|
|
|
|
quoted_string: ESCAPED_STRING
|
2023-07-23 15:20:14 +10:00
|
|
|
|
|
|
|
|
// Embed common.lark for packaging
|
|
|
|
|
_STRING_INNER: /.*?/
|
|
|
|
|
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
|
|
|
|
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
|
|
|
|
WS: /[ \\t\\f\\r\\n]/+
|
|
|
|
|
|
|
|
|
|
%ignore WS // Disregard spaces in text
|
2023-08-17 23:33:29 +10:00
|
|
|
"""
|
|
|
|
|
)
|
2023-07-22 22:49:22 +10:00
|
|
|
|
2023-08-26 15:15:19 +10:00
|
|
|
format_grammar = lark.Lark(
|
|
|
|
|
"""start: function
|
|
|
|
|
|
2024-04-10 17:32:39 +10:00
|
|
|
function: round | number | format_length | lower | upper | title | concat | substr | ESCAPED_STRING | NUMBER
|
2023-08-26 15:15:19 +10:00
|
|
|
|
|
|
|
|
round: "round(" function "," NUMBER ")"
|
2024-04-10 17:32:39 +10:00
|
|
|
number: "number(" function ["," ESCAPED_STRING ["," ESCAPED_STRING]] ")"
|
2023-08-26 15:15:19 +10:00
|
|
|
format_length: metric_length | imperial_length
|
|
|
|
|
metric_length: "metric_length(" function "," NUMBER "," NUMBER ")"
|
2023-09-07 12:18:32 +10:00
|
|
|
imperial_length: "imperial_length(" function "," NUMBER ["," ESCAPED_STRING "," ESCAPED_STRING] ")"
|
2023-08-26 15:15:19 +10:00
|
|
|
lower: "lower(" function ")"
|
|
|
|
|
upper: "upper(" function ")"
|
|
|
|
|
title: "title(" function ")"
|
|
|
|
|
concat: "concat(" function ("," function)* ")"
|
2023-09-12 17:47:35 +10:00
|
|
|
substr: "substr(" function "," SIGNED_INT ["," SIGNED_INT] ")"
|
2023-08-26 15:15:19 +10:00
|
|
|
|
|
|
|
|
// Embed common.lark for packaging
|
|
|
|
|
DIGIT: "0".."9"
|
|
|
|
|
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
|
|
|
|
INT: DIGIT+
|
|
|
|
|
SIGNED_INT: ["+"|"-"] INT
|
|
|
|
|
DECIMAL: INT "." INT? | "." INT
|
|
|
|
|
_EXP: ("e"|"E") SIGNED_INT
|
|
|
|
|
FLOAT: INT _EXP | DECIMAL _EXP?
|
|
|
|
|
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
|
|
|
|
NUMBER: FLOAT | INT
|
|
|
|
|
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
|
|
|
|
_STRING_INNER: /.*?/
|
|
|
|
|
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
|
|
|
|
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
|
|
|
|
LCASE_LETTER: "a".."z"
|
|
|
|
|
UCASE_LETTER: "A".."Z"
|
|
|
|
|
LETTER: UCASE_LETTER | LCASE_LETTER
|
|
|
|
|
WORD: LETTER+
|
|
|
|
|
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
|
|
|
|
WS_INLINE: (" "|/\\t/)+
|
|
|
|
|
WS: /[ \\t\\f\\r\\n]/+
|
|
|
|
|
CR : /\\r/
|
|
|
|
|
LF : /\\n/
|
|
|
|
|
NEWLINE: (CR? LF)+
|
|
|
|
|
|
|
|
|
|
%ignore WS // Disregard spaces in text
|
|
|
|
|
"""
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FormatTransformer(lark.Transformer):
|
|
|
|
|
def start(self, args):
|
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
|
|
def function(self, args):
|
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
|
|
def ESCAPED_STRING(self, args):
|
|
|
|
|
return args[1:-1].replace("\\", "")
|
|
|
|
|
|
|
|
|
|
def NUMBER(self, args):
|
|
|
|
|
return str(args)
|
|
|
|
|
|
|
|
|
|
def lower(self, args):
|
|
|
|
|
return str(args[0]).lower()
|
|
|
|
|
|
|
|
|
|
def upper(self, args):
|
|
|
|
|
return str(args[0]).upper()
|
|
|
|
|
|
|
|
|
|
def title(self, args):
|
|
|
|
|
return str(args[0]).title()
|
|
|
|
|
|
|
|
|
|
def concat(self, args):
|
|
|
|
|
return "".join(args)
|
|
|
|
|
|
2023-09-07 13:02:45 +10:00
|
|
|
def substr(self, args):
|
2023-09-12 17:47:35 +10:00
|
|
|
if len(args) == 3:
|
|
|
|
|
if args[2] is None:
|
|
|
|
|
return str(args[0])[int(args[1]) :]
|
|
|
|
|
return str(args[0])[int(args[1]) : int(args[2])]
|
|
|
|
|
elif len(args) == 2:
|
|
|
|
|
return str(args[0])[int(args[1]) :]
|
2023-09-07 13:02:45 +10:00
|
|
|
|
2023-08-26 15:15:19 +10:00
|
|
|
def round(self, args):
|
2024-01-10 14:42:14 +11:00
|
|
|
value = Decimal(0.0 if args[0] == "None" else args[0] or 0.0)
|
2023-09-08 12:20:52 +10:00
|
|
|
nearest = Decimal(args[1])
|
|
|
|
|
result = round(value / nearest) * nearest
|
2023-09-08 11:15:01 +10:00
|
|
|
if nearest % 1 == 0:
|
|
|
|
|
return str(int(result))
|
|
|
|
|
return str(result)
|
2023-08-26 15:15:19 +10:00
|
|
|
|
2024-04-10 17:32:39 +10:00
|
|
|
def number(self, args):
|
|
|
|
|
if isinstance(args[0], str):
|
|
|
|
|
args[0] = float(args[0]) if "." in args[0] else int(args[0])
|
|
|
|
|
if len(args) >= 3 and args[2]:
|
|
|
|
|
return "{:,}".format(args[0]).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])
|
|
|
|
|
|
2023-08-26 15:15:19 +10:00
|
|
|
def format_length(self, args):
|
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
|
|
def metric_length(self, args):
|
|
|
|
|
value, precision, decimal_places = args
|
|
|
|
|
return ifcopenshell.util.unit.format_length(
|
|
|
|
|
float(value), float(precision), int(decimal_places), unit_system="metric"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def imperial_length(self, args):
|
|
|
|
|
if len(args) == 2:
|
2024-04-19 16:18:46 +05:00
|
|
|
input_unit, output_unit = "foot", "foot"
|
2023-08-26 15:15:19 +10:00
|
|
|
value, precision = args
|
|
|
|
|
else:
|
2023-09-07 12:18:32 +10:00
|
|
|
value, precision, input_unit, output_unit = args
|
|
|
|
|
input_unit = "inch" if input_unit == "inch" else "foot"
|
|
|
|
|
output_unit = "inch" if output_unit == "inch" else "foot"
|
2023-08-26 15:15:19 +10:00
|
|
|
|
|
|
|
|
return ifcopenshell.util.unit.format_length(
|
2023-09-07 12:18:32 +10:00
|
|
|
float(value), int(precision), unit_system="imperial", input_unit=input_unit, output_unit=output_unit
|
2023-08-26 15:15:19 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2023-08-26 16:42:18 +10:00
|
|
|
class GetElementTransformer(lark.Transformer):
|
|
|
|
|
def start(self, args):
|
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
|
|
def keys(self, args):
|
|
|
|
|
return args
|
|
|
|
|
|
|
|
|
|
def key(self, args):
|
|
|
|
|
return args[0]
|
|
|
|
|
|
|
|
|
|
def quoted_string(self, args):
|
|
|
|
|
return str(args[0])
|
|
|
|
|
|
|
|
|
|
def regex_string(self, args):
|
|
|
|
|
return re.compile(args[0])
|
|
|
|
|
|
|
|
|
|
def unquoted_string(self, args):
|
|
|
|
|
return str(args[0])
|
|
|
|
|
|
|
|
|
|
def ESCAPED_STRING(self, args):
|
|
|
|
|
return args[1:-1].replace("\\", "")
|
|
|
|
|
|
|
|
|
|
|
2024-04-05 14:06:10 +05:00
|
|
|
def format(query: str) -> str:
|
2023-08-26 15:15:19 +10:00
|
|
|
return FormatTransformer().transform(format_grammar.parse(query))
|
|
|
|
|
|
2023-07-22 22:49:22 +10:00
|
|
|
|
2024-03-18 16:09:35 +05:00
|
|
|
def get_element_value(element: ifcopenshell.entity_instance, query: str) -> Any:
|
|
|
|
|
keys: list[str] = GetElementTransformer().transform(get_element_grammar.parse(query))
|
2023-08-26 16:42:18 +10:00
|
|
|
return Selector.get_element_value(element, keys)
|
2023-03-13 11:09:56 +11:00
|
|
|
|
|
|
|
|
|
2024-03-13 15:21:46 +05:00
|
|
|
def filter_elements(
|
|
|
|
|
ifc_file: ifcopenshell.file,
|
|
|
|
|
query: str,
|
|
|
|
|
elements: Optional[set[ifcopenshell.entity_instance]] = None,
|
|
|
|
|
edit_in_place=False,
|
|
|
|
|
) -> set[ifcopenshell.entity_instance]:
|
|
|
|
|
"""
|
|
|
|
|
Filter elements based on the provided `query`.
|
|
|
|
|
|
|
|
|
|
:param ifc_file: The IFC file object
|
2024-05-07 19:08:13 +10:00
|
|
|
:type ifc_file: ifcopenshell.file
|
2024-03-13 15:21:46 +05:00
|
|
|
:param query: Query to execute
|
|
|
|
|
:type query: str
|
2024-05-28 17:29:34 +10:00
|
|
|
:param elements: Base set of IFC elements for the query. If not provided,
|
|
|
|
|
all elements in the IFC are queried. If provided, the query will be
|
|
|
|
|
applied to this set of elements, so the result will be a subset of
|
|
|
|
|
elements.
|
2024-05-07 19:08:13 +10:00
|
|
|
:type elements: set[ifcopenshell.entity_instance], optional
|
2024-03-13 15:21:46 +05:00
|
|
|
:param edit_in_place: If `True`, mutate the provided `elements` in place. Defaults to `False`
|
|
|
|
|
:type edit_in_place: bool
|
|
|
|
|
:return: Set of filtered elements
|
2024-05-07 19:08:13 +10:00
|
|
|
:rtype: set[ifcopenshell.entity_instance]
|
2024-03-13 15:21:46 +05:00
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
|
|
# Select all walls in the file.
|
|
|
|
|
elements = ifcopenshell.util.selector.filter_elements(ifc_file, "IfcWall, IfcSlab")
|
|
|
|
|
|
|
|
|
|
# Add doors to the elements too.
|
|
|
|
|
elements = ifcopenshell.util.selector.filter_elements(ifc_file, "IfcDoor", elements)
|
|
|
|
|
|
|
|
|
|
# Changed our mind, exclude the slabs.
|
|
|
|
|
elements = ifcopenshell.util.selector.filter_elements(ifc_file, "! IfcSlab", elements)
|
|
|
|
|
|
|
|
|
|
# {#1=IfcWall(...), #2=IfcDoor(...)}
|
|
|
|
|
print(elements)
|
|
|
|
|
"""
|
2024-04-08 17:19:17 +10:00
|
|
|
if not query:
|
|
|
|
|
return elements or set()
|
2023-08-24 11:50:33 +10:00
|
|
|
if elements and not edit_in_place:
|
|
|
|
|
elements = elements.copy()
|
2023-07-20 19:22:15 +10:00
|
|
|
transformer = FacetTransformer(ifc_file, elements)
|
2023-07-23 15:20:14 +10:00
|
|
|
transformer.transform(filter_elements_grammar.parse(query))
|
2023-07-20 19:22:15 +10:00
|
|
|
return transformer.get_results()
|
|
|
|
|
|
|
|
|
|
|
2024-05-17 16:06:03 +05:00
|
|
|
class SetElementValueException(Exception): ...
|
|
|
|
|
|
|
|
|
|
|
2024-03-18 16:09:35 +05:00
|
|
|
def set_element_value(
|
|
|
|
|
ifc_file: ifcopenshell.file,
|
2024-05-17 15:19:22 +05:00
|
|
|
element: Union[ifcopenshell.entity_instance, Iterable[ifcopenshell.entity_instance], None],
|
2024-03-18 16:09:35 +05:00
|
|
|
query: Union[str, list[str]],
|
2024-05-17 15:19:22 +05:00
|
|
|
value: Any,
|
|
|
|
|
) -> None:
|
2024-05-17 16:06:03 +05:00
|
|
|
original_element = element
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(query, (list, tuple)):
|
|
|
|
|
keys = query
|
|
|
|
|
else:
|
|
|
|
|
keys = GetElementTransformer().transform(get_element_grammar.parse(query))
|
2023-08-17 23:33:29 +10:00
|
|
|
|
|
|
|
|
for i, key in enumerate(keys):
|
|
|
|
|
if element is None:
|
|
|
|
|
return
|
|
|
|
|
if key == "type":
|
|
|
|
|
element = ifcopenshell.util.element.get_type(element)
|
|
|
|
|
elif key in ("material", "mat"):
|
|
|
|
|
element = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
|
|
|
|
elif key in ("materials", "mats"):
|
|
|
|
|
element = ifcopenshell.util.element.get_materials(element)
|
|
|
|
|
elif key == "styles":
|
|
|
|
|
element = ifcopenshell.util.element.get_styles(element)
|
|
|
|
|
elif key in ("item", "i"):
|
|
|
|
|
if element.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
element = element.MaterialLayers
|
|
|
|
|
elif element.is_a("IfcMaterialProfileSet"):
|
|
|
|
|
element = element.MaterialProfiles
|
|
|
|
|
elif element.is_a("IfcMaterialConstituentSet"):
|
|
|
|
|
element = element.MaterialConstituents
|
|
|
|
|
elif key == "container":
|
|
|
|
|
element = ifcopenshell.util.element.get_container(element)
|
2023-08-21 23:06:49 +10:00
|
|
|
elif key == "space":
|
|
|
|
|
element = ifcopenshell.util.element.get_container(element, ifc_class="IfcSpace")
|
|
|
|
|
elif key == "storey":
|
|
|
|
|
element = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuildingStorey")
|
|
|
|
|
elif key == "building":
|
|
|
|
|
element = ifcopenshell.util.element.get_container(element, ifc_class="IfcBuilding")
|
|
|
|
|
elif key == "site":
|
|
|
|
|
element = ifcopenshell.util.element.get_container(element, ifc_class="IfcSite")
|
2023-08-17 23:33:29 +10:00
|
|
|
elif key == "class":
|
2023-08-26 16:42:18 +10:00
|
|
|
if element.is_a().lower() != value.lower():
|
2023-08-19 23:09:27 +10:00
|
|
|
return ifcopenshell.util.schema.reassign_class(ifc_file, element, value)
|
2024-05-26 21:57:08 +10:00
|
|
|
return
|
2023-08-17 23:33:29 +10:00
|
|
|
elif key == "id":
|
|
|
|
|
return
|
2023-11-05 09:38:32 -06:00
|
|
|
elif key == "classification":
|
|
|
|
|
element = ifcopenshell.util.classification.get_references(element)
|
2023-09-07 13:47:54 +10:00
|
|
|
elif key in ("x", "y", "z", "easting", "northing", "elevation") and hasattr(element, "ObjectPlacement"):
|
2024-03-18 16:37:43 +05:00
|
|
|
# TODO: add support
|
|
|
|
|
if key in ("easting", "northing", "elevation"):
|
|
|
|
|
return
|
|
|
|
|
|
2024-03-19 17:01:50 +05:00
|
|
|
placement = element.ObjectPlacement
|
|
|
|
|
if placement is None:
|
|
|
|
|
matrix = np.eye(4)
|
|
|
|
|
else:
|
|
|
|
|
matrix = ifcopenshell.util.placement.get_local_placement(placement)
|
2024-03-19 16:49:18 +05:00
|
|
|
|
|
|
|
|
# check if value is within tolerance to avoid api calls
|
2024-03-19 17:01:50 +05:00
|
|
|
coord_i = "xyz".index(key)
|
2024-03-19 16:49:18 +05:00
|
|
|
prev_value = matrix[coord_i][3]
|
|
|
|
|
new_value = float(value) if value else 0.0
|
2024-03-21 14:17:02 +05:00
|
|
|
if ifcopenshell.util.shape.is_x(new_value, prev_value):
|
2024-03-19 16:49:18 +05:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
matrix[coord_i][3] = new_value
|
2024-03-18 16:37:43 +05:00
|
|
|
ifcopenshell.api.run(
|
|
|
|
|
"geometry.edit_object_placement", ifc_file, product=element, matrix=matrix, is_si=False
|
|
|
|
|
)
|
2024-05-17 16:06:03 +05:00
|
|
|
return
|
2023-08-17 23:33:29 +10:00
|
|
|
elif isinstance(element, ifcopenshell.entity_instance):
|
|
|
|
|
if key == "Name" and element.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it.
|
|
|
|
|
|
2024-05-17 15:25:39 +05:00
|
|
|
if isinstance(key, str) and ((current_value := getattr(element, key, ...)) is not ...):
|
|
|
|
|
# check if key is not last
|
|
|
|
|
if len(keys) != i + 1:
|
|
|
|
|
element = current_value
|
|
|
|
|
continue
|
|
|
|
|
|
2024-05-17 16:06:03 +05:00
|
|
|
if current_value == value:
|
|
|
|
|
return
|
|
|
|
|
else:
|
2024-05-17 15:25:39 +05:00
|
|
|
# check if key is not last
|
2024-03-29 20:37:22 +11:00
|
|
|
try:
|
|
|
|
|
# Try our luck
|
|
|
|
|
return setattr(element, key, value)
|
|
|
|
|
except:
|
|
|
|
|
# Try to cast
|
|
|
|
|
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
|
|
|
|
element.wrapped_data.declaration()
|
|
|
|
|
.as_entity()
|
|
|
|
|
.attribute_by_index(element.wrapped_data.get_argument_index(key))
|
|
|
|
|
)
|
|
|
|
|
if data_type == "string":
|
|
|
|
|
value = str(value)
|
|
|
|
|
elif data_type == "float":
|
|
|
|
|
value = float(value)
|
|
|
|
|
elif data_type == "integer":
|
|
|
|
|
value = int(value)
|
|
|
|
|
elif data_type == "boolean":
|
|
|
|
|
if value in ("True", "true", "TRUE", "Yes", "1"):
|
|
|
|
|
value = True
|
|
|
|
|
elif value in ("False", "false", "FALSE", "No", "0"):
|
2024-05-17 13:57:44 +05:00
|
|
|
value = False
|
2024-03-29 20:37:22 +11:00
|
|
|
else:
|
|
|
|
|
value = bool(value)
|
2024-05-17 15:25:39 +05:00
|
|
|
elif data_type == "entity":
|
|
|
|
|
value = ifc_file.by_guid(value)
|
|
|
|
|
if current_value == value:
|
|
|
|
|
return
|
2024-03-29 20:37:22 +11:00
|
|
|
return setattr(element, key, value)
|
2023-08-17 23:33:29 +10:00
|
|
|
else:
|
|
|
|
|
# Try to extract pset
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, re.Pattern):
|
2023-08-17 23:33:29 +10:00
|
|
|
psets = ifcopenshell.util.element.get_psets(element)
|
|
|
|
|
matching_psets = []
|
|
|
|
|
for pset_name, pset in psets.items():
|
2023-08-26 16:42:18 +10:00
|
|
|
if key.match(pset_name):
|
2023-08-17 23:33:29 +10:00
|
|
|
matching_psets.append(pset)
|
|
|
|
|
result = matching_psets or None
|
2023-08-26 16:42:18 +10:00
|
|
|
if result and len(result) == 1:
|
|
|
|
|
result = result[0]
|
2023-08-17 23:33:29 +10:00
|
|
|
else:
|
|
|
|
|
result = ifcopenshell.util.element.get_pset(element, key)
|
|
|
|
|
|
2023-08-26 16:42:18 +10:00
|
|
|
if value and not result and len(keys) == i + 2: # The next key is the prop name
|
|
|
|
|
if "qto" in key.lower() or "quantity" in key.lower() or "quantities" in key.lower():
|
|
|
|
|
pset = ifcopenshell.api.run("pset.add_qto", ifc_file, product=element, name=key)
|
|
|
|
|
else:
|
|
|
|
|
pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name=key)
|
|
|
|
|
result = {"id": pset.id()}
|
2023-08-17 23:33:29 +10:00
|
|
|
|
|
|
|
|
element = result
|
|
|
|
|
elif isinstance(element, dict): # Such as from the result of a prior get_pset
|
|
|
|
|
pset = ifc_file.by_id(element["id"])
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, re.Pattern):
|
2023-08-19 23:09:27 +10:00
|
|
|
for prop, prop_value in element.items():
|
2023-08-26 16:42:18 +10:00
|
|
|
if key.match(prop):
|
2023-08-19 23:09:27 +10:00
|
|
|
if pset.is_a("IfcPropertySet") and prop_value != value:
|
2023-08-17 23:33:29 +10:00
|
|
|
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={prop: value})
|
2023-08-19 23:09:27 +10:00
|
|
|
elif pset.is_a("IfcElementQuantity") and prop_value != float(value):
|
2023-08-17 23:33:29 +10:00
|
|
|
ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={prop: float(value)})
|
2023-08-26 16:42:18 +10:00
|
|
|
elif pset.is_a("IfcPropertySet") and element.get(key, None) != value:
|
2023-08-17 23:33:29 +10:00
|
|
|
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={key: value})
|
2023-08-26 16:42:18 +10:00
|
|
|
elif pset.is_a("IfcElementQuantity"):
|
|
|
|
|
try:
|
|
|
|
|
value = float(value)
|
|
|
|
|
if element.get(key, None) != value:
|
|
|
|
|
ifcopenshell.api.run("pset.edit_qto", ifc_file, qto=pset, properties={key: value})
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2023-08-17 23:33:29 +10:00
|
|
|
return
|
2024-05-17 16:15:12 +05:00
|
|
|
elif isinstance(element, (list, tuple, set)): # If we use regex
|
2023-08-17 23:33:29 +10:00
|
|
|
if key.isnumeric():
|
|
|
|
|
try:
|
|
|
|
|
element = element[int(key)]
|
|
|
|
|
except IndexError:
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
for v in element:
|
2024-05-17 14:14:29 +05:00
|
|
|
set_element_value(ifc_file, v, keys[i:], value)
|
2023-08-17 23:33:29 +10:00
|
|
|
return
|
|
|
|
|
|
2024-05-17 16:06:03 +05:00
|
|
|
raise SetElementValueException(
|
|
|
|
|
f"Failed to set value for element '{original_element}' with query '{query}' (invalid or unsupported query)."
|
|
|
|
|
)
|
2023-08-17 23:33:29 +10:00
|
|
|
|
2024-05-28 17:29:34 +10:00
|
|
|
|
2023-07-20 19:22:15 +10:00
|
|
|
class FacetTransformer(lark.Transformer):
|
2024-03-13 15:21:46 +05:00
|
|
|
def __init__(self, ifc_file: ifcopenshell.file, elements: Optional[set[ifcopenshell.entity_instance]] = None):
|
2023-07-20 19:22:15 +10:00
|
|
|
self.file = ifc_file
|
|
|
|
|
self.results = []
|
2024-05-28 17:29:34 +10:00
|
|
|
if elements is None:
|
|
|
|
|
self.base_elements = None
|
|
|
|
|
self.elements = set()
|
|
|
|
|
else:
|
|
|
|
|
self.base_elements = elements.copy()
|
|
|
|
|
self.elements = set()
|
2023-07-20 19:22:15 +10:00
|
|
|
self.container_parents = {}
|
|
|
|
|
self.container_trees = {}
|
|
|
|
|
|
|
|
|
|
def get_results(self):
|
|
|
|
|
results = set()
|
|
|
|
|
for r in self.results:
|
|
|
|
|
results |= r
|
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
def facet_list(self, args):
|
|
|
|
|
if self.elements:
|
|
|
|
|
self.results.append(self.elements)
|
|
|
|
|
self.elements = set()
|
|
|
|
|
|
2023-07-20 19:42:34 +10:00
|
|
|
def instance(self, args):
|
2024-05-28 17:29:34 +10:00
|
|
|
if self.base_elements is None:
|
|
|
|
|
if args[0].data == "globalid":
|
|
|
|
|
try:
|
|
|
|
|
self.elements.add(self.file.by_guid(args[0].children[0].value))
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
self.elements.remove(self.file.by_guid(args[1].children[0].value))
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2023-07-20 19:42:34 +10:00
|
|
|
else:
|
2024-05-28 17:29:34 +10:00
|
|
|
if args[0].data == "globalid":
|
|
|
|
|
self.elements |= {
|
|
|
|
|
e for e in self.base_elements if getattr(e, "GlobalId", None) == args[0].children[0].value
|
|
|
|
|
}
|
|
|
|
|
else:
|
|
|
|
|
self.elements -= {
|
|
|
|
|
e for e in self.base_elements if getattr(e, "GlobalId", None) == args[1].children[0].value
|
|
|
|
|
}
|
2023-07-20 19:42:34 +10:00
|
|
|
|
2023-07-20 19:22:15 +10:00
|
|
|
def entity(self, args):
|
2024-05-28 17:29:34 +10:00
|
|
|
if self.base_elements is None:
|
|
|
|
|
if args[0].data == "ifc_class":
|
|
|
|
|
try:
|
|
|
|
|
self.elements |= set(self.file.by_type(args[0].children[0].value))
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
self.elements -= set(self.file.by_type(args[1].children[0].value))
|
|
|
|
|
except:
|
|
|
|
|
pass
|
2023-07-20 19:22:15 +10:00
|
|
|
else:
|
2024-05-28 17:29:34 +10:00
|
|
|
if args[0].data == "ifc_class":
|
|
|
|
|
self.elements |= {e for e in self.base_elements if e.is_a(args[0].children[0].value)}
|
|
|
|
|
else:
|
|
|
|
|
self.elements -= {e for e in self.base_elements if e.is_a(args[1].children[0].value)}
|
2023-07-20 19:22:15 +10:00
|
|
|
|
|
|
|
|
def attribute(self, args):
|
|
|
|
|
name, comparison, value = args
|
|
|
|
|
name = name.children[0].value
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
2024-04-19 16:05:33 +05:00
|
|
|
if name == "PredefinedType":
|
|
|
|
|
element_value = ifcopenshell.util.element.get_predefined_type(element)
|
|
|
|
|
else:
|
|
|
|
|
element_value = getattr(element, name, None)
|
2023-07-20 19:22:15 +10:00
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
|
|
|
|
def type(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
element_value = getattr(ifcopenshell.util.element.get_type(element), "Name", None)
|
|
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
|
|
|
|
def material(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
materials = ifcopenshell.util.element.get_materials(element)
|
|
|
|
|
result = False if materials else None
|
|
|
|
|
for material in materials:
|
|
|
|
|
if self.compare(material.Name, comparison, value):
|
|
|
|
|
result = True
|
|
|
|
|
if self.compare(getattr(material, "Category", None), comparison, value):
|
|
|
|
|
result = True
|
|
|
|
|
if result is not None:
|
|
|
|
|
return result if comparison == "=" else not result
|
|
|
|
|
return self.compare(None, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
|
|
|
|
def property(self, args):
|
|
|
|
|
pset, prop, comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
if isinstance(pset, str) and isinstance(prop, str):
|
|
|
|
|
element_value = ifcopenshell.util.element.get_pset(element, pset, prop)
|
|
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
elif isinstance(pset, str) and isinstance(prop, re.Pattern):
|
|
|
|
|
element_props = ifcopenshell.util.element.get_pset(element, pset) or {}
|
|
|
|
|
for element_prop, element_value in element_props.items():
|
|
|
|
|
if prop.match(element_prop):
|
|
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
elif isinstance(pset, re.Pattern):
|
|
|
|
|
element_psets = ifcopenshell.util.element.get_psets(element)
|
|
|
|
|
for element_pset, element_props in element_psets.items():
|
|
|
|
|
if not pset.match(element_pset):
|
|
|
|
|
continue
|
|
|
|
|
if isinstance(prop, str):
|
|
|
|
|
element_value = element_props.get(prop, None)
|
|
|
|
|
if element_value is not None:
|
|
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
elif isinstance(prop, re.Pattern):
|
|
|
|
|
for element_prop, element_value in element_props.items():
|
|
|
|
|
if prop.match(element_prop):
|
|
|
|
|
return self.compare(element_value, comparison, value)
|
|
|
|
|
return self.compare(None, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
|
|
|
|
def classification(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
references = ifcopenshell.util.classification.get_references(element)
|
|
|
|
|
result = False if references else None
|
|
|
|
|
for reference in references:
|
|
|
|
|
if self.compare(reference.Name, comparison, value):
|
|
|
|
|
result = True
|
|
|
|
|
if self.compare(
|
|
|
|
|
getattr(reference, "Identification", getattr(reference, "ItemReference", None)), comparison, value
|
|
|
|
|
):
|
|
|
|
|
result = True
|
|
|
|
|
if result is not None:
|
|
|
|
|
return result if comparison == "=" else not result
|
|
|
|
|
return self.compare(None, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
|
|
|
|
def location(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
container = ifcopenshell.util.element.get_container(element)
|
2024-03-01 20:02:15 +11:00
|
|
|
if not container:
|
|
|
|
|
container = ifcopenshell.util.element.get_aggregate(element)
|
2023-07-20 19:22:15 +10:00
|
|
|
containers = self.get_container_tree(container)
|
|
|
|
|
result = False if containers else None
|
|
|
|
|
for container in containers:
|
2024-06-01 14:53:55 +10:00
|
|
|
if self.compare(container.Name, "=", value):
|
2023-07-20 19:22:15 +10:00
|
|
|
result = True
|
|
|
|
|
if result is not None:
|
|
|
|
|
return result if comparison == "=" else not result
|
|
|
|
|
return self.compare(None, comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
2024-01-09 16:44:40 +11:00
|
|
|
def group(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
result = False
|
|
|
|
|
for rel in getattr(element, "HasAssignments", []):
|
|
|
|
|
if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup:
|
2024-06-01 14:53:55 +10:00
|
|
|
if self.compare(rel.RelatingGroup.Name, "=", value):
|
2024-01-09 16:44:40 +11:00
|
|
|
result = True
|
|
|
|
|
return result if comparison == "=" else not result
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
2024-06-01 15:10:45 +10:00
|
|
|
def parent(self, args):
|
|
|
|
|
comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
parents = []
|
|
|
|
|
result = False
|
|
|
|
|
if parent := ifcopenshell.util.element.get_parent(element):
|
|
|
|
|
parents.append(parent)
|
|
|
|
|
while parents:
|
|
|
|
|
parent = parents.pop()
|
|
|
|
|
if self.compare(parent.Name, comparison, value):
|
|
|
|
|
result = True
|
|
|
|
|
break
|
|
|
|
|
if grandparent := ifcopenshell.util.element.get_parent(parent):
|
|
|
|
|
parents.append(grandparent)
|
|
|
|
|
return result if comparison == "=" else not result
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
2023-08-21 23:44:20 +10:00
|
|
|
def query(self, args):
|
|
|
|
|
keys, comparison, value = args
|
|
|
|
|
|
|
|
|
|
def filter_function(element):
|
|
|
|
|
return self.compare(get_element_value(element, keys), comparison, value)
|
|
|
|
|
|
|
|
|
|
self.elements = set(filter(filter_function, self.elements))
|
|
|
|
|
|
2023-07-20 19:22:15 +10:00
|
|
|
def get_container_tree(self, container):
|
|
|
|
|
tree = self.container_trees.get(container, None)
|
|
|
|
|
if tree:
|
|
|
|
|
return tree
|
|
|
|
|
|
|
|
|
|
tree = []
|
|
|
|
|
|
|
|
|
|
while container:
|
|
|
|
|
if container.is_a("IfcProject"):
|
|
|
|
|
break
|
|
|
|
|
tree.append(container)
|
|
|
|
|
container = ifcopenshell.util.element.get_aggregate(container)
|
|
|
|
|
|
|
|
|
|
tree_copy = tree.copy()
|
|
|
|
|
while tree_copy:
|
|
|
|
|
self.container_trees[tree_copy.pop(0)] = tree_copy.copy()
|
|
|
|
|
return tree
|
|
|
|
|
|
|
|
|
|
def comparison(self, args):
|
2024-01-13 15:58:57 +11:00
|
|
|
if args[0].data == "not":
|
|
|
|
|
comparison = args[1].data
|
|
|
|
|
is_not = "!"
|
2023-12-28 22:03:00 -03:00
|
|
|
else:
|
2024-01-13 15:58:57 +11:00
|
|
|
comparison = args[0].data
|
|
|
|
|
is_not = ""
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
is_not
|
|
|
|
|
+ {
|
|
|
|
|
"equals": "=",
|
|
|
|
|
"morethanequalto": ">=",
|
|
|
|
|
"lessthanequalto": "<=",
|
|
|
|
|
"morethan": ">",
|
|
|
|
|
"lessthan": "<",
|
|
|
|
|
"contains": "*=",
|
|
|
|
|
}[comparison]
|
|
|
|
|
)
|
2023-07-20 19:22:15 +10:00
|
|
|
|
2023-08-21 23:44:20 +10:00
|
|
|
def keys(self, args):
|
|
|
|
|
return self.value(args)
|
|
|
|
|
|
2023-07-20 19:22:15 +10:00
|
|
|
def pset(self, args):
|
|
|
|
|
return self.value(args)
|
|
|
|
|
|
|
|
|
|
def prop(self, args):
|
|
|
|
|
return self.value(args)
|
|
|
|
|
|
|
|
|
|
def value(self, args):
|
|
|
|
|
if args[0].data == "unquoted_string":
|
|
|
|
|
return args[0].children[0].value
|
|
|
|
|
elif args[0].data == "quoted_string":
|
|
|
|
|
return args[0].children[0].value[1:-1].replace('\\"', '"')
|
|
|
|
|
elif args[0].data == "regex_string":
|
|
|
|
|
return re.compile(args[0].children[0].value)
|
|
|
|
|
elif args[0].data == "special":
|
|
|
|
|
if args[0].children[0].data == "null":
|
|
|
|
|
return None
|
|
|
|
|
elif args[0].children[0].data == "true":
|
|
|
|
|
return True
|
|
|
|
|
elif args[0].children[0].data == "false":
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def compare(self, element_value, comparison, value):
|
2024-01-13 15:58:57 +11:00
|
|
|
if isinstance(element_value, (list, tuple)):
|
|
|
|
|
return any(self.compare(ev, comparison, value) for ev in element_value)
|
|
|
|
|
elif isinstance(value, str):
|
2024-03-30 23:07:06 +11:00
|
|
|
try:
|
|
|
|
|
if isinstance(element_value, int):
|
|
|
|
|
value = int(value)
|
|
|
|
|
elif isinstance(element_value, float):
|
|
|
|
|
value = float(value)
|
|
|
|
|
|
|
|
|
|
if isinstance(element_value, (int, float)):
|
|
|
|
|
operator = comparison.lstrip("!")
|
|
|
|
|
if operator == ">=":
|
|
|
|
|
result = element_value >= value
|
|
|
|
|
elif operator == "<=":
|
|
|
|
|
result = element_value <= value
|
|
|
|
|
elif operator == ">":
|
|
|
|
|
result = element_value > value
|
|
|
|
|
elif operator == "<":
|
|
|
|
|
result = element_value < value
|
|
|
|
|
else:
|
|
|
|
|
result = element_value == value # Tolerance?
|
|
|
|
|
elif isinstance(element_value, str):
|
|
|
|
|
operator = comparison.lstrip("!")
|
|
|
|
|
if operator == "*=":
|
|
|
|
|
result = value in element_value
|
|
|
|
|
else:
|
|
|
|
|
result = element_value == value
|
2024-01-13 15:58:57 +11:00
|
|
|
else:
|
|
|
|
|
result = element_value == value
|
2024-03-30 23:07:06 +11:00
|
|
|
except:
|
|
|
|
|
# Potentially they are trying to compare a value which cannot
|
|
|
|
|
# be legally casted to the element_value, or cannot use the
|
|
|
|
|
# `in` or more / less than comparison operators.
|
|
|
|
|
result = False
|
2024-01-13 15:58:57 +11:00
|
|
|
elif isinstance(value, re.Pattern):
|
|
|
|
|
result = bool(value.match(element_value)) if element_value is not None else False
|
|
|
|
|
elif value in (None, True, False):
|
|
|
|
|
result = element_value is value
|
|
|
|
|
|
|
|
|
|
if comparison.startswith("!"):
|
|
|
|
|
return not result
|
|
|
|
|
return result
|
2023-07-20 19:22:15 +10:00
|
|
|
|
|
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
class Selector:
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
2024-03-21 11:20:57 +05:00
|
|
|
def parse(
|
|
|
|
|
cls, ifc_file: ifcopenshell.file, query: str, elements: Optional[list[ifcopenshell.entity_instance]] = None
|
|
|
|
|
) -> list[ifcopenshell.entity_instance]:
|
2022-03-21 17:28:16 +11:00
|
|
|
cls.file = ifc_file
|
2022-03-22 11:32:40 +11:00
|
|
|
cls.elements = elements
|
2020-11-01 20:08:27 +07:00
|
|
|
l = lark.Lark(
|
|
|
|
|
"""start: query (lfunction query)*
|
2020-05-17 18:32:56 +10:00
|
|
|
query: selector | group
|
|
|
|
|
group: "(" query (lfunction query)* ")"
|
|
|
|
|
selector: (inverse_relationship)? guid_selector | (inverse_relationship)? class_selector
|
|
|
|
|
guid_selector: "#" /[0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$]{22}/
|
|
|
|
|
class_selector: "." WORD filter ?
|
|
|
|
|
filter: "[" filter_key (comparison filter_value)? "]"
|
2023-03-09 11:07:13 +11:00
|
|
|
filter_key: WORD | ESCAPED_STRING | keys_regex | keys_quoted | keys_simple
|
2023-05-31 13:23:40 +10:00
|
|
|
filter_value: filter_regex | ESCAPED_STRING | SIGNED_FLOAT | SIGNED_INT | BOOLEAN | NULL
|
|
|
|
|
filter_regex: "r" ESCAPED_STRING
|
2023-03-25 22:07:18 +11:00
|
|
|
keys_regex: "r" ESCAPED_STRING ("." ESCAPED_STRING)*
|
|
|
|
|
keys_quoted: ESCAPED_STRING ("." ESCAPED_STRING)*
|
|
|
|
|
keys_simple: /[^\\W][^.=<>!%*\\]]*/ ("." /[^\\W][^.=<>!%*\\]]*/)*
|
2020-05-17 18:32:56 +10:00
|
|
|
lfunction: and | or
|
2022-10-19 09:49:06 +02:00
|
|
|
inverse_relationship: types | decomposed_by | bounded_by | grouped_by
|
2020-05-17 18:32:56 +10:00
|
|
|
types: "*"
|
2022-03-22 11:23:13 +11:00
|
|
|
decomposed_by: "@"
|
|
|
|
|
bounded_by: "@@"
|
2022-10-19 09:49:06 +02:00
|
|
|
grouped_by: "@@@"
|
2020-05-17 18:32:56 +10:00
|
|
|
and: "&"
|
|
|
|
|
or: "|"
|
2022-03-22 10:40:05 +11:00
|
|
|
not: "!"
|
2022-03-22 10:49:48 +11:00
|
|
|
comparison: (not)* (oneof | contains | morethanequalto | lessthanequalto | equal | morethan | lessthan)
|
|
|
|
|
oneof: "%="
|
2020-05-17 18:32:56 +10:00
|
|
|
contains: "*="
|
|
|
|
|
morethanequalto: ">="
|
2022-03-22 10:39:10 +11:00
|
|
|
lessthanequalto: "<="
|
2020-05-17 18:32:56 +10:00
|
|
|
equal: "="
|
|
|
|
|
morethan: ">"
|
|
|
|
|
lessthan: "<"
|
2022-03-24 01:02:58 +01:00
|
|
|
BOOLEAN: "TRUE" | "FALSE" | "true" | "false"| "True" | "False"
|
2022-03-21 18:03:25 +11:00
|
|
|
NULL: "NULL"
|
2020-05-17 18:32:56 +10:00
|
|
|
|
|
|
|
|
// Embed common.lark for packaging
|
|
|
|
|
DIGIT: "0".."9"
|
|
|
|
|
HEXDIGIT: "a".."f"|"A".."F"|DIGIT
|
|
|
|
|
INT: DIGIT+
|
|
|
|
|
SIGNED_INT: ["+"|"-"] INT
|
|
|
|
|
DECIMAL: INT "." INT? | "." INT
|
|
|
|
|
_EXP: ("e"|"E") SIGNED_INT
|
|
|
|
|
FLOAT: INT _EXP | DECIMAL _EXP?
|
|
|
|
|
SIGNED_FLOAT: ["+"|"-"] FLOAT
|
|
|
|
|
NUMBER: FLOAT | INT
|
|
|
|
|
SIGNED_NUMBER: ["+"|"-"] NUMBER
|
|
|
|
|
_STRING_INNER: /.*?/
|
|
|
|
|
_STRING_ESC_INNER: _STRING_INNER /(?<!\\\\)(\\\\\\\\)*?/
|
|
|
|
|
ESCAPED_STRING : "\\"" _STRING_ESC_INNER "\\""
|
|
|
|
|
LCASE_LETTER: "a".."z"
|
|
|
|
|
UCASE_LETTER: "A".."Z"
|
|
|
|
|
LETTER: UCASE_LETTER | LCASE_LETTER
|
|
|
|
|
WORD: LETTER+
|
|
|
|
|
CNAME: ("_"|LETTER) ("_"|LETTER|DIGIT)*
|
|
|
|
|
WS_INLINE: (" "|/\\t/)+
|
|
|
|
|
WS: /[ \\t\\f\\r\\n]/+
|
|
|
|
|
CR : /\\r/
|
|
|
|
|
LF : /\\n/
|
|
|
|
|
NEWLINE: (CR? LF)+
|
|
|
|
|
|
|
|
|
|
%ignore WS // Disregard spaces in text
|
2020-11-01 20:08:27 +07:00
|
|
|
"""
|
|
|
|
|
)
|
2020-05-17 18:32:56 +10:00
|
|
|
|
|
|
|
|
start = l.parse(query)
|
2022-03-21 17:28:16 +11:00
|
|
|
return cls.get_group(start)
|
2020-05-17 18:32:56 +10:00
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def get_group(cls, group):
|
2020-05-17 18:32:56 +10:00
|
|
|
lfunction = None
|
|
|
|
|
for child in group.children:
|
2020-11-01 20:08:27 +07:00
|
|
|
if child.data == "query":
|
2022-03-21 17:28:16 +11:00
|
|
|
new_results = cls.get_query(child)
|
2020-05-17 18:32:56 +10:00
|
|
|
if not lfunction:
|
|
|
|
|
results = new_results
|
2020-11-01 20:08:27 +07:00
|
|
|
elif lfunction == "or":
|
2020-05-17 18:32:56 +10:00
|
|
|
results.extend(new_results)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif lfunction == "and":
|
2020-05-17 18:32:56 +10:00
|
|
|
results = list(set(results).intersection(new_results))
|
|
|
|
|
results = list(set(results))
|
2020-11-01 20:08:27 +07:00
|
|
|
elif child.data == "lfunction":
|
2020-05-17 18:32:56 +10:00
|
|
|
lfunction = child.children[0].data
|
|
|
|
|
return results
|
|
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def get_query(cls, query):
|
2020-05-17 18:32:56 +10:00
|
|
|
for child in query.children:
|
2020-11-01 20:08:27 +07:00
|
|
|
if child.data == "selector":
|
2022-03-21 17:28:16 +11:00
|
|
|
return cls.get_selector(child)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif child.data == "group":
|
2022-03-21 17:28:16 +11:00
|
|
|
return cls.get_group(child)
|
2020-05-17 18:32:56 +10:00
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def get_selector(cls, selector):
|
2020-05-17 18:32:56 +10:00
|
|
|
if len(selector.children) == 1:
|
|
|
|
|
inverse_relationship = None
|
|
|
|
|
class_or_guid_selector = selector.children[0]
|
|
|
|
|
else:
|
|
|
|
|
inverse_relationship = selector.children[0]
|
|
|
|
|
class_or_guid_selector = selector.children[1]
|
|
|
|
|
|
2020-11-01 20:08:27 +07:00
|
|
|
if class_or_guid_selector.data == "class_selector":
|
2022-03-21 17:28:16 +11:00
|
|
|
results = cls.get_class_selector(class_or_guid_selector)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif class_or_guid_selector.data == "guid_selector":
|
2022-03-21 17:28:16 +11:00
|
|
|
results = cls.get_guid_selector(class_or_guid_selector)
|
2020-05-17 18:32:56 +10:00
|
|
|
|
|
|
|
|
if not inverse_relationship:
|
|
|
|
|
return results
|
2023-05-06 11:05:13 +10:00
|
|
|
return cls.parse_inverse_relationship(results, inverse_relationship.children[0].data)
|
2020-05-17 18:32:56 +10:00
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def parse_inverse_relationship(cls, elements, inverse_relationship):
|
2020-05-17 18:32:56 +10:00
|
|
|
results = []
|
|
|
|
|
for element in elements:
|
2020-11-01 20:08:27 +07:00
|
|
|
if inverse_relationship == "types":
|
|
|
|
|
if hasattr(element, "Types") and element.Types:
|
2020-05-17 18:32:56 +10:00
|
|
|
results.extend(element.Types[0].RelatedObjects)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif hasattr(element, "ObjectTypeOf") and element.ObjectTypeOf:
|
2020-05-17 18:32:56 +10:00
|
|
|
results.extend(element.ObjectTypeOf[0].RelatedObjects)
|
2022-03-22 11:23:13 +11:00
|
|
|
elif inverse_relationship == "decomposed_by":
|
2022-07-19 14:51:48 +02:00
|
|
|
results.extend(ifcopenshell.util.element.get_decomposition(element))
|
2022-10-19 09:49:06 +02:00
|
|
|
elif inverse_relationship == "grouped_by":
|
|
|
|
|
results.extend(ifcopenshell.util.element.get_grouped_by(element))
|
2022-03-22 11:23:13 +11:00
|
|
|
elif inverse_relationship == "bounded_by" and hasattr(element, "BoundedBy"):
|
2021-08-04 11:55:51 +02:00
|
|
|
for relationship in element.BoundedBy:
|
|
|
|
|
results.append(relationship.RelatedBuildingElement)
|
2020-05-17 18:32:56 +10:00
|
|
|
return results
|
|
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def get_class_selector(cls, class_selector):
|
2020-11-01 20:08:27 +07:00
|
|
|
if class_selector.children[0] == "COBie":
|
2022-03-21 17:28:16 +11:00
|
|
|
elements = ifcopenshell.util.fm.get_cobie_components(cls.file)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif class_selector.children[0] == "COBieType":
|
2022-03-21 17:28:16 +11:00
|
|
|
elements = ifcopenshell.util.fm.get_cobie_types(cls.file)
|
2021-07-23 16:10:38 +10:00
|
|
|
elif class_selector.children[0] == "FMHEM":
|
2022-03-21 17:28:16 +11:00
|
|
|
elements = ifcopenshell.util.fm.get_fmhem_types(cls.file)
|
2020-07-16 11:37:56 +10:00
|
|
|
else:
|
2022-03-22 11:32:40 +11:00
|
|
|
if cls.elements is None:
|
|
|
|
|
elements = cls.file.by_type(class_selector.children[0])
|
|
|
|
|
else:
|
2023-05-06 11:05:13 +10:00
|
|
|
elements = [e for e in cls.elements if e.is_a(class_selector.children[0])]
|
|
|
|
|
if len(class_selector.children) > 1 and class_selector.children[1].data == "filter":
|
2022-03-21 17:28:16 +11:00
|
|
|
return cls.filter_elements(elements, class_selector.children[1])
|
2020-05-17 18:32:56 +10:00
|
|
|
return elements
|
|
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def filter_elements(cls, elements, filter_rule):
|
2020-05-17 18:32:56 +10:00
|
|
|
results = []
|
2023-03-13 11:09:56 +11:00
|
|
|
filter_query = cls.parse_filter_query(filter_rule.children[0].children[0])
|
2020-05-17 18:32:56 +10:00
|
|
|
comparison = value = None
|
|
|
|
|
if len(filter_rule.children) > 1:
|
|
|
|
|
comparison = filter_rule.children[1].children[0].data
|
2022-03-22 10:40:05 +11:00
|
|
|
if comparison == "not":
|
|
|
|
|
comparison += filter_rule.children[1].children[1].data
|
2023-05-31 13:23:40 +10:00
|
|
|
filter_value = filter_rule.children[2].children[0]
|
|
|
|
|
if isinstance(filter_value, lark.Tree):
|
|
|
|
|
is_regex = True
|
|
|
|
|
token_type = filter_value.data
|
|
|
|
|
else:
|
|
|
|
|
is_regex = False
|
|
|
|
|
token_type = filter_value.type
|
|
|
|
|
if token_type == "filter_regex":
|
|
|
|
|
value = str(filter_value.children[0][1:-1])
|
|
|
|
|
elif token_type == "ESCAPED_STRING":
|
|
|
|
|
value = str(filter_value[1:-1])
|
2022-03-21 18:03:25 +11:00
|
|
|
elif token_type == "SIGNED_INT":
|
2023-05-31 13:23:40 +10:00
|
|
|
value = int(filter_value)
|
2022-03-21 18:03:25 +11:00
|
|
|
elif token_type == "SIGNED_FLOAT":
|
2023-05-31 13:23:40 +10:00
|
|
|
value = float(filter_value)
|
2022-03-21 18:03:25 +11:00
|
|
|
elif token_type == "BOOLEAN":
|
2023-05-31 13:23:40 +10:00
|
|
|
value = filter_value.lower() == "true"
|
2022-03-21 18:03:25 +11:00
|
|
|
elif token_type == "NULL":
|
|
|
|
|
value = None
|
2020-05-17 18:32:56 +10:00
|
|
|
for element in elements:
|
2023-08-26 16:42:18 +10:00
|
|
|
if filter_query["is_regex"]:
|
|
|
|
|
filter_query["keys"] = [re.compile(k) for k in filter_query["keys"]]
|
|
|
|
|
element_value = cls.get_element_value(element, filter_query["keys"])
|
2022-07-20 09:33:58 +02:00
|
|
|
if element_value is None and value is not None and "not" not in comparison:
|
2020-05-17 18:32:56 +10:00
|
|
|
continue
|
2023-05-31 13:23:40 +10:00
|
|
|
if comparison and cls.filter_element(element, element_value, comparison, value, is_regex=is_regex):
|
2022-04-27 22:19:31 +10:00
|
|
|
results.append(element)
|
|
|
|
|
elif not comparison and element_value:
|
2020-05-17 18:32:56 +10:00
|
|
|
results.append(element)
|
|
|
|
|
return results
|
|
|
|
|
|
2023-03-13 11:09:56 +11:00
|
|
|
@classmethod
|
|
|
|
|
def parse_filter_query(cls, filter_query):
|
|
|
|
|
keys = filter_query
|
|
|
|
|
is_regex = False
|
|
|
|
|
if isinstance(keys, str):
|
|
|
|
|
keys = [keys]
|
|
|
|
|
elif keys.data == "keys_regex":
|
|
|
|
|
is_regex = True
|
2023-05-06 11:05:13 +10:00
|
|
|
keys = [k[1:-1].replace('\\"', '"') for k in keys.children]
|
2023-03-13 11:09:56 +11:00
|
|
|
elif keys.data == "keys_quoted":
|
2023-05-06 11:05:13 +10:00
|
|
|
keys = [k[1:-1].replace('\\"', '"') for k in keys.children]
|
2023-03-13 11:09:56 +11:00
|
|
|
elif keys.data == "keys_simple":
|
2023-03-25 22:07:18 +11:00
|
|
|
keys = keys.children
|
2023-03-13 11:09:56 +11:00
|
|
|
return {"keys": keys, "is_regex": is_regex}
|
|
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
2024-03-18 16:09:35 +05:00
|
|
|
def get_element_value(cls, element: ifcopenshell.entity_instance, keys: list[str]) -> Any:
|
2023-03-06 17:47:03 +11:00
|
|
|
value = element
|
|
|
|
|
for key in keys:
|
2023-04-20 17:41:52 +10:00
|
|
|
if value is None:
|
|
|
|
|
return
|
2023-03-06 17:47:03 +11:00
|
|
|
if key == "type":
|
2023-03-25 22:07:18 +11:00
|
|
|
value = ifcopenshell.util.element.get_type(value)
|
|
|
|
|
elif key in ("material", "mat"):
|
|
|
|
|
value = ifcopenshell.util.element.get_material(value, should_skip_usage=True)
|
2023-08-14 11:55:15 +10:00
|
|
|
elif key in ("materials", "mats"):
|
|
|
|
|
value = ifcopenshell.util.element.get_materials(value)
|
2024-05-11 22:57:16 +10:00
|
|
|
elif key == "profiles":
|
|
|
|
|
value = ifcopenshell.util.shape.get_profiles(value)
|
2023-08-14 11:55:15 +10:00
|
|
|
elif key == "styles":
|
|
|
|
|
value = ifcopenshell.util.element.get_styles(value)
|
2023-03-25 22:07:18 +11:00
|
|
|
elif key in ("item", "i"):
|
|
|
|
|
if value.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
value = value.MaterialLayers
|
|
|
|
|
elif value.is_a("IfcMaterialProfileSet"):
|
|
|
|
|
value = value.MaterialProfiles
|
|
|
|
|
elif value.is_a("IfcMaterialConstituentSet"):
|
|
|
|
|
value = value.MaterialConstituents
|
2023-03-06 17:47:03 +11:00
|
|
|
elif key == "container":
|
2023-03-27 21:47:11 +11:00
|
|
|
value = ifcopenshell.util.element.get_container(value)
|
2023-08-21 23:06:49 +10:00
|
|
|
elif key == "space":
|
2023-08-27 22:32:31 +10:00
|
|
|
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSpace")
|
2023-08-21 23:06:49 +10:00
|
|
|
elif key == "storey":
|
2023-08-27 22:32:31 +10:00
|
|
|
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuildingStorey")
|
2023-08-21 23:06:49 +10:00
|
|
|
elif key == "building":
|
2023-08-27 22:32:31 +10:00
|
|
|
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcBuilding")
|
2023-08-21 23:06:49 +10:00
|
|
|
elif key == "site":
|
2023-08-27 22:32:31 +10:00
|
|
|
value = ifcopenshell.util.element.get_container(value, ifc_class="IfcSite")
|
|
|
|
|
elif key in ("types", "occurrences"):
|
|
|
|
|
value = ifcopenshell.util.element.get_types(value)
|
2023-08-21 23:44:20 +10:00
|
|
|
elif key == "count":
|
|
|
|
|
if isinstance(value, set):
|
|
|
|
|
value = len(list(value))
|
|
|
|
|
elif isinstance(value, (list, tuple)):
|
|
|
|
|
value = len(value)
|
2023-08-26 18:24:48 +10:00
|
|
|
else:
|
|
|
|
|
value = 1
|
2023-03-27 21:47:11 +11:00
|
|
|
elif key == "class":
|
|
|
|
|
value = value.is_a()
|
2023-08-27 22:32:31 +10:00
|
|
|
elif key == "predefined_type":
|
|
|
|
|
value = ifcopenshell.util.element.get_predefined_type(value)
|
2023-07-05 10:36:43 +10:00
|
|
|
elif key == "id":
|
|
|
|
|
value = value.id()
|
2023-11-05 09:38:32 -06:00
|
|
|
elif key == "classification":
|
|
|
|
|
value = ifcopenshell.util.classification.get_references(value)
|
2023-09-07 13:47:54 +10:00
|
|
|
elif key in ("x", "y", "z", "easting", "northing", "elevation") and hasattr(value, "ObjectPlacement"):
|
|
|
|
|
if getattr(value, "ObjectPlacement", None):
|
|
|
|
|
matrix = ifcopenshell.util.placement.get_local_placement(value.ObjectPlacement)
|
2023-09-07 22:09:26 +10:00
|
|
|
xyz = matrix[:, 3][:3]
|
2023-09-07 13:47:54 +10:00
|
|
|
if key in ("x", "y", "z"):
|
|
|
|
|
value = xyz["xyz".index(key)]
|
|
|
|
|
else:
|
|
|
|
|
enh = ifcopenshell.util.geolocation.auto_xyz2enh(element.wrapped_data.file, *xyz)
|
|
|
|
|
value = enh[("easting", "northing", "elevation").index(key)]
|
|
|
|
|
else:
|
|
|
|
|
value = None
|
2023-03-06 17:47:03 +11:00
|
|
|
elif isinstance(value, ifcopenshell.entity_instance):
|
2023-05-06 11:05:13 +10:00
|
|
|
if key == "Name" and value.is_a("IfcMaterialLayerSet"):
|
|
|
|
|
key = "LayerSetName" # This oddity in the IFC spec is annoying so we account for it.
|
|
|
|
|
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, re.Pattern):
|
2023-09-07 12:18:32 +10:00
|
|
|
attribute = None # Should we support regex attributes? Probably not for now.
|
2023-08-26 16:42:18 +10:00
|
|
|
else:
|
|
|
|
|
attribute = getattr(value, key, None)
|
2023-03-09 11:07:13 +11:00
|
|
|
|
|
|
|
|
if attribute is not None:
|
|
|
|
|
value = attribute
|
2023-03-06 17:47:03 +11:00
|
|
|
else:
|
2023-03-09 11:07:13 +11:00
|
|
|
# Try to extract pset
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, re.Pattern):
|
2023-03-09 11:07:13 +11:00
|
|
|
psets = ifcopenshell.util.element.get_psets(value)
|
|
|
|
|
matching_psets = []
|
|
|
|
|
for pset_name, pset in psets.items():
|
2023-08-26 16:42:18 +10:00
|
|
|
if key.match(pset_name):
|
|
|
|
|
del pset["id"]
|
2023-03-09 11:07:13 +11:00
|
|
|
matching_psets.append(pset)
|
|
|
|
|
result = matching_psets or None
|
2023-08-26 16:42:18 +10:00
|
|
|
if result and len(result) == 1:
|
|
|
|
|
result = result[0]
|
2023-03-09 11:07:13 +11:00
|
|
|
else:
|
|
|
|
|
result = ifcopenshell.util.element.get_pset(value, key)
|
2023-08-26 16:42:18 +10:00
|
|
|
if result:
|
|
|
|
|
del result["id"]
|
2023-03-09 11:07:13 +11:00
|
|
|
|
2023-03-06 17:47:03 +11:00
|
|
|
value = result
|
2023-05-06 11:05:13 +10:00
|
|
|
elif isinstance(value, dict): # Such as from the result of a prior get_pset
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, re.Pattern):
|
2023-03-09 11:07:13 +11:00
|
|
|
results = []
|
|
|
|
|
for prop_name, prop_value in value.items():
|
2023-08-26 16:42:18 +10:00
|
|
|
if key.match(prop_name):
|
2023-04-06 19:21:14 +10:00
|
|
|
if isinstance(prop_value, (list, tuple)):
|
|
|
|
|
results.extend(prop_value)
|
|
|
|
|
else:
|
|
|
|
|
results.append(prop_value)
|
2023-08-26 16:42:18 +10:00
|
|
|
value = results or None
|
|
|
|
|
if value and len(value) == 1:
|
|
|
|
|
value = value[0]
|
2023-03-09 11:07:13 +11:00
|
|
|
else:
|
|
|
|
|
value = value.get(key, None)
|
2023-11-05 09:38:32 -06:00
|
|
|
elif isinstance(value, (list, tuple, set)): # If we use regex
|
2023-08-26 16:42:18 +10:00
|
|
|
if isinstance(key, str) and key.isnumeric():
|
2023-04-20 17:41:52 +10:00
|
|
|
try:
|
|
|
|
|
value = value[int(key)]
|
|
|
|
|
except IndexError:
|
|
|
|
|
return
|
2023-04-04 09:58:23 +10:00
|
|
|
else:
|
|
|
|
|
results = []
|
|
|
|
|
for v in value:
|
2023-08-26 16:42:18 +10:00
|
|
|
subvalue = cls.get_element_value(v, [key])
|
2023-04-04 09:58:23 +10:00
|
|
|
if isinstance(subvalue, list):
|
|
|
|
|
results.extend(subvalue)
|
|
|
|
|
else:
|
|
|
|
|
results.append(subvalue)
|
|
|
|
|
value = results
|
2023-03-06 17:47:03 +11:00
|
|
|
return value
|
2020-07-14 19:27:56 +10:00
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
2023-05-31 13:23:40 +10:00
|
|
|
def filter_element(cls, element, element_value, comparison, value, is_regex=False):
|
2022-03-22 10:40:05 +11:00
|
|
|
if comparison.startswith("not"):
|
2023-05-31 13:23:40 +10:00
|
|
|
return not cls.filter_element(element, element_value, comparison[3:], value, is_regex=is_regex)
|
2022-08-19 20:10:54 +10:00
|
|
|
elif comparison == "equal" and isinstance(element_value, list):
|
2023-05-31 13:23:40 +10:00
|
|
|
if is_regex:
|
|
|
|
|
for element_v in element_value:
|
|
|
|
|
if re.match(value, element_v):
|
|
|
|
|
return True
|
|
|
|
|
return False
|
2022-08-19 20:10:54 +10:00
|
|
|
return value in element_value
|
2022-03-22 10:40:05 +11:00
|
|
|
elif comparison == "equal":
|
2023-05-31 13:23:40 +10:00
|
|
|
if is_regex:
|
|
|
|
|
return bool(re.match(value, element_value))
|
2022-03-21 18:03:25 +11:00
|
|
|
return element_value == value
|
2022-08-19 20:10:54 +10:00
|
|
|
elif comparison == "contains" and isinstance(element_value, list):
|
|
|
|
|
return bool([ev for ev in element_value if value in str(ev)])
|
2020-11-01 20:08:27 +07:00
|
|
|
elif comparison == "contains":
|
2020-05-17 18:32:56 +10:00
|
|
|
return value in str(element_value)
|
2020-11-01 20:08:27 +07:00
|
|
|
elif comparison == "morethan":
|
2022-03-22 10:40:05 +11:00
|
|
|
return element_value > value
|
2020-11-01 20:08:27 +07:00
|
|
|
elif comparison == "lessthan":
|
2022-03-22 10:40:05 +11:00
|
|
|
return element_value < value
|
2020-11-01 20:08:27 +07:00
|
|
|
elif comparison == "morethanequalto":
|
2022-03-22 10:40:05 +11:00
|
|
|
return element_value >= value
|
2020-11-01 20:08:27 +07:00
|
|
|
elif comparison == "lessthanequalto":
|
2022-03-22 10:40:05 +11:00
|
|
|
return element_value <= value
|
2022-03-22 10:49:48 +11:00
|
|
|
elif comparison == "oneof":
|
|
|
|
|
return element_value in value.split(",")
|
2020-05-17 18:32:56 +10:00
|
|
|
return False
|
|
|
|
|
|
2022-03-21 17:28:16 +11:00
|
|
|
@classmethod
|
|
|
|
|
def get_guid_selector(cls, guid_selector):
|
|
|
|
|
return [cls.file.by_id(guid_selector.children[0])]
|