diff --git a/src/ifcgeom/ConversionSettings.cpp b/src/ifcgeom/ConversionSettings.cpp index 0af9262474..48bdff2643 100644 --- a/src/ifcgeom/ConversionSettings.cpp +++ b/src/ifcgeom/ConversionSettings.cpp @@ -78,3 +78,19 @@ std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, Out } return in; } + +std::istream& ifcopenshell::geometry::settings::operator>>(std::istream& in, TriangulationMethod& v) { + std::string token; + in >> token; + boost::to_upper(token); + if (token == "TRIANGLE_MESH") { + v = TRIANGLE_MESH; + } else if (token == "POLYHEDRON_WITHOUT_HOLES") { + v = POLYHEDRON_WITHOUT_HOLES; + } else if (token == "POLYHEDRON_WITH_HOLES") { + v = POLYHEDRON_WITH_HOLES; + } else { + in.setstate(std::ios_base::failbit); + } + return in; +} diff --git a/src/ifcgeom/ConversionSettings.h b/src/ifcgeom/ConversionSettings.h index b1f6fe18b4..a3bbf5739b 100644 --- a/src/ifcgeom/ConversionSettings.h +++ b/src/ifcgeom/ConversionSettings.h @@ -378,12 +378,28 @@ namespace ifcopenshell { static constexpr const char* const name = "model-rotation"; static constexpr const char* const description = "Applies an arbitrary quaternion rotation of form 'x,y,z,w' to all placements."; }; + + enum TriangulationMethod { + TRIANGLE_MESH, + POLYHEDRON_WITHOUT_HOLES, + POLYHEDRON_WITH_HOLES + }; + + std::istream& operator>>(std::istream& in, TriangulationMethod& ioo); + + struct TriangulationType : public SettingBase { + static constexpr const char* const name = "triangulation-type"; + static constexpr const char* const description = "Type of planar facet to be emitted"; + static constexpr TriangulationMethod defaultvalue = TRIANGLE_MESH; + }; + + } template class IFC_GEOM_API SettingsContainer { public: - typedef boost::variant, std::set, std::vector, IteratorOutputOptions, PiecewiseStepMethod, OutputDimensionalityTypes> value_variant_t; + typedef boost::variant, std::set, std::vector, IteratorOutputOptions, PiecewiseStepMethod, OutputDimensionalityTypes, TriangulationMethod> value_variant_t; private: settings_t settings; @@ -470,73 +486,12 @@ namespace ifcopenshell { }; class IFC_GEOM_API Settings : public SettingsContainer< - std::tuple + std::tuple > {}; } } -// namespace ifcopenshell { -// namespace geometry { -// -// class IFC_GEOM_API ConversionSettings { -// public: -// // Tolerances and settings for various geometrical operations: -// enum GeomValue { -// // -// // Default: 0.001m / 1mm -// GV_DEFLECTION_TOLERANCE, -// -// // The length unit used the creation of TopoDS_Shapes, primarily affects the -// // interpretation of IfcCartesianPoints and IfcVector magnitudes -// // DefaultL 1.0 -// GV_LENGTH_UNIT, -// // The plane angle unit used for the creation of TopoDS_Shapes, primarily affects -// // the interpretation of IfcParamaterValues of IfcTrimmedCurves -// // Default: -1.0 (= not set, fist try degrees, then radians) -// GV_PLANEANGLE_UNIT, -// // The precision used in boolean operations, setting this value too low results -// // in artefacts and potentially modelling failures -// // Default: 0.00001 (obtained from IfcGeometricRepresentationContext if available) -// GV_PRECISION, -// // Whether to process shapes of type Face or higher (1) Wire or lower (-1) or all (0) -// GV_DIMENSIONALITY, -// GV_LAYERSET_FIRST, -// GV_DISABLE_BOOLEAN_RESULT, -// GV_NO_WIRE_INTERSECTION_CHECK, -// GV_PRECISION_FACTOR, -// GV_NO_WIRE_INTERSECTION_TOLERANCE, -// GV_DEBUG_BOOLEAN, -// GV_BOOLEAN_ATTEMPT_2D, -// NUM_SETTINGS -// }; -// -// void setValue(GeomValue var, double value); -// -// double getValue(GeomValue var) const; -// -// private: -// std::array values_ = { -// /* deflection_tolerance = */ 0.001, -// // @todo make sure these 'read-only' variables work. -// /* minimal_face_area = */ std::numeric_limits::quiet_NaN(), -// /* max_faces_to_orient = */ -1.0, -// /* ifc_length_unit = */ 1.0, -// /* ifc_planeangle_unit = */ -1.0, -// /* modelling_precision = */ 0.00001, -// /* dimensionality = */ 1., -// /* layerset_first = */ -1., -// /* disable_boolean_result = */ -1. -// /* no_wire_intersection_check = */ -1., -// /* precision_factor = */ 10., -// /* no_wire_intersection_tolerance = */ -1., -// /* boolean_debug_setting = */ -1., -// /* boolean_attempt_2d = */ 1. -// }; -// }; -// } -// } - // @todo find a place namespace IfcGeom { class IFC_GEOM_API geometry_exception : public std::exception { diff --git a/src/ifcgeom/IfcGeomRepresentation.cpp b/src/ifcgeom/IfcGeomRepresentation.cpp index 24b1c7f319..c1457a8119 100644 --- a/src/ifcgeom/IfcGeomRepresentation.cpp +++ b/src/ifcgeom/IfcGeomRepresentation.cpp @@ -332,23 +332,23 @@ IfcGeom::Representation::Triangulation::Triangulation(const BRep& shape_model) int surface_style_id = -1; if (iit->hasStyle()) { - auto jt = std::find(_materials.begin(), _materials.end(), iit->StylePtr()); - if (jt == _materials.end()) { - surface_style_id = (int)_materials.size(); - _materials.push_back(iit->StylePtr()); + auto jt = std::find(materials_.begin(), materials_.end(), iit->StylePtr()); + if (jt == materials_.end()) { + surface_style_id = (int)materials_.size(); + materials_.push_back(iit->StylePtr()); } else { - surface_style_id = (int)(jt - _materials.begin()); + surface_style_id = (int)(jt - materials_.begin()); } } if (settings().get().get() && surface_style_id == -1) { const auto& material = IfcGeom::get_default_style(shape_model.entity()); - auto mit = std::find(_materials.begin(), _materials.end(), material); - if (mit == _materials.end()) { - surface_style_id = (int)_materials.size(); - _materials.push_back(material); + auto mit = std::find(materials_.begin(), materials_.end(), material); + if (mit == materials_.end()) { + surface_style_id = (int)materials_.size(); + materials_.push_back(material); } else { - surface_style_id = (int)(mit - _materials.begin()); + surface_style_id = (int)(mit - materials_.begin()); } } @@ -393,7 +393,7 @@ int IfcGeom::Representation::Triangulation::addVertex(int item_id, int material_ const double X = convert ? (pX /unit_magnitude) : pX; const double Y = convert ? (pY /unit_magnitude) : pY; const double Z = convert ? (pZ /unit_magnitude) : pZ; - int i = (int)_verts.size() / 3; + int i = (int)verts_.size() / 3; if (settings().get().get()) { const VertexKey key = std::make_tuple(item_id, material_index, X, Y, Z); typename VertexKeyMap::const_iterator it = welds.find(key); @@ -401,9 +401,9 @@ int IfcGeom::Representation::Triangulation::addVertex(int item_id, int material_ i = (int)(welds.size() + weld_offset_); welds[key] = i; } - _verts.push_back(X); - _verts.push_back(Y); - _verts.push_back(Z); + verts_.push_back(X); + verts_.push_back(Y); + verts_.push_back(Z); return i; } diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index 8efc98f659..8d137d8eb1 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -103,14 +103,20 @@ namespace IfcGeom { typedef std::map VertexKeyMap; typedef std::pair Edge; - std::vector _verts; - std::vector _faces; - std::vector _edges; - std::vector _normals; + std::vector verts_; + + // @nb only one of these is populated based on settings, we didn't want to go + // all in with templates or subtypes because of reduced ease of use. + std::vector faces_; + std::vector> polyhedral_faces_without_holes_; + std::vector>> polyhedral_faces_with_holes_; + + std::vector edges_; + std::vector normals_; std::vector uvs_; - std::vector _material_ids; - std::vector _materials; - std::vector _item_ids; + std::vector material_ids_; + std::vector materials_; + std::vector item_ids_; size_t weld_offset_; VertexKeyMap welds; @@ -120,15 +126,17 @@ namespace IfcGeom { {} public: - const std::vector& verts() const { return _verts; } - const std::vector& faces() const { return _faces; } - const std::vector& edges() const { return _edges; } - const std::vector& normals() const { return _normals; } + const std::vector& verts() const { return verts_; } + const std::vector& faces() const { return faces_; } + const std::vector>& polyhedral_faces_without_holes() const { return polyhedral_faces_without_holes_; } + const std::vector>>& polyhedral_faces_with_holes() const { return polyhedral_faces_with_holes_; } + const std::vector& edges() const { return edges_; } + const std::vector& normals() const { return normals_; } std::vector& uvs() { return uvs_; } const std::vector& uvs() const { return uvs_; } - const std::vector& material_ids() const { return _material_ids; } - const std::vector& materials() const { return _materials; } - const std::vector& item_ids() const { return _item_ids; } + const std::vector& material_ids() const { return material_ids_; } + const std::vector& materials() const { return materials_; } + const std::vector& item_ids() const { return item_ids_; } Triangulation(const BRep& shape_model); @@ -146,14 +154,14 @@ namespace IfcGeom { const std::vector& item_ids ) : Representation(settings, entity, id) - , _verts(verts) - , _faces(faces) - , _edges(edges) - , _normals(normals) + , verts_(verts) + , faces_(faces) + , edges_(edges) + , normals_(normals) , uvs_(uvs) - , _material_ids(material_ids) - , _materials(materials) - , _item_ids(item_ids) + , material_ids_(material_ids) + , materials_(materials) + , item_ids_(item_ids) {} virtual ~Triangulation() {} @@ -168,30 +176,44 @@ namespace IfcGeom { int addVertex(int item_index, int material_index, double X, double Y, double Z); void addNormal(double X, double Y, double Z) { - _normals.push_back(X); - _normals.push_back(Y); - _normals.push_back(Z); + normals_.push_back(X); + normals_.push_back(Y); + normals_.push_back(Z); } void addFace(int item_id, int style, int i0, int i1, int i2) { - _faces.push_back(i0); - _faces.push_back(i1); - _faces.push_back(i2); + faces_.push_back(i0); + faces_.push_back(i1); + faces_.push_back(i2); - _item_ids.push_back(item_id); - _material_ids.push_back(style); + item_ids_.push_back(item_id); + material_ids_.push_back(style); + } + + void addFace(int item_id, int style, const std::vector& outer_bound) { + polyhedral_faces_without_holes_.push_back(outer_bound); + + item_ids_.push_back(item_id); + material_ids_.push_back(style); + } + + void addFace(int item_id, int style, const std::vector>& bounds) { + polyhedral_faces_with_holes_.push_back(bounds); + + item_ids_.push_back(item_id); + material_ids_.push_back(style); } void addEdge(int style, int i0, int i1) { - _edges.push_back(i0); - _edges.push_back(i1); + edges_.push_back(i0); + edges_.push_back(i1); - _material_ids.push_back(style); + material_ids_.push_back(style); } void registerEdge(int i0, int i1) { - _edges.push_back(i0); - _edges.push_back(i1); + edges_.push_back(i0); + edges_.push_back(i1); } void addEdge(int n1, int n2, std::map, int>& edgecount); diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp index 1660f5928b..e15d28dc70 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "OpenCascadeConversionResult.h" @@ -15,6 +16,12 @@ #include +#include +#include +#include +#include +#include + #if OCC_VERSION_HEX >= 0x70600 #include #endif @@ -24,6 +31,44 @@ using IfcGeom::OpaqueCoordinate; using IfcGeom::NumberNativeDouble; using IfcGeom::ConversionResultShape; +struct EdgeKey { + int v1, v2; + + // These are not part of the hash or equality, + // but retained to easily created a directed + // graph of the original boundary edges. Since + // the boundary edges are exactly those with + // count=1 we don't need to worry about + // conflicting original vertex indices. + int ov1, ov2; + + EdgeKey(int a, int b) + : ov1(a) + , ov2(b) + { + if (a < b) { + v1 = a; + v2 = b; + } else { + v1 = b; + v2 = a; + } + } + + bool operator==(const EdgeKey& other) const { + return v1 == other.v1 && v2 == other.v2; + } +}; + +namespace std { + template <> + struct hash { + std::size_t operator()(const EdgeKey& ek) const { + return std::hash()(ek.v1) ^ std::hash()(ek.v2); + } + }; +} + namespace { // We bypass the conversion to gp_GTrsf, because it does not work void taxonomy_transform(const Eigen::Matrix4d* m, gp_XYZ& xyz) { @@ -35,6 +80,78 @@ namespace { xyz.ChangeData()[2] = v2(2); } } + + // Function to find boundary loops from triangles + std::vector> find_boundary_loops(const std::vector& positions, const std::vector>& triangles) { + std::unordered_map edge_count; + + // Count how many triangles each edge belongs to + for (const auto& triangle : triangles) { + int v1, v2, v3; + std::tie(v1, v2, v3) = triangle; + + edge_count[{v1, v2}]++; + edge_count[{v2, v3}]++; + edge_count[{v3, v1}]++; + } + + // Boundary edges have count 1 + std::vector boundary_edges; + for (auto& p : edge_count) { + if (p.second == 1) { + boundary_edges.push_back(p.first); + } + } + + // We retained original directed edges so we build + // a mapping out of these directed edges. + std::unordered_map vertex_successors; + for (const auto& e : boundary_edges) { + vertex_successors[e.ov1] = e.ov2; + } + + std::vector> loops; + while (!vertex_successors.empty()) { + loops.emplace_back(); + auto it = vertex_successors.begin(); + loops.back() = { it->first, it->second }; + vertex_successors.erase(it); + + int current = loops.back().back(); + while (!vertex_successors.empty() && current != loops.back().front()) { + auto next = vertex_successors[current]; + if (loops.back().front() != next) { + loops.back().push_back(next); + } + vertex_successors.erase(current); + current = next; + } + } + + // Sort the loops by smallest x-coord of their constituent positions + // In order to put the outermost loop in front + if (loops.size() > 1) { + std::vector> min_xs; + for (auto& l : loops) { + double min_x = std::numeric_limits::infinity(); + for (auto& i : l) { + const auto& x = positions[i * 3]; + if (x < min_x) { + min_x = x; + } + } + min_xs.push_back({ min_x, min_xs.size() }); + } + std::sort(min_xs.begin(), min_xs.end()); + decltype(loops) loops_copy; + for (auto& p : min_xs) { + loops_copy.emplace_back(std::move(loops[p.second])); + } + std::swap(loops, loops_copy); + } + + return loops; + } } void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const { @@ -71,6 +188,18 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr TopExp_Explorer exp; for (exp.Init(shape_, TopAbs_FACE); exp.More(); exp.Next(), ++num_faces) { TopoDS_Face face = TopoDS::Face(exp.Current()); + + size_t num_bounds = 0; + for (TopoDS_Iterator it(face); it.More(); it.Next(), ++num_bounds) {} + + const bool is_planar = BRep_Tool::Surface(face) && BRep_Tool::Surface(face)->DynamicType() == STANDARD_TYPE(Geom_Plane); + const bool has_inner_bounds = num_bounds > 1; + + const bool polyhedral_output_with_holes = settings.get().get() == settings::POLYHEDRON_WITH_HOLES && is_planar; + const bool polyhedral_output_without_holes = settings.get().get() == settings::POLYHEDRON_WITHOUT_HOLES && is_planar && !has_inner_bounds; + + std::vector> triangle_indices; + TopLoc_Location loc; Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc); @@ -154,11 +283,21 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr _normals.push_back((float)normal.Z()); */ - t->addFace(item_id, surface_style_id, dict[n1], dict[n2], dict[n3]); + if (polyhedral_output_without_holes || polyhedral_output_with_holes) { + triangle_indices.push_back({ dict[n1], dict[n2], dict[n3] }); + } else { + if (settings.get().get() == settings::POLYHEDRON_WITHOUT_HOLES) { + t->addFace(item_id, surface_style_id, std::vector{ dict[n1], dict[n2], dict[n3] }); + } else if (settings.get().get() == settings::POLYHEDRON_WITH_HOLES) { + t->addFace(item_id, surface_style_id, std::vector>{{ dict[n1], dict[n2], dict[n3] }}); + } else { + t->addFace(item_id, surface_style_id, dict[n1], dict[n2], dict[n3]); - t->addEdge(dict[n1], dict[n2], edgecount); - t->addEdge(dict[n2], dict[n3], edgecount); - t->addEdge(dict[n3], dict[n1], edgecount); + t->addEdge(dict[n1], dict[n2], edgecount); + t->addEdge(dict[n2], dict[n3], edgecount); + t->addEdge(dict[n3], dict[n1], edgecount); + } + } } for (auto& p : edgecount) { // @todo should be != 2? @@ -172,6 +311,19 @@ void ifcopenshell::geometry::OpenCascadeShape::Triangulate(ifcopenshell::geometr } } } + + if (polyhedral_output_without_holes || polyhedral_output_with_holes) { + auto loops = find_boundary_loops(t->verts(), triangle_indices); + if (polyhedral_output_without_holes) { + if (!loops.empty() && !loops[0].empty()) { + t->addFace(item_id, surface_style_id, loops[0]); + } + } else { + if (!loops.empty()) { + t->addFace(item_id, surface_style_id, loops); + } + } + } } if (!t->normals().empty() && settings.get().get()) { diff --git a/src/ifcopenshell-python/ifcopenshell/geom/main.py b/src/ifcopenshell-python/ifcopenshell/geom/main.py index cfd6616aa0..1773275bea 100644 --- a/src/ifcopenshell-python/ifcopenshell/geom/main.py +++ b/src/ifcopenshell-python/ifcopenshell/geom/main.py @@ -101,6 +101,9 @@ SETTING = Literal[ "piecewise-step-param", "use-python-opencascade", "no-parallel-mapping", + "triangulation-type", + "model-rotation", + "model-offset", ] SERIALIZER_SETTING = Literal[ "use-element-names", diff --git a/src/ifcopenshell-python/test/bootstrap.py b/src/ifcopenshell-python/test/bootstrap.py index 9f0503d1fe..055ea8774a 100644 --- a/src/ifcopenshell-python/test/bootstrap.py +++ b/src/ifcopenshell-python/test/bootstrap.py @@ -24,6 +24,7 @@ if os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) == sys.path[0] # does not contain the built binary sys.path[0:1] = [] + import pytest import ifcopenshell import ifcopenshell.api.project @@ -75,3 +76,8 @@ class IFC2X3: ifcopenshell.api.pre_listeners = {} ifcopenshell.api.post_listeners = {} + + +@pytest.fixture(autouse=True) +def file(request): + return ifcopenshell.open(os.path.join("test/fixtures", request.param)) diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i index dbf9451a7c..322df42693 100644 --- a/src/ifcwrap/IfcGeomWrapper.i +++ b/src/ifcwrap/IfcGeomWrapper.i @@ -681,7 +681,17 @@ struct ShapeRTTI : public boost::static_visitor %pythoncode %{ # Hide the getters with read-only property implementations - faces = property(faces) + faces_tri = property(faces) + polyhedral_faces_without_holes = property(polyhedral_faces_without_holes) + polyhedral_faces_with_holes = property(polyhedral_faces_with_holes) + def get_faces(self): + if self.faces_tri: + return self.faces_tri + elif self.polyhedral_faces_without_holes: + return self.polyhedral_faces_without_holes + else: + return self.polyhedral_faces_with_holes + faces = property(get_faces) edges = property(edges) material_ids = property(material_ids) materials = property(materials) diff --git a/src/ifcwrap/IfcParseWrapper.i b/src/ifcwrap/IfcParseWrapper.i index 117041a6e7..e0e109b97a 100644 --- a/src/ifcwrap/IfcParseWrapper.i +++ b/src/ifcwrap/IfcParseWrapper.i @@ -56,24 +56,6 @@ private: %rename("add") addEntity; %rename("remove") removeEntity; -%{ - -template -struct is_std_vector : std::false_type {}; -template -struct is_std_vector> : std::true_type {}; -template -constexpr bool is_std_vector_v = is_std_vector::value; - -template -struct is_std_vector_vector : std::false_type {}; -template -struct is_std_vector_vector, Alloc2>> : std::true_type {}; -template -constexpr bool is_std_vector_vector_v = is_std_vector_vector::value; - -%} - class attribute_value_derived {}; %{ class attribute_value_derived {}; @@ -780,9 +762,7 @@ static IfcUtil::ArgumentType helper_fn_attribute_type(const IfcUtil::IfcBaseClas PyObject* convert_cpp_attribute_to_python(AttributeValue arg) { return arg.array_->apply_visitor([](auto& v){ using U = std::decay_t; - if constexpr (is_std_vector_vector_v) { - return pythonize_vector2(v); - } else if constexpr (is_std_vector_v) { + if constexpr (is_std_vector_v) { return pythonize_vector(v); } else if constexpr (std::is_same_v) { return pythonize(std::string(v.value())); diff --git a/src/ifcwrap/IfcPython.i b/src/ifcwrap/IfcPython.i index f70388663d..72fe60f21d 100644 --- a/src/ifcwrap/IfcPython.i +++ b/src/ifcwrap/IfcPython.i @@ -148,6 +148,24 @@ #endif %} +%{ + +template +struct is_std_vector : std::false_type {}; +template +struct is_std_vector> : std::true_type {}; +template +constexpr bool is_std_vector_v = is_std_vector::value; + +template +struct is_std_vector_vector : std::false_type {}; +template +struct is_std_vector_vector, Alloc2>> : std::true_type {}; +template +constexpr bool is_std_vector_vector_v = is_std_vector_vector::value; + +%} + // Create docstrings for generated python code. %feature("autodoc", "1"); diff --git a/src/ifcwrap/utils/type_conversion.i b/src/ifcwrap/utils/type_conversion.i index 85907f78db..6c56cc631b 100644 --- a/src/ifcwrap/utils/type_conversion.i +++ b/src/ifcwrap/utils/type_conversion.i @@ -189,21 +189,15 @@ } template - PyObject* pythonize_vector(const std::vector& v) { + PyObject* pythonize_vector(const T& v) { const size_t size = v.size(); PyObject* pyobj = PyTuple_New(size); for (size_t i = 0; i < size; ++i) { - PyTuple_SetItem(pyobj, i, pythonize(v[i])); - } - return pyobj; - } - - template - PyObject* pythonize_vector2(const std::vector< std::vector >& v) { - const size_t size = v.size(); - PyObject* pyobj = PyTuple_New(size); - for (size_t i = 0; i < size; ++i) { - PyTuple_SetItem(pyobj, i, pythonize_vector(v[i])); + if constexpr (is_std_vector_v) { + PyTuple_SetItem(pyobj, i, pythonize_vector(v[i])); + } else { + PyTuple_SetItem(pyobj, i, pythonize(v[i])); + } } return pyobj; } diff --git a/src/ifcwrap/utils/typemaps_out.i b/src/ifcwrap/utils/typemaps_out.i index 2984e5f059..1c9761e4fe 100644 --- a/src/ifcwrap/utils/typemaps_out.i +++ b/src/ifcwrap/utils/typemaps_out.i @@ -38,9 +38,7 @@ try { $result = $1.array_->apply_visitor([](auto& v){ using U = std::decay_t; - if constexpr (is_std_vector_vector_v) { - return pythonize_vector2(v); - } else if constexpr (is_std_vector_v) { + if constexpr (is_std_vector_v) { return pythonize_vector(v); } else if constexpr (std::is_same_v) { return pythonize(std::string(v.value())); @@ -70,10 +68,22 @@ %define CREATE_VECTOR_TYPEMAP_OUT(template_type) %typemap(out) std::vector { - $result = pythonize_vector($1); + $result = pythonize_vector>($1); } %typemap(out) const std::vector& { - $result = pythonize_vector(*$1); + $result = pythonize_vector>(*$1); + } + %typemap(out) std::vector> { + $result = pythonize_vector>>($1); + } + %typemap(out) const std::vector>& { + $result = pythonize_vector>>(*$1); + } + %typemap(out) std::vector>> { + $result = pythonize_vector>>>($1); + } + %typemap(out) const std::vector>>& { + $result = pythonize_vector>>>(*$1); } %enddef