From 0279a5ab7c5f805b368d9509c29f8329bc15a8d0 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 12 Jul 2026 08:06:49 +0300 Subject: [PATCH] express: include referencing SELECT types in typeof() (#3187) Per ISO 10303-11 12.4.3, TYPEOF() must return every type a value conforms to, including any SELECT data type that references it (transitively) as a member. The previous implementation only walked entity supertypes and defined-type chains, so it never reported SELECT types. This silently broke every WHERE rule that tests SELECT membership through typeof: for example IfcFillAreaStyle.MaxOneColour and ConsistentHatchStyleDef test `'ifc4.ifccolour' in typeof(style)`, so an IfcFillAreaStyle with two IfcColourRgb entries was never rejected. typeof() now builds a per-schema (cached) reverse map from a type name to the SELECT types that reference it as a direct member, then closes the base type set over that map so selects-of-selects are also included. The canonical implementation lives in the rule_compiler.py preamble and is copied verbatim into every generated rules/*.py, so the source of truth and the generated files are updated in lockstep. Verified: typeof(IfcColourRgb) now yields ifccolour, ifccolourorfactor, and ifcfillstyleselect; an IfcFillAreaStyle with two colours now fails MaxOneColour. All 138 express rule fixtures still pass (75 pass-cases at 0 errors, 63 fail-cases still flagged), no regressions. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- .../ifcopenshell/express/rule_compiler.py | 31 +++++++++++++++++-- .../ifcopenshell/express/rules/IFC2X3.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X1.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X2.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_ADD1.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_ADD2.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_RC1.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_RC2.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_RC3.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_RC4.py | 27 ++++++++++++++-- .../ifcopenshell/express/rules/IFC4X3_TC1.py | 27 ++++++++++++++-- 13 files changed, 316 insertions(+), 39 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py b/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py index 38fa867778..21c85132f8 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rule_compiler.py @@ -939,10 +939,27 @@ def typeof(inst): # If V evaluates to indeterminate (?), an empty set is returned. return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + # Per ISO 10303-11 12.4.3, TYPEOF() also includes every select data type + # that references (transitively) any of the value's types as a member of + # its select list. Cache, per schema, the reverse map type name -> select + # types directly referencing it. + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -951,7 +968,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: def __bool__(self): diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC2X3.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC2X3.py index bdcf863e48..83968563b8 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC2X3.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC2X3.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4.py index 7b931af2e9..2014111f77 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X1.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X1.py index b07e9a2234..99c161a33d 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X1.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X1.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X2.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X2.py index 07f6a261e7..6bd1f6039c 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X2.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X2.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3.py index 63389eba70..1e389997a6 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD1.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD1.py index 67bba82f2b..459edbcfce 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD1.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD1.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD2.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD2.py index 1645ddf8f5..69629f6868 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD2.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_ADD2.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC1.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC1.py index ae0ee5bce1..c0b7a33a9a 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC1.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC1.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC2.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC2.py index 36329b4af0..193599a6d3 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC2.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC2.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC3.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC3.py index f04cb77e97..1cd320f354 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC3.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC3.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC4.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC4.py index 89afa919b8..683aaeb93e 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC4.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_RC4.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: diff --git a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_TC1.py b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_TC1.py index c9d106e350..4276e7eed9 100644 --- a/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_TC1.py +++ b/src/ifcopenshell-python/ifcopenshell/express/rules/IFC4X3_TC1.py @@ -96,11 +96,24 @@ def typeof(inst): if not inst: return express_set([]) schema_name = inst.is_a(True).split('.')[0].lower() + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) + select_parents = getattr(typeof, '_select_parents', None) + if select_parents is None: + select_parents = typeof._select_parents = {} + schema_selects = select_parents.get(schema_name) + if schema_selects is None: + schema_selects = {} + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.select_type): + sel_name = decl.name().lower() + for member in decl.select_list(): + schema_selects.setdefault(member.name().lower(), []).append(sel_name) + select_parents[schema_name] = schema_selects def inner(): - decl = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name).declaration_by_name(inst.is_a()) + decl = schema.declaration_by_name(inst.is_a()) while decl: - yield '.'.join((schema_name, decl.name().lower())) + yield decl.name().lower() if isinstance(decl, ifcopenshell.ifcopenshell_wrapper.entity): decl = decl.supertype() else: @@ -109,7 +122,15 @@ def typeof(inst): decl = decl.declared_type() if not isinstance(decl, ifcopenshell.ifcopenshell_wrapper.type_declaration): break - return express_set(inner()) + names = set(inner()) + queue = list(names) + while queue: + name = queue.pop() + for sel_name in schema_selects.get(name, ()): + if sel_name not in names: + names.add(sel_name) + queue.append(sel_name) + return express_set('.'.join((schema_name, name)) for name in names) class indeterminate_type: