Skip to content

Commit ff2b79a

Browse files
committed
Distinguish between nil and derived in validate.py
1 parent 6317223 commit ff2b79a

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

src/ifcopenshell-python/ifcopenshell/validate.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,16 @@ def validate(f, logger):
284284
It is recommended to supply the path to the file, so that internal C++ errors reported during the parse stage
285285
are also captured.
286286
"""
287+
288+
# Originally there was no way in Python to distinguish on an entity instance attribute value whether the
289+
# value supplied in the model was NIL ($) or 'missing because derived in subtype' (*). For validation this
290+
# however this may be important, and hence a feature switch has been implemented to return *-values as
291+
# instances of a dedicated type `ifcopenshell.ifcopenshell_wrapper.attribute_value_derived`.
292+
attribute_value_derived_org = ifcopenshell.ifcopenshell_wrapper.get_feature('use_attribute_value_derived')
293+
ifcopenshell.ifcopenshell_wrapper.set_feature('use_attribute_value_derived', True)
294+
287295
filename = None
296+
288297
if not isinstance(f, ifcopenshell.file):
289298

290299
# get_log() clears log existing output
@@ -335,7 +344,18 @@ def validate(f, logger):
335344
zip(attrs, values, entity.derived())
336345
):
337346

338-
if val is None and not (is_derived or attr.optional()):
347+
if is_derived and not isinstance(val, ifcopenshell.ifcopenshell_wrapper.attribute_value_derived):
348+
if hasattr(logger, "set_instance"):
349+
logger.error("Attribute %s.%s is derived in subtype", entity, attr)
350+
else:
351+
logger.error(
352+
"For instance:\n %s\n %s\nWith attribute:\n %s\nDerived in subtype\n",
353+
inst,
354+
annotate_inst_attr_pos(inst, i),
355+
attr,
356+
)
357+
358+
if val is None and not attr.optional() and not is_derived:
339359
if hasattr(logger, "set_instance"):
340360
logger.error("Attribute %s.%s not optional", entity, attr)
341361
else:
@@ -346,7 +366,7 @@ def validate(f, logger):
346366
attr,
347367
)
348368

349-
if val is not None:
369+
if val is not None and not is_derived:
350370
attr_type = attr.type_of_attribute()
351371
try:
352372
assert_valid(attr_type, val, schema, attr=attr)
@@ -379,6 +399,8 @@ def validate(f, logger):
379399
# are verified.
380400
log_internal_cpp_errors(filename, logger)
381401

402+
# Restore the original value for 'use_attribute_value_derived'
403+
ifcopenshell.ifcopenshell_wrapper.set_feature('use_attribute_value_derived', attribute_value_derived_org)
382404

383405
if __name__ == "__main__":
384406
import sys

src/ifcwrap/IfcParseWrapper.i

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,41 @@ private:
5454
%rename("add") addEntity;
5555
%rename("remove") removeEntity;
5656

57+
class attribute_value_derived {};
5758
%{
59+
class attribute_value_derived {};
60+
%}
61+
62+
%extend attribute_value_derived {
63+
%pythoncode %{
64+
def __bool__(self): return False
65+
def __repr__(self): return '*'
66+
%}
67+
}
68+
69+
%inline %{
70+
static bool feature_use_attribute_value_derived = false;
71+
72+
void set_feature(const std::string& x, PyObject* v) {
73+
if (PyBool_Check(v) && x == "use_attribute_value_derived") {
74+
feature_use_attribute_value_derived = v == Py_True;
75+
} else {
76+
throw std::runtime_error("Invalid feature specification");
77+
}
78+
}
79+
80+
PyObject* get_feature(const std::string& x) {
81+
if (x == "use_attribute_value_derived") {
82+
return PyBool_FromLong(feature_use_attribute_value_derived);
83+
} else {
84+
throw std::runtime_error("Invalid feature specification");
85+
}
86+
}
87+
88+
%}
89+
90+
%{
91+
5892
static const std::string& helper_fn_declaration_get_name(const IfcParse::declaration* decl) {
5993
return decl->name();
6094
}

src/ifcwrap/utils/typemaps_out.i

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,16 @@
3838
try {
3939
const Argument& arg = *($1.second);
4040
const IfcUtil::ArgumentType type = $1.first;
41-
if (arg.isNull() || type == IfcUtil::Argument_DERIVED) {
41+
if (arg.isNull()) {
4242
Py_INCREF(Py_None);
4343
$result = Py_None;
44+
} else if (type == IfcUtil::Argument_DERIVED) {
45+
if (feature_use_attribute_value_derived) {
46+
$result = SWIG_NewPointerObj(new attribute_value_derived, SWIGTYPE_p_attribute_value_derived, SWIG_POINTER_OWN);
47+
} else {
48+
Py_INCREF(Py_None);
49+
$result = Py_None;
50+
}
4451
} else {
4552
switch(type) {
4653
case IfcUtil::Argument_INT: {

0 commit comments

Comments
 (0)