From c52f851d79793a6ecfba1821d9925544b8d63a76 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Thu, 2 Dec 2021 11:05:36 +0100 Subject: [PATCH] #1807 method mapping proposal --- .../ifcopenshell/entity_instance.py | 86 ++++++++++++------- src/ifcparse/IfcSchema.cpp | 15 ++++ src/ifcparse/IfcSchema.h | 4 +- src/ifcwrap/IfcParseWrapper.i | 15 ++-- 4 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/entity_instance.py b/src/ifcopenshell-python/ifcopenshell/entity_instance.py index 2b5015cb85..a9d5b81a06 100644 --- a/src/ifcopenshell-python/ifcopenshell/entity_instance.py +++ b/src/ifcopenshell-python/ifcopenshell/entity_instance.py @@ -33,6 +33,51 @@ except ImportError as e: logging = type("logger", (object,), {"exception": staticmethod(lambda s: print(s))}) +def set_derived_atribute(*args): + raise TypeError("Unable to set derived attribute") + + +# For every schema and its entities populate a list +# of functions for every entity attribute (including +# inherited attributes) to set that particular +# attribute by index. +# For example. IFC2X3.IfcWall with have a list of +# 9 methods. The first will point at +# ifcopenshell.ifcopenshell_wrapper.entity_instance.setArgumentAsString +# because the first attribute GlobalId ultimately +# is of type string. +# Previously, resolving the appropriate function was +# done for each invocation of __setitem__. Now this +# mapping is built once during initialization of the +# module. +_method_dict = {} +for nm in ifcopenshell_wrapper.schema_names(): + schema = ifcopenshell_wrapper.schema_by_name(nm) + for decl in schema.declarations(): + if isinstance(decl, ifcopenshell_wrapper.entity): + fq_name = ".".join((nm, decl.name())) + + # get type strings as reported by IfcOpenShell C++ + type_strs = decl.argument_types() + + # convert case for setter function + type_strs = [x.title().replace(" ", "") for x in type_strs] + + # binary and enumeration are passed from python as string as well + type_strs = [x.replace("Binary", "String") for x in type_strs] + type_strs = [x.replace("Enumeration", "String") for x in type_strs] + + # prefix to get method names + fn_names = ["setArgumentAs" + x for x in type_strs] + + # resolve to actual functions in wrapper + functions = [ + set_derived_atribute if mname == "setArgumentAsDerived" else getattr(ifcopenshell_wrapper.entity_instance, mname) \ + for mname in fn_names] + + _method_dict[fq_name] = functions + + class entity_instance(object): """This is the base Python class for all IFC objects. @@ -52,6 +97,7 @@ class entity_instance(object): if isinstance(e, tuple): e = ifcopenshell_wrapper.new_IfcBaseClass(*e) super(entity_instance, self).__setattr__("wrapped_data", e) + super(entity_instance, self).__setattr__("method_list", None) self.wrapped_data.file = file def __getattr__(self, name): @@ -65,7 +111,7 @@ class entity_instance(object): return entity_instance.wrap_value(self.wrapped_data.get_inverse(name), self.wrapped_data.file) else: raise AttributeError( - "entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(), name) + "entity instance of type '%s' has no attribute '%s'" % (self.wrapped_data.is_a(True), name) ) @staticmethod @@ -129,38 +175,16 @@ class entity_instance(object): if self.wrapped_data.file and self.wrapped_data.file.transaction: self.wrapped_data.file.transaction.store_edit(self, idx, value) - attr_type = real_attr_type = self.attribute_type(idx).title().replace(" ", "") - real_attr_type = real_attr_type.replace("Derived", "None") - attr_type = attr_type.replace("Binary", "String") - attr_type = attr_type.replace("Enumeration", "String") - + if self.method_list is None: + super(entity_instance, self).__setattr__("method_list", _method_dict[self.is_a(True)]) + + method = self.method_list[idx] + if value is None: - if attr_type != "Derived": + if method is not set_derived_atribute: self.wrapped_data.setArgumentAsNull(idx) - else: - valid = attr_type != "Derived" - if valid: - try: - if isinstance(value, unicode): - value = value.encode("utf-8") - except BaseException: - pass - - try: - if attr_type != "Derived": - getattr(self.wrapped_data, "setArgumentAs%s" % attr_type)( - idx, entity_instance.unwrap_value(value) - ) - except BaseException as e: - import traceback - traceback.print_exc() - valid = False - - if not valid: - raise ValueError( - "Expected %s for attribute %s.%s, got %r" - % (real_attr_type, self.is_a(), self.attribute_name(idx), value) - ) + else: + self.method_list[idx](self.wrapped_data, idx, entity_instance.unwrap_value(value)) return value diff --git a/src/ifcparse/IfcSchema.cpp b/src/ifcparse/IfcSchema.cpp index 758d15780f..536c1c00e0 100644 --- a/src/ifcparse/IfcSchema.cpp +++ b/src/ifcparse/IfcSchema.cpp @@ -134,3 +134,18 @@ const IfcParse::schema_definition* IfcParse::schema_by_name(const std::string& n } return it->second; } + +std::vector IfcParse::schema_names() { + // Load schema modules + try { + IfcParse::schema_by_name("IFC2X3"); + } catch (IfcParse::IfcException&) {} + + // Populate vector with map keys + std::vector return_value; + for (auto& pair : schemas) { + return_value.push_back(pair.first); + } + + return return_value; +} diff --git a/src/ifcparse/IfcSchema.h b/src/ifcparse/IfcSchema.h index 3e32f5d1d7..d50ab8ba77 100644 --- a/src/ifcparse/IfcSchema.h +++ b/src/ifcparse/IfcSchema.h @@ -448,7 +448,9 @@ namespace IfcParse { IFC_PARSE_API const schema_definition* schema_by_name(const std::string&); - void register_schema(schema_definition*); + IFC_PARSE_API std::vector schema_names(); + + IFC_PARSE_API void register_schema(schema_definition*); } #endif diff --git a/src/ifcwrap/IfcParseWrapper.i b/src/ifcwrap/IfcParseWrapper.i index 9db87a8291..51a3badb3d 100644 --- a/src/ifcwrap/IfcParseWrapper.i +++ b/src/ifcwrap/IfcParseWrapper.i @@ -225,8 +225,12 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas return self->declaration().is(s); } - std::string is_a() const { - return self->declaration().name(); + std::string is_a(bool with_schema=false) const { + auto t = self->declaration().name(); + if (with_schema) { + t = self->declaration().schema()->name() + "." + t; + } + return t; } std::pair get_argument(unsigned i) { @@ -661,11 +665,12 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas auto pt = attr->type_of_attribute(); if ($self->derived()[i++]) { at = IfcUtil::Argument_DERIVED; - } - if (pt == 0) { + } else if (!pt) { at = IfcUtil::Argument_UNKNOWN; + } else { + at = IfcUtil::from_parameter_type(pt); } - r.push_back(IfcUtil::ArgumentTypeToString(IfcUtil::from_parameter_type(pt))); + r.push_back(IfcUtil::ArgumentTypeToString(at)); } return r; }