Skip to content

Commit f7adeb9

Browse files
committed
parse units and precision when a file is opened from the python interface
1 parent 607f7bd commit f7adeb9

File tree

5 files changed

+149
-138
lines changed

5 files changed

+149
-138
lines changed

src/ifcgeom/IfcGeom.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ namespace IfcGeom {
8282
const int DISABLE_OPENING_SUBTRACTIONS = 1 << 0;
8383
const int DISABLE_OBJECT_PLACEMENT = 1 << 1;
8484
const int SEW_SHELLS = 1 << 2;
85+
const int CONVERT_TO_METERS = 1 << 3;
8586

8687
bool convert_wire_to_face(const TopoDS_Wire& wire, TopoDS_Face& face);
8788
bool convert_curve_to_wire(const Handle(Geom_Curve)& curve, TopoDS_Wire& wire);
@@ -107,7 +108,8 @@ namespace IfcGeom {
107108
void apply_tolerance(TopoDS_Shape& s, double t);
108109
void SetValue(GeomValue var, double value);
109110
double GetValue(GeomValue var);
110-
std::string create_brep_data(Ifc2x3::IfcProduct* s, unsigned int settings);
111+
std::string create_brep_data(IfcSchema::IfcProduct* s, unsigned int settings);
112+
void initialize_units_and_precision(IfcSchema::IfcProject* proj, double& unit_magnitude, std::string& unit_name);
111113
bool fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape);
112114
IfcSchema::IfcProductDefinitionShape* tesselate(TopoDS_Shape& shape, double deflection, IfcEntities es);
113115

src/ifcgeom/IfcGeomFunctions.cpp

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -690,10 +690,11 @@ bool IfcGeom::fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape) {
690690
return true;
691691
}
692692

693-
std::string IfcGeom::create_brep_data(Ifc2x3::IfcProduct* ifc_product, unsigned int settings) {
693+
std::string IfcGeom::create_brep_data(IfcSchema::IfcProduct* ifc_product, unsigned int settings) {
694694
const bool no_openings = !!(settings & DISABLE_OPENING_SUBTRACTIONS);
695695
const bool no_placement = !!(settings & DISABLE_OBJECT_PLACEMENT);
696696
const bool sew_shells = !!(settings & SEW_SHELLS);
697+
const bool convert_to_meters = !!(settings & CONVERT_TO_METERS);
697698

698699
const double old_max_faces_to_sew = GetValue(GV_MAX_FACES_TO_SEW);
699700
const double inf = std::numeric_limits<double>::infinity();
@@ -703,13 +704,13 @@ std::string IfcGeom::create_brep_data(Ifc2x3::IfcProduct* ifc_product, unsigned
703704

704705
if (ifc_product->hasRepresentation()) {
705706

706-
Ifc2x3::IfcProductRepresentation* prod_rep = ifc_product->Representation();
707-
Ifc2x3::IfcRepresentation::list li = prod_rep->Representations();
708-
Ifc2x3::IfcShapeRepresentation* shape_rep = 0;
709-
for (Ifc2x3::IfcRepresentation::it i = li->begin(); i != li->end(); ++i) {
707+
IfcSchema::IfcProductRepresentation* prod_rep = ifc_product->Representation();
708+
IfcSchema::IfcRepresentation::list li = prod_rep->Representations();
709+
IfcSchema::IfcShapeRepresentation* shape_rep = 0;
710+
for (IfcSchema::IfcRepresentation::it i = li->begin(); i != li->end(); ++i) {
710711
const std::string representation_identifier = (*i)->RepresentationIdentifier();
711-
if ((*i)->is(Ifc2x3::Type::IfcShapeRepresentation) && (representation_identifier == "Body" || representation_identifier == "Facetation")) {
712-
shape_rep = (Ifc2x3::IfcShapeRepresentation*) *i;
712+
if ((*i)->is(IfcSchema::Type::IfcShapeRepresentation) && (representation_identifier == "Body" || representation_identifier == "Facetation")) {
713+
shape_rep = (IfcSchema::IfcShapeRepresentation*) *i;
713714
break;
714715
}
715716
}
@@ -781,6 +782,11 @@ std::string IfcGeom::create_brep_data(Ifc2x3::IfcProduct* ifc_product, unsigned
781782
BRepBuilderAPI_GTransform(s,trsf,true).Shape();
782783
builder.Add(compound,moved_shape);
783784
}
785+
if (GetValue(GV_LENGTH_UNIT) != 1. && !convert_to_meters) {
786+
gp_Trsf length_unit_scale;
787+
length_unit_scale.SetScaleFactor(1. / GetValue(GV_LENGTH_UNIT));
788+
compound.Move(length_unit_scale);
789+
}
784790
std::stringstream sstream;
785791
BRepTools::Write(compound,sstream);
786792
brep_data = sstream.str();
@@ -791,4 +797,124 @@ std::string IfcGeom::create_brep_data(Ifc2x3::IfcProduct* ifc_product, unsigned
791797
SetValue(GV_MAX_FACES_TO_SEW, old_max_faces_to_sew);
792798

793799
return brep_data;
800+
}
801+
802+
double UnitPrefixToValue( IfcSchema::IfcSIPrefix::IfcSIPrefix v ) {
803+
if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_EXA ) return (double) 1e18;
804+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_PETA ) return (double) 1e15;
805+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_TERA ) return (double) 1e12;
806+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_GIGA ) return (double) 1e9;
807+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MEGA ) return (double) 1e6;
808+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_KILO ) return (double) 1e3;
809+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_HECTO ) return (double) 1e2;
810+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_DECA ) return (double) 1;
811+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_DECI ) return (double) 1e-1;
812+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_CENTI ) return (double) 1e-2;
813+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MILLI ) return (double) 1e-3;
814+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MICRO ) return (double) 1e-6;
815+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_NANO ) return (double) 1e-9;
816+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_PICO ) return (double) 1e-12;
817+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_FEMTO ) return (double) 1e-15;
818+
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_ATTO ) return (double) 1e-18;
819+
else return 1.0f;
820+
}
821+
822+
void IfcGeom::initialize_units_and_precision(IfcSchema::IfcProject* proj, double& unit_magnitude, std::string& unit_name) {
823+
824+
// IfcOpenShell measures internally in meters, therefore a conversion factor
825+
// is obtained based on the length unit in the file.
826+
827+
// Set default units, set length to meters, angles to undefined
828+
IfcGeom::SetValue(IfcGeom::GV_LENGTH_UNIT,1.0);
829+
IfcGeom::SetValue(IfcGeom::GV_PLANEANGLE_UNIT,-1.0);
830+
831+
IfcUtil::IfcAbstractSelect::list units = IfcUtil::IfcAbstractSelect::list();
832+
try {
833+
IfcSchema::IfcUnitAssignment* unit_assignment = proj->UnitsInContext();
834+
units = unit_assignment->Units();
835+
} catch (const IfcParse::IfcException&) {}
836+
837+
try {
838+
for ( IfcUtil::IfcAbstractSelect::it it = units->begin(); it != units->end(); ++ it ) {
839+
std::string current_unit_name = "";
840+
const IfcUtil::IfcAbstractSelect::ptr base = *it;
841+
IfcSchema::IfcSIUnit::ptr unit = IfcSchema::IfcSIUnit::ptr();
842+
double value = 1.0f;
843+
if ( base->is(IfcSchema::Type::IfcConversionBasedUnit) ) {
844+
const IfcSchema::IfcConversionBasedUnit::ptr u = reinterpret_pointer_cast<IfcUtil::IfcAbstractSelect,IfcSchema::IfcConversionBasedUnit>(base);
845+
current_unit_name = u->Name();
846+
const IfcSchema::IfcMeasureWithUnit::ptr u2 = u->ConversionFactor();
847+
IfcSchema::IfcUnit u3 = u2->UnitComponent();
848+
if ( u3->is(IfcSchema::Type::IfcSIUnit) ) {
849+
unit = (IfcSchema::IfcSIUnit*) u3;
850+
}
851+
IfcSchema::IfcValue v = u2->ValueComponent();
852+
const double f = *((IfcUtil::IfcBaseEntity*)v)->entity->getArgument(0);
853+
value *= f;
854+
} else if ( base->is(IfcSchema::Type::IfcSIUnit) ) {
855+
unit = reinterpret_pointer_cast<IfcUtil::IfcAbstractSelect,IfcSchema::IfcSIUnit>(base);
856+
}
857+
if ( unit ) {
858+
if ( unit->hasPrefix() ) {
859+
value *= UnitPrefixToValue(unit->Prefix());
860+
}
861+
IfcSchema::IfcUnitEnum::IfcUnitEnum type = unit->UnitType();
862+
if ( type == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT ) {
863+
IfcGeom::SetValue(IfcGeom::GV_LENGTH_UNIT,value);
864+
if (current_unit_name.empty()) {
865+
if (unit->hasPrefix()) {
866+
current_unit_name = IfcSchema::IfcSIPrefix::ToString(unit->Prefix());
867+
}
868+
current_unit_name += IfcSchema::IfcSIUnitName::ToString(unit->Name());
869+
}
870+
unit_magnitude = value;
871+
unit_name = current_unit_name;
872+
} else if ( type == IfcSchema::IfcUnitEnum::IfcUnit_PLANEANGLEUNIT ) {
873+
IfcGeom::SetValue(IfcGeom::GV_PLANEANGLE_UNIT,value);
874+
}
875+
}
876+
}
877+
} catch (const IfcParse::IfcException& ex) {
878+
std::stringstream ss;
879+
ss << "Failed to determine unit information '" << ex.what() << "'";
880+
Logger::Message(Logger::LOG_ERROR, ss.str());
881+
}
882+
883+
// Boolean operations rely on a precision value for vertex and face coincedence. This
884+
// value is obtained from the IfcGeometricRepresentationContexts in the file.
885+
886+
// Set an initial guess in case reading from file fails.
887+
IfcGeom::SetValue(IfcGeom::GV_PRECISION, 0.00001);
888+
889+
try {
890+
IfcSchema::IfcRepresentationContext::list rep_contexts = proj->RepresentationContexts();
891+
// Currently, IfcGeometricRepresentationContext aren't used as much as they should be
892+
// in the evaluation of shape representations, hence, we try to find the one with the
893+
// lowest precision. Typically, a value of 1e-5 is encountered. This value is applied
894+
// to all TopoDS_Shapes generated by one of the IfcGeom::convert() functions.
895+
// TODO: Many of the empirically found tolerances should probably be substituted by
896+
// one that is defined in the model file.
897+
double lowest_precision_encountered = std::numeric_limits<double>::infinity();
898+
bool any_precision_encountered = false;
899+
for (IfcSchema::IfcRepresentationContext::it it = rep_contexts->begin(); it != rep_contexts->end(); ++it) {
900+
if ((*it)->is(IfcSchema::Type::IfcGeometricRepresentationContext)) {
901+
IfcSchema::IfcGeometricRepresentationContext* rep_context = (IfcSchema::IfcGeometricRepresentationContext*)*it;
902+
if (rep_context->is(IfcSchema::Type::IfcGeometricRepresentationSubContext)) continue;
903+
if (rep_context->hasPrecision()) {
904+
const double precision = rep_context->Precision();
905+
if (precision < lowest_precision_encountered) {
906+
any_precision_encountered = true;
907+
lowest_precision_encountered = precision;
908+
}
909+
}
910+
}
911+
}
912+
if (any_precision_encountered) {
913+
IfcGeom::SetValue(IfcGeom::GV_PRECISION, lowest_precision_encountered);
914+
}
915+
} catch (const IfcParse::IfcException& ex) {
916+
std::stringstream ss;
917+
ss << "Failed to determine precision value '" << ex.what() << "'";
918+
Logger::Message(Logger::LOG_ERROR, ss.str());
919+
}
794920
}

src/ifcgeom/IfcGeomObjects.cpp

Lines changed: 5 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -587,140 +587,16 @@ const IfcGeomObjects::IfcGeomBrepDataObject* IfcGeomObjects::GetBrepData() {
587587
}
588588
return current_brep_data_obj;
589589
}
590-
double UnitPrefixToValue( IfcSchema::IfcSIPrefix::IfcSIPrefix v ) {
591-
if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_EXA ) return (double) 1e18;
592-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_PETA ) return (double) 1e15;
593-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_TERA ) return (double) 1e12;
594-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_GIGA ) return (double) 1e9;
595-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MEGA ) return (double) 1e6;
596-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_KILO ) return (double) 1e3;
597-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_HECTO ) return (double) 1e2;
598-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_DECA ) return (double) 1;
599-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_DECI ) return (double) 1e-1;
600-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_CENTI ) return (double) 1e-2;
601-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MILLI ) return (double) 1e-3;
602-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_MICRO ) return (double) 1e-6;
603-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_NANO ) return (double) 1e-9;
604-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_PICO ) return (double) 1e-12;
605-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_FEMTO ) return (double) 1e-15;
606-
else if ( v == IfcSchema::IfcSIPrefix::IfcSIPrefix_ATTO ) return (double) 1e-18;
607-
else return 1.0f;
608-
}
609-
610-
void IfcGeomObjects::InitPrecision() {
611-
IfcGeom::SetValue(IfcGeom::GV_PRECISION, 0.00001);
612-
613-
try {
614-
IfcSchema::IfcGeometricRepresentationContext::list rep_contexts = ifc_file->EntitiesByType<IfcSchema::IfcGeometricRepresentationContext>();
615-
// Currently, IfcGeometricRepresentationContext aren't used as much as they should be
616-
// in the evaluation of shape representations, hence, we try to find the one with the
617-
// lowest precision. Typically, a value of 1e-5 is encountered. This value is applied
618-
// to all TopoDS_Shapes generated by one of the IfcGeom::convert() functions.
619-
// TODO: Many of the empirically found tolerances should probably be substituted by
620-
// one that is defined in the model file.
621-
double lowest_precision_encountered = std::numeric_limits<double>::infinity();
622-
bool any_precision_encountered = false;
623-
for (IfcSchema::IfcGeometricRepresentationContext::it it = rep_contexts->begin(); it != rep_contexts->end(); ++it) {
624-
IfcSchema::IfcGeometricRepresentationContext* rep_context = *it;
625-
if (rep_context->is(IfcSchema::Type::IfcGeometricRepresentationSubContext)) continue;
626-
if (rep_context->hasPrecision()) {
627-
const double precision = rep_context->Precision();
628-
if (precision < lowest_precision_encountered) {
629-
any_precision_encountered = true;
630-
lowest_precision_encountered = precision;
631-
}
632-
}
633-
}
634-
if (any_precision_encountered) {
635-
IfcGeom::SetValue(IfcGeom::GV_PRECISION, lowest_precision_encountered);
636-
}
637-
} catch (const IfcParse::IfcException& ex) {
638-
std::stringstream ss;
639-
ss << "Failed to determine precision value '" << ex.what() << "'";
640-
Logger::Message(Logger::LOG_ERROR, ss.str());
641-
}
642-
}
643590

644591
static std::string unit_name = "METER";
645592
static float unit_magnitude = 1.0f;
646593

647594
void IfcGeomObjects::InitUnits() {
648-
// Set default units, set length to meters, angles to undefined
649-
IfcGeom::SetValue(IfcGeom::GV_LENGTH_UNIT,1.0);
650-
IfcGeom::SetValue(IfcGeom::GV_PLANEANGLE_UNIT,-1.0);
651-
652-
IfcSchema::IfcUnitAssignment::list unit_assignments = ifc_file->EntitiesByType<IfcSchema::IfcUnitAssignment>();
653-
IfcUtil::IfcAbstractSelect::list units = IfcUtil::IfcAbstractSelect::list();
654-
try {
655-
if ( unit_assignments->Size() ) {
656-
IfcSchema::IfcUnitAssignment::ptr unit_assignment = *unit_assignments->begin();
657-
units = unit_assignment->Units();
658-
}
659-
} catch (const IfcParse::IfcException&) {}
660-
661-
if (!units) {
662-
// No units eh... Since tolerances and deflection are specified internally in meters
663-
// we will try to find another indication of the model size.
664-
IfcSchema::IfcExtrudedAreaSolid::list extrusions = ifc_file->EntitiesByType<IfcSchema::IfcExtrudedAreaSolid>();
665-
if ( ! extrusions->Size() ) return;
666-
double max_height = -1.0f;
667-
for ( IfcSchema::IfcExtrudedAreaSolid::it it = extrusions->begin(); it != extrusions->end(); ++ it ) {
668-
try {
669-
const double depth = (*it)->Depth();
670-
if ( depth > max_height ) max_height = depth;
671-
} catch (const IfcParse::IfcException&) {}
672-
}
673-
if ( max_height > 100.0f ) {
674-
IfcGeom::SetValue(IfcGeom::GV_LENGTH_UNIT,0.001);
675-
Logger::Message(Logger::LOG_NOTICE, "Guessed length unit to be in millimeters based on extrusion depth");
676-
}
677-
return;
678-
}
679-
680-
try {
681-
for ( IfcUtil::IfcAbstractSelect::it it = units->begin(); it != units->end(); ++ it ) {
682-
std::string current_unit_name = "";
683-
const IfcUtil::IfcAbstractSelect::ptr base = *it;
684-
IfcSchema::IfcSIUnit::ptr unit = IfcSchema::IfcSIUnit::ptr();
685-
double value = 1.0f;
686-
if ( base->is(IfcSchema::Type::IfcConversionBasedUnit) ) {
687-
const IfcSchema::IfcConversionBasedUnit::ptr u = reinterpret_pointer_cast<IfcUtil::IfcAbstractSelect,IfcSchema::IfcConversionBasedUnit>(base);
688-
current_unit_name = u->Name();
689-
const IfcSchema::IfcMeasureWithUnit::ptr u2 = u->ConversionFactor();
690-
IfcSchema::IfcUnit u3 = u2->UnitComponent();
691-
if ( u3->is(IfcSchema::Type::IfcSIUnit) ) {
692-
unit = (IfcSchema::IfcSIUnit*) u3;
693-
}
694-
IfcSchema::IfcValue v = u2->ValueComponent();
695-
const double f = *((IfcUtil::IfcBaseEntity*)v)->entity->getArgument(0);
696-
value *= f;
697-
} else if ( base->is(IfcSchema::Type::IfcSIUnit) ) {
698-
unit = reinterpret_pointer_cast<IfcUtil::IfcAbstractSelect,IfcSchema::IfcSIUnit>(base);
699-
}
700-
if ( unit ) {
701-
if ( unit->hasPrefix() ) {
702-
value *= UnitPrefixToValue(unit->Prefix());
703-
}
704-
IfcSchema::IfcUnitEnum::IfcUnitEnum type = unit->UnitType();
705-
if ( type == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT ) {
706-
IfcGeom::SetValue(IfcGeom::GV_LENGTH_UNIT,value);
707-
if (current_unit_name.empty()) {
708-
if (unit->hasPrefix()) {
709-
current_unit_name = IfcSchema::IfcSIPrefix::ToString(unit->Prefix());
710-
}
711-
current_unit_name += IfcSchema::IfcSIUnitName::ToString(unit->Name());
712-
}
713-
unit_magnitude = value;
714-
unit_name = current_unit_name;
715-
} else if ( type == IfcSchema::IfcUnitEnum::IfcUnit_PLANEANGLEUNIT ) {
716-
IfcGeom::SetValue(IfcGeom::GV_PLANEANGLE_UNIT,value);
717-
}
718-
}
719-
}
720-
} catch (const IfcParse::IfcException& ex) {
721-
std::stringstream ss;
722-
ss << "Failed to determine unit information '" << ex.what() << "'";
723-
Logger::Message(Logger::LOG_ERROR, ss.str());
595+
IfcSchema::IfcProject::list projects = ifc_file->EntitiesByType<IfcSchema::IfcProject>();
596+
if (projects->Size() == 1) {
597+
double unit_magnitude_double;
598+
IfcGeom::initialize_units_and_precision(*projects->begin(), unit_magnitude_double, unit_name);
599+
unit_magnitude = static_cast<float>(unit_magnitude_double);
724600
}
725601
}
726602

@@ -729,7 +605,6 @@ bool IfcGeomObjects::Init(const std::string fn) {
729605
}
730606
bool _Init() {
731607
IfcGeomObjects::InitUnits();
732-
IfcGeomObjects::InitPrecision();
733608

734609
shapereps = ifc_file->EntitiesByType<IfcSchema::IfcShapeRepresentation>();
735610
if ( ! shapereps ) return false;

src/ifcwrap/Interface.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ namespace IfcParse {
9999
IfcParse::IfcFile* open(const std::string& s) {
100100
IfcParse::IfcFile* f = new IfcParse::IfcFile();
101101
f->Init(s);
102+
IfcEntities projects = f->EntitiesByType("IfcProject");
103+
if (projects->Size() == 1) {
104+
double unit_magnitude;
105+
std::string unit_name;
106+
IfcGeom::initialize_units_and_precision((IfcSchema::IfcProject*)*projects->begin(), unit_magnitude, unit_name);
107+
}
102108
return f;
103109
}
104110

@@ -115,6 +121,7 @@ namespace IfcParse {
115121
const int DISABLE_OPENING_SUBTRACTIONS = 1 << 0;
116122
const int DISABLE_OBJECT_PLACEMENT = 1 << 1;
117123
const int SEW_SHELLS = 1 << 2;
124+
const int CONVERT_TO_METERS = 1 << 3;
118125
}
119126

120127
std::ostream& operator<< (std::ostream& os, const IfcParse::IfcFile& f);

src/ifcwrap/ifc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,4 @@ def clean(): return ifc_wrapper.clean()
104104
DISABLE_OPENING_SUBTRACTIONS = ifc_wrapper.DISABLE_OPENING_SUBTRACTIONS
105105
DISABLE_OBJECT_PLACEMENT = ifc_wrapper.DISABLE_OBJECT_PLACEMENT
106106
SEW_SHELLS = ifc_wrapper.SEW_SHELLS
107+
CONVERT_TO_METERS = ifc_wrapper.CONVERT_TO_METERS

0 commit comments

Comments
 (0)