diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml new file mode 100644 index 0000000..28e6519 --- /dev/null +++ b/.github/workflows/ci-build.yaml @@ -0,0 +1,49 @@ +name: ci-nano-min + +# bump 1 +on: + push: + paths-ignore: + - README.md + - .gitignore + +jobs: + build_test: + name: nano-min-${{ matrix.platform.name }}-${{ matrix.pyver.name }} + runs-on: ${{ matrix.platform.distver }} + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + pyver: [ + { name: py311, distver: '3.11' }, + ] + platform: [ + { name: win, distver: windows-latest }, + { name: linux, distver: ubuntu-latest }, + { name: macOS, distver: macos-latest } + ] + steps: + - uses: actions/checkout@v3 + # Add ninja + - name: Install ninja + run: | + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + sudo apt-get install ninja-build + elif [[ "$OSTYPE" == "darwin"* ]]; then + brew install ninja + elif [[ "$OSTYPE" == "win32" ]]; then + choco install ninja + fi + - uses: mamba-org/setup-micromamba@v1 # https://github.com/mamba-org/setup-micromamba + with: + channels: conda-forge + environment-file: environment.build.yml + create-args: >- + python=${{ matrix.pyver.distver }} + + - name: build local packages + run: | + boa build conda --python ${{ matrix.pyver.distver }} -m ./conda/conda_build_config.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dce8399 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +.idea/ +cmake-build*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c372815 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +cmake_minimum_required(VERSION 3.20) + +project(ada-cpp LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + message(STATUS "Building for x64 architecture") +else () + message(FATAL_ERROR "This project requires a 64-bit toolchain. Please update your toolchain arch to 'x86_amd64'") +endif () + +if (NOT CMAKE_BUILD_TYPE) + message(STATUS "Build type not set, defaulting to Release") + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif () +message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) + +find_package(Python 3 COMPONENTS Interpreter Development.Module REQUIRED) + +# Create a list called ADA_CPP_SOURCES of all cpp files inside the src dir +set(ADA_CPP_SOURCES + src/adacpp.cpp + src/helpers/helpers.cpp + src/models/Models.cpp + src/exchange/step_writer.cpp + src/exchange/gltf_writer.cpp + src/geom/ShapeTesselator.cpp + src/geom/tess_helpers.cpp + ) + +# Find OpenCASCADE +find_package(OpenCASCADE REQUIRED) +if (OpenCASCADE_FOUND) + message(STATUS "OpenCASCADE version found: " ${OpenCASCADE_MAJOR_VERSION} ".." ${OpenCASCADE_MINOR_VERSION} ".." ${OpenCASCADE_MAINTENANCE_VERSION}) + message(STATUS "OpenCASCADE include directory: " ${OpenCASCADE_INCLUDE_DIR}) + message(STATUS "OpenCASCADE binary directory: " ${OpenCASCADE_BINARY_DIR}) + message(STATUS "OpenCASCADE library directory: " ${OpenCASCADE_LIBRARY_DIR}) + + include_directories(${OpenCASCADE_INCLUDE_DIR}) + link_directories(${OpenCASCADE_LIBRARY_DIR}) +endif (OpenCASCADE_FOUND) + + +# Detect the installed nanobind package and import it into CMake +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR) + +message(STATUS "NanoBind Cmake directory: " ${NB_DIR}) +list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}") + +# Import nanobind through CMake's find_package mechanism +find_package(nanobind CONFIG REQUIRED) + +# Print the list of cpp files separated by spaces without altering it +string(REPLACE ";" " " ADA_CPP_SOURCES_STR "${ADA_CPP_SOURCES}") +message(STATUS "AdaCpp sources: " ${ADA_CPP_SOURCES_STR}) + +# Create a Python module +nanobind_add_module(_ada_cpp_ext_impl ${ADA_CPP_SOURCES}) + +target_link_libraries(_ada_cpp_ext_impl + PRIVATE + TKernel + TKMath + TKBRep + TKPrim + TKTopAlgo + TKSTEP + TKXDESTEP + TKRWMesh + ) + +# Link the module to OpenCASCADE +install(TARGETS _ada_cpp_ext_impl LIBRARY DESTINATION .) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..64a4e43 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# Ada-CPP + +A drop-in replacement library for adapy created to improve performance using c++. +The compilation and binding methods are based on the [nanobind-minimal repo](https://github.com/Krande/nanobind-minimal) + +## Performance metrics +Todo + +## Installation + +First install the pre-requisites for both occt and nanobind + build requirements from conda-forge. + +```bash +mamba env update -f environment.build.yml --prune +``` + +Activate the environment and install the package in editable mode. + +```bash +pip install --no-build-isolation . +``` + +### Conda Build install + +Installing as conda package + +```bash +mamba mambabuild . -c conda-forge --python 3.11 --override-channels +mamba install --use-local ada-cpp +``` + + diff --git a/conda/conda_build_config.yaml b/conda/conda_build_config.yaml new file mode 100644 index 0000000..36ca80e --- /dev/null +++ b/conda/conda_build_config.yaml @@ -0,0 +1,4 @@ +occt: + - 7.7.0 + - 7.7.1=*novtk* + - 7.7.1=*all* \ No newline at end of file diff --git a/conda/recipe.yaml b/conda/recipe.yaml new file mode 100644 index 0000000..7cf9671 --- /dev/null +++ b/conda/recipe.yaml @@ -0,0 +1,43 @@ +context: + name: ada-cpp + version: 0.0.1 + +package: + name: '{{ name|lower }}' + version: '{{ version }}' + +source: + path: ../ + +build: + number: 1 + script: python -m pip install --no-deps --ignore-installed . + +requirements: + host: + - python + - occt + - scikit-build-core + - nanobind + - pip + run: + - python + - "{{ pin_compatible('occt', max_pin='x.x.x') }}" + +test: + source_files: + - tests + requires: + - pytest + commands: + - pytest tests + +about: + home: https://github.com/Krande/adacpp + license: "GPL-3.0-or-later" + license_family: GPL3 + summary: "A module with drop-in replacement functions for ada-py written in c++ to improve performance." + +extra: + recipe-maintainers: + - Krande \ No newline at end of file diff --git a/environment.build.yml b/environment.build.yml new file mode 100644 index 0000000..a28edc0 --- /dev/null +++ b/environment.build.yml @@ -0,0 +1,13 @@ +name: ada-cpp +channels: + - conda-forge +dependencies: + - occt ==7.7.0 + - nanobind ==1.2.0 + - pytest ==7.3.1 + - scikit-build-core ==0.3.3 + - cmake ==3.26.3 + - conda-verify ==3.1.1 + - numpy ==1.24.3 + - nomkl + - boa ==0.15.1 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2110965 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[build-system] +requires = [ + "scikit-build-core[pyproject]", + "nanobind>=1.1.0" +] + +build-backend = "scikit_build_core.build" + +[project] +name = "ada-cpp" +version = "0.0.1" +authors = [ + { name = "Kristoffer H. Andersen", email = "kristoffer_andersen@outlook.com" }, +] +description = "A minimal python wrapping of OCCT using nanobind" +readme = "README.md" +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.10", + "Topic :: Scientific/Engineering", + "Topic :: Utilities", +] + +[project.urls] +"Homepage" = "https://github.com/Krande/adacpp" +"Bug Tracker" = "https://github.com/Krande/adacpp/issues" + +[tool.scikit-build] +cmake.args = ["-DCMAKE_OSX_DEPLOYMENT_TARGET:STRING=10.14", "-Wno-dev"] +cmake.verbose = true +#cmake.build-type = "Debug" + +build-dir = "build/{wheel_tag}" + +# This is the only editable mode currently +editable.mode = "redirect" + +wheel.packages = ["src/adacpp"] +wheel.install-dir = "adacpp" diff --git a/src/adacpp.cpp b/src/adacpp.cpp new file mode 100644 index 0000000..8bd865f --- /dev/null +++ b/src/adacpp.cpp @@ -0,0 +1,9 @@ +#include "binding_core.h" +#include "exchange/step_writer.h" +#include "geom/tess_helpers.h" + +// Define the modules that will be exposed in python +NB_MODULE(_ada_cpp_ext_impl, m) { + step_writer_module(m); + geom_module(m); +} \ No newline at end of file diff --git a/src/adacpp/__init__.py b/src/adacpp/__init__.py new file mode 100644 index 0000000..5209b9d --- /dev/null +++ b/src/adacpp/__init__.py @@ -0,0 +1,4 @@ +from ._ada_cpp_ext_impl import write_box_to_step, write_boxes_to_step, get_box_mesh +from .utils import do_this + +__doc__ = "A module with drop-in replacement functions for ada-py written in c++ to improve performance." diff --git a/src/adacpp/utils.py b/src/adacpp/utils.py new file mode 100644 index 0000000..349bf58 --- /dev/null +++ b/src/adacpp/utils.py @@ -0,0 +1,3 @@ +def do_this(): + """A function that prints 'do_this()' to the console.""" + print("do_this()") diff --git a/src/binding_core.h b/src/binding_core.h new file mode 100644 index 0000000..dd907c6 --- /dev/null +++ b/src/binding_core.h @@ -0,0 +1,17 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_BINDING_CORE_H +#define NANO_OCCT_BINDING_CORE_H + + +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +#endif //NANO_OCCT_BINDING_CORE_H diff --git a/src/exchange/gltf_writer.cpp b/src/exchange/gltf_writer.cpp new file mode 100644 index 0000000..fa3c812 --- /dev/null +++ b/src/exchange/gltf_writer.cpp @@ -0,0 +1,78 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../models/OccShape.h" +#include "../helpers/helpers.h" +#include "../geom/tess_helpers.h" +#include "../models/Mesh.h" + + +enum class Units { + M, + MM, +}; + +void to_gltf( + const std::filesystem::path& gltf_file, + const std::vector& occ_shape_iterable, + Units export_units = Units::M, + Units source_units = Units::M +) { + Handle(TDocStd_Document) doc = new TDocStd_Document(TCollection_ExtendedString("ada-py")); + Handle(XCAFDoc_ShapeTool) shape_tool = XCAFDoc_DocumentTool::ShapeTool(doc->Main()); + Handle(XCAFDoc_ColorTool) color_tool = XCAFDoc_DocumentTool::ColorTool(doc->Main()); + + int i = 1; + for (const auto& step_shape : occ_shape_iterable) { + TopoDS_Shape shp = step_shape.shape; + if (shp.ShapeType() == TopAbs_COMPOUND) { + continue; + } + +// Mesh mesh = tessellate_shape(shp, true, 1.0, false); + + TDF_Label sub_shape_label = shape_tool->AddShape(shp); + set_color(sub_shape_label, step_shape.color, color_tool); + set_name(sub_shape_label, step_shape.name); + + i++; + } + + RWGltf_WriterTrsfFormat a_format = RWGltf_WriterTrsfFormat_Compact; + + TColStd_IndexedDataMapOfStringString a_file_info; + a_file_info.Add(TCollection_AsciiString("Authors"), TCollection_AsciiString("ada-py")); + + bool binary = gltf_file.extension() == ".glb"; + + RWGltf_CafWriter glb_writer(TCollection_AsciiString(gltf_file.string().c_str()), binary); + if (export_units == Units::M && source_units == Units::MM) { + glb_writer.ChangeCoordinateSystemConverter().SetInputLengthUnit(0.001); + } else if (export_units == Units::MM && source_units == Units::M) { + glb_writer.ChangeCoordinateSystemConverter().SetInputLengthUnit(1000); + } + + glb_writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(RWMesh_CoordinateSystem_Zup); + glb_writer.SetTransformationFormat(a_format); + Message_ProgressRange pr; + glb_writer.Perform(doc, a_file_info, pr); +} + diff --git a/src/exchange/gltf_writer.h b/src/exchange/gltf_writer.h new file mode 100644 index 0000000..9c3ded4 --- /dev/null +++ b/src/exchange/gltf_writer.h @@ -0,0 +1,14 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_GLTF_WRITER_H +#define NANO_OCCT_GLTF_WRITER_H + + +class gltf_writer { + +}; + + +#endif //NANO_OCCT_GLTF_WRITER_H diff --git a/src/exchange/step_writer.cpp b/src/exchange/step_writer.cpp new file mode 100644 index 0000000..69785f9 --- /dev/null +++ b/src/exchange/step_writer.cpp @@ -0,0 +1,121 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../binding_core.h" +#include "../helpers/helpers.h" + + + +class AdaCPPStepWriter { +public: + explicit AdaCPPStepWriter(const std::string &top_level_name = "Assembly") { + app_ = new TDocStd_Application(); + doc_ = new TDocStd_Document(TCollection_ExtendedString("XmlOcaf")); + app_->InitDocument(doc_); + + // The shape tool + shape_tool_ = XCAFDoc_DocumentTool::ShapeTool(doc_->Main()); + // Set auto naming to false + shape_tool_->SetAutoNaming(false); + + // The color tool + color_tool_ = XCAFDoc_DocumentTool::ColorTool(doc_->Main()); + + // Set up the compound + comp_builder_.MakeCompound(comp_); + + tll_ = shape_tool_->AddShape(comp_, Standard_True); + set_name(tll_, top_level_name); + } + + void add_shape(const TopoDS_Shape &shape, const std::string &name, + Color &rgb_color, + const TDF_Label &parent = TDF_Label()) { + comp_builder_.Add(comp_, shape); + TDF_Label parent_label = parent.IsNull() ? tll_ : parent; + TDF_Label shape_label = shape_tool_->AddSubShape(parent_label, shape); + if (shape_label.IsNull()) { + shape_label = shape_tool_->AddShape(shape, Standard_False, Standard_False); +// std::cout << "Adding as SubShape label generated an IsNull label. Adding as shape instead" << std::endl; + } + set_color(shape_label, rgb_color, color_tool_); + set_name(shape_label, name); + } + + void export_step(const std::filesystem::path &step_file) { + // Create the directory if it doesn't exist and check that step_file.parent_path() is not "" + + if (!step_file.parent_path().empty() && step_file.parent_path() != "") { + std::filesystem::create_directories(step_file.parent_path()); + } + + // Set up the writer + Handle(XSControl_WorkSession) session = new XSControl_WorkSession(); + + STEPCAFControl_Writer writer(session, Standard_False); + writer.SetColorMode(Standard_True); + writer.SetNameMode(Standard_True); + + Interface_Static::SetCVal("write.step.unit", "m"); + Interface_Static::SetCVal("write.step.schema", "AP214"); + + writer.Transfer(doc_, STEPControl_AsIs); + IFSelect_ReturnStatus status = writer.Write(step_file.string().c_str()); + + if (status != IFSelect_RetDone) { + throw std::runtime_error("STEP export failed"); + } else { + std::cout << "STEP export status: " << static_cast(status) << std::endl; + } + } + +private: + Handle(TDocStd_Application) app_; + Handle(TDocStd_Document) doc_; + Handle(XCAFDoc_ShapeTool) shape_tool_; + Handle(XCAFDoc_ColorTool) color_tool_; + TopoDS_Compound comp_; + BRep_Builder comp_builder_; + TDF_Label tll_; +}; + +// take in a list of box dimensions and origins and write to step file using the AdaCPPStepWriter class +void write_boxes_to_step(const std::string &filename, const std::vector> &box_origins, + const std::vector> &box_dims) { + AdaCPPStepWriter writer(filename); + for (int i = 0; i < box_origins.size(); i++) { + TopoDS_Solid box = create_box(box_origins[i], box_dims[i]); + Color color = random_color(); + writer.add_shape(box, "box_" + std::to_string(i), color); + } + writer.export_step(filename); +} + +// take in a single box dimension and origin and write to step file using the STEPControl_Writer class +void write_box_to_step(const std::string &filename, const std::vector &box_origin, + const std::vector &box_dims) { + + TopoDS_Solid aBox = create_box(box_origin, box_dims); + STEPControl_Writer writer; + writer.Transfer(aBox, STEPControl_AsIs); + writer.Write(filename.c_str()); +} + +void step_writer_module(nb::module_ &m) { + m.def("write_box_to_step", &write_box_to_step, "filename"_a, "box_origin"_a, "box_dims"_a, + "Write a box to a step file"); + m.def("write_boxes_to_step", &write_boxes_to_step, "filename"_a, "box_origins"_a, "box_dims"_a, + "Write a list of boxes to a step file"); +} \ No newline at end of file diff --git a/src/exchange/step_writer.h b/src/exchange/step_writer.h new file mode 100644 index 0000000..7f882c2 --- /dev/null +++ b/src/exchange/step_writer.h @@ -0,0 +1,26 @@ +#ifndef NANO_OCCT_STEP_WRITER_H +#define NANO_OCCT_STEP_WRITER_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../binding_core.h" +#include "../helpers/helpers.h" + + + +void step_writer_module(nb::module_ &m); + +#endif // NANO_OCCT_STEP_WRITER_H diff --git a/src/geom/ShapeTesselator.cpp b/src/geom/ShapeTesselator.cpp new file mode 100644 index 0000000..1ba47b9 --- /dev/null +++ b/src/geom/ShapeTesselator.cpp @@ -0,0 +1,553 @@ +// Copyright 2011 Fotios Sioutis (sfotis@gmail.com) +// +//This file is part of pythonOCC. +// +//pythonOCC is free software: you can redistribute it and/or modify +//it under the terms of the GNU Lesser General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//pythonOCC is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public License +//along with pythonOCC. If not, see . + +//--------------------------------------------------------------------------- +#include "ShapeTesselator.h" +#include +#include +#include +//--------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------- +ShapeTesselator::ShapeTesselator(TopoDS_Shape aShape) : + myShape(std::move(aShape)), + locVertexcoord(nullptr), + locNormalcoord(nullptr), + locTriIndices(nullptr), + computed(false) { + ComputeDefaultDeviation(); +} + +void ShapeTesselator::Compute(bool compute_edges, float mesh_quality, bool parallel) { + if (!computed) { + Tesselate(compute_edges, mesh_quality, parallel); + } + computed = true; +} + +ShapeTesselator::~ShapeTesselator() { + if (locVertexcoord) + delete[] locVertexcoord; + + if (locNormalcoord) + delete[] locNormalcoord; + + if (locTriIndices) + delete[] locTriIndices; + + for (std::vector::iterator edgeit = edgelist.begin(); edgeit != edgelist.end(); ++edgeit) { + aedge *edge = *edgeit; + if (edge) { + if (edge->vertex_coord) + delete[] edge->vertex_coord; + + delete edge; + *edgeit = NULL; + } + } + edgelist.clear(); +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::SetDeviation(Standard_Real aDeviation) { + myDeviation = aDeviation; +} + + +//--------------------------------------------------------------------------- +void ShapeTesselator::Tesselate(bool compute_edges, float mesh_quality, bool parallel) { + TopExp_Explorer ExpFace; + // clean shape to remove any previous tringulation + BRepTools::Clean(myShape); + //Triangulate + BRepMesh_IncrementalMesh(myShape, myDeviation * mesh_quality, false, 0.5 * mesh_quality, parallel); + + + for (ExpFace.Init(myShape, TopAbs_FACE); ExpFace.More(); ExpFace.Next()) { + Standard_Integer validFaceTriCount = 0; + Standard_Integer invalidFaceTriCount = 0; + Standard_Integer invalidNormalCount = 0; + TopLoc_Location aLocation; + + const TopoDS_Face &myFace = TopoDS::Face(ExpFace.Current()); + Handle(Poly_Triangulation) myT = BRep_Tool::Triangulation(myFace, aLocation); + + + if (myT.IsNull()) { + invalidFaceTriCount++; + continue; + } + + aface *this_face = new aface; + + //write vertex buffer + const TColgp_Array1OfPnt &Nodes = myT->MapNodeArray()->Array1(); + this_face->vertex_coord = new double[Nodes.Length() * 3]; + this_face->number_of_coords = Nodes.Length(); + for (Standard_Integer i = Nodes.Lower(); i <= Nodes.Upper(); i++) { + gp_Pnt p = myT->Node(i).Transformed(aLocation).XYZ(); + this_face->vertex_coord[((i - 1) * 3) + 0] = p.X(); + this_face->vertex_coord[((i - 1) * 3) + 1] = p.Y(); + this_face->vertex_coord[((i - 1) * 3) + 2] = p.Z(); + } + + // compute normals and write normal buffer, using the uv nodes + if (myT->HasUVNodes()) { + BRepGProp_Face prop(myFace); + + const TColgp_Array1OfPnt2d &uvnodes = myT->MapUVNodeArray()->Array1(); + Standard_Integer ilower = uvnodes.Lower(); + Standard_Integer iBufferSize = uvnodes.Upper() - uvnodes.Lower() + 1; + this_face->normal_coord = new Standard_Real[iBufferSize * 3]; + this_face->number_of_normals = iBufferSize; + + for (int i = uvnodes.Lower(); i <= uvnodes.Upper(); ++i) { + const gp_Pnt2d &uv_pnt = myT->UVNode(i); + gp_Pnt p; + gp_Vec n; + prop.Normal(uv_pnt.X(), uv_pnt.Y(), p, n); + if (n.SquareMagnitude() > 0.) { + n.Normalize(); + } + if (myFace.Orientation() == TopAbs_INTERNAL) { + n.Reverse(); + } + this_face->normal_coord[((i - 1) * 3) + 0] = n.X(); + this_face->normal_coord[((i - 1) * 3) + 1] = n.Y(); + this_face->normal_coord[((i - 1) * 3) + 2] = n.Z(); + } + } else { + invalidNormalCount++; + } + + //write triangle buffer + TopAbs_Orientation orient = myFace.Orientation(); + const Poly_Array1OfTriangle &triangles = myT->Triangles(); + this_face->tri_indexes = new int[triangles.Length() * 3]; + for (Standard_Integer nt = 1; nt <= myT->NbTriangles(); nt++) { + Standard_Integer n0, n1, n2; + triangles(nt).Get(n0, n1, n2); + if (orient == TopAbs_REVERSED) { + Standard_Integer tmp = n1; + n1 = n2; + n2 = tmp; + } + this_face->tri_indexes[validFaceTriCount * 3 + 0] = n0; + this_face->tri_indexes[validFaceTriCount * 3 + 1] = n1; + this_face->tri_indexes[validFaceTriCount * 3 + 2] = n2; + validFaceTriCount++; + } + + this_face->number_of_triangles = validFaceTriCount; + this_face->number_of_invalid_triangles = invalidFaceTriCount; + this_face->number_of_invalid_normals = invalidNormalCount; + facelist.push_back(this_face); + } + + JoinPrimitives(); + + if (compute_edges) ComputeEdges(); +} + + +//---------------------------INTERFACE--------------------------------------- +void ShapeTesselator::ComputeDefaultDeviation() { + // This method automatically computes precision from the bounding box of the shape + Bnd_Box aBox; + Standard_Real aXmin, aYmin, aZmin, aXmax, aYmax, aZmax; + + //calculate the bounding box + BRepBndLib::Add(myShape, aBox); + aBox.Get(aXmin, aYmin, aZmin, aXmax, aYmax, aZmax); + + Standard_Real adeviation = std::max(aXmax - aXmin, std::max(aYmax - aYmin, aZmax - aZmin)) * 2e-2; + myDeviation = adeviation; +} + +void ShapeTesselator::ComputeEdges() { + TopLoc_Location aTrsf; + + // clear current data + std::vector::iterator it; + for (it = edgelist.begin(); it != edgelist.end(); ++it) { + if (*it) { + if ((*it)->vertex_coord) + delete[] (*it)->vertex_coord; + + delete *it; + *it = NULL; + } + } + edgelist.clear(); + // get indexed map of edges + TopTools_IndexedMapOfShape M; + TopExp::MapShapes(myShape, TopAbs_EDGE, M); + + // explore all boundary edges + TopTools_IndexedDataMapOfShapeListOfShape edgeMap; + TopExp::MapShapesAndAncestors(myShape, TopAbs_EDGE, TopAbs_FACE, edgeMap); + + for (int iEdge = 1; iEdge <= edgeMap.Extent(); iEdge++) { + + // skip free edges, might be the case if the shape passed to + // the tesselator is a Compound + const TopTools_ListOfShape &faceList = edgeMap.FindFromIndex(iEdge); + + if (faceList.Extent() == 0) { + printf("Skipped free edge during shape tesselation/edges computation.\n"); + continue; + } + + // take one of the shared edges and get edge triangulation + //const TopoDS_Face& aFace = TopoDS::Face (faceList.First ()); + const TopoDS_Edge &anEdge = TopoDS::Edge(M(iEdge)); + gp_Trsf myTransf; + TopLoc_Location aLoc; + + // triangulate the edge + Handle(Poly_Polygon3D) aPoly = BRep_Tool::Polygon3D(anEdge, aLoc); + + + aedge *theEdge = new aedge; + Standard_Integer nbNodesInFace; + + // edge triangulation successfull + if (!aPoly.IsNull()) { + if (!aLoc.IsIdentity()) myTransf = aLoc.Transformation(); + nbNodesInFace = aPoly->NbNodes(); + theEdge->number_of_coords = nbNodesInFace; + theEdge->vertex_coord = new Standard_Real[nbNodesInFace * 3 * sizeof(Standard_Real)]; + + const TColgp_Array1OfPnt &Nodes = aPoly->Nodes(); + gp_Pnt V; + for (Standard_Integer i = 0; i < nbNodesInFace; i++) { + V = Nodes(i + 1); + V.Transform(myTransf); + theEdge->vertex_coord[i * 3 + 0] = V.X(); + theEdge->vertex_coord[i * 3 + 1] = V.Y(); + theEdge->vertex_coord[i * 3 + 2] = V.Z(); + } + } + // edge triangulation failed + else { + const TopoDS_Face &aFace = TopoDS::Face(edgeMap.FindFromIndex(iEdge).First()); + // take the face's triangulation instead + Handle(Poly_Triangulation) aPolyTria = BRep_Tool::Triangulation(aFace, aLoc); + if (!aLoc.IsIdentity()) myTransf = aLoc.Transformation(); + //if (aPolyTria.IsNull()) break; + // this holds the indices of the edge's triangulation to the actual points + Handle(Poly_PolygonOnTriangulation) aPoly = BRep_Tool::PolygonOnTriangulation(anEdge, aPolyTria, aLoc); + if (aPoly.IsNull()) continue; // polygon does not exist + + // getting size and create the array + nbNodesInFace = aPoly->NbNodes(); + theEdge->number_of_coords = nbNodesInFace; + theEdge->vertex_coord = new Standard_Real[nbNodesInFace * 3 * sizeof(Standard_Real)]; + + const TColStd_Array1OfInteger &indices = aPoly->Nodes(); + const TColgp_Array1OfPnt &Nodes = aPolyTria->MapNodeArray()->Array1(); + + // go through the index array + for (Standard_Integer i = indices.Lower(); i <= indices.Upper(); i++) { + // node index in face triangulation + gp_Pnt V = Nodes(indices(i)); + V.Transform(myTransf); + int locIndex = i - Nodes.Lower(); + theEdge->vertex_coord[locIndex * 3 + 0] = V.X(); + theEdge->vertex_coord[locIndex * 3 + 1] = V.Y(); + theEdge->vertex_coord[locIndex * 3 + 2] = V.Z(); + } + } + edgelist.push_back(theEdge); + } +} + +void ShapeTesselator::EnsureMeshIsComputed() { + // this method ensures that the mesh is computed before returning any + // related data + if (!computed) { + printf("The mesh is not computed. Currently computing with default parameters ..."); + Compute(true, 1.0, false); + printf("done\n"); + printf("Call explicitely the Compute method to set the parameters value."); + } +} + +std::string formatFloatNumber(float f) { + // returns string representation of the float number f. + // set epsilon to 1e-3 + float epsilon = 1e-3; + std::stringstream formatted_float; + if (std::abs(f) < epsilon) { + f = 0.; + } + formatted_float << f; + return formatted_float.str(); +} + +std::vector ShapeTesselator::GetVerticesPositionAsTuple() { + EnsureMeshIsComputed(); + // create the vector and allocate memory + std::vector vertices_position; + vertices_position.reserve(tot_triangle_count); + // loop over tertices + for (int i = 0; i < tot_triangle_count; i++) { + int pID = locTriIndices[(i * 3) + 0] * 3; + vertices_position.push_back(locVertexcoord[pID]); + vertices_position.push_back(locVertexcoord[pID + 1]); + vertices_position.push_back(locVertexcoord[pID + 2]); + // Second vertex + int qID = locTriIndices[(i * 3) + 1] * 3; + vertices_position.push_back(locVertexcoord[qID]); + vertices_position.push_back(locVertexcoord[qID + 1]); + vertices_position.push_back(locVertexcoord[qID + 2]); + // Third vertex + int rID = locTriIndices[(i * 3) + 2] * 3; + vertices_position.push_back(locVertexcoord[rID]); + vertices_position.push_back(locVertexcoord[rID + 1]); + vertices_position.push_back(locVertexcoord[rID + 2]); + } + return vertices_position; +} + +std::vector ShapeTesselator::GetNormalsAsTuple() { + EnsureMeshIsComputed(); + // create the vector and allocate memory + std::vector normals; + normals.reserve(tot_triangle_count); + // loop over normals + for (int i = 0; i < tot_triangle_count; i++) { + int pID = locTriIndices[(i * 3) + 0] * 3; + normals.push_back(locNormalcoord[pID]); + normals.push_back(locNormalcoord[pID + 1]); + normals.push_back(locNormalcoord[pID + 2]); + // Second normal + int qID = locTriIndices[(i * 3) + 1] * 3; + normals.push_back(locNormalcoord[qID]); + normals.push_back(locNormalcoord[qID + 1]); + normals.push_back(locNormalcoord[qID + 2]); + // Third normal + int rID = locTriIndices[(i * 3) + 2] * 3; + normals.push_back(locNormalcoord[rID]); + normals.push_back(locNormalcoord[rID + 1]); + normals.push_back(locNormalcoord[rID + 2]); + } + return normals; +} + + +//--------------------------------------------------------------------------- +Standard_Real *ShapeTesselator::VerticesList() { + EnsureMeshIsComputed(); + return locVertexcoord; +} + +//--------------------------------------------------------------------------- +Standard_Real *ShapeTesselator::NormalsList() { + EnsureMeshIsComputed(); + return locNormalcoord; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetInvalidTriangleCount() { + EnsureMeshIsComputed(); + return tot_invalid_triangle_count; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetTriangleCount() { + EnsureMeshIsComputed(); + return tot_triangle_count; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetVertexCount() { + EnsureMeshIsComputed(); + return tot_vertex_count; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetNormalCount() { + EnsureMeshIsComputed(); + return tot_normal_count; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetInvalidNormalCount() { + EnsureMeshIsComputed(); + return tot_invalid_normal_count; +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjGetEdgeCount() { + EnsureMeshIsComputed(); + return edgelist.size(); +} + +//--------------------------------------------------------------------------- +Standard_Integer ShapeTesselator::ObjEdgeGetVertexCount(int iEdge) { + EnsureMeshIsComputed(); + aedge *edge = edgelist.at(iEdge); + if (!edge) { + return 0; + } + return edge->number_of_coords; +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::GetVertex(int ivert, float &x, float &y, float &z) { + EnsureMeshIsComputed(); + x = locVertexcoord[ivert * 3 + 0]; + y = locVertexcoord[ivert * 3 + 1]; + z = locVertexcoord[ivert * 3 + 2]; +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::GetNormal(int ivert, float &x, float &y, float &z) { + EnsureMeshIsComputed(); + x = locNormalcoord[ivert * 3 + 0]; + y = locNormalcoord[ivert * 3 + 1]; + z = locNormalcoord[ivert * 3 + 2]; +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::GetTriangleIndex(int triangleIdx, int &v1, int &v2, int &v3) { + EnsureMeshIsComputed(); + v1 = locTriIndices[3 * triangleIdx + 0]; + v2 = locTriIndices[3 * triangleIdx + 1]; + v3 = locTriIndices[3 * triangleIdx + 2]; +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::GetEdgeVertex(int iEdge, int ivert, float &x, float &y, float &z) { + EnsureMeshIsComputed(); + aedge *e = edgelist.at(iEdge); + if (!e) { + return; + } + + x = e->vertex_coord[3 * ivert + 0]; + y = e->vertex_coord[3 * ivert + 1]; + z = e->vertex_coord[3 * ivert + 2]; +} + +//--------------------------------------------------------------------------- +void ShapeTesselator::ObjGetTriangle(int trianglenum, int *vertices, int *normals) { + EnsureMeshIsComputed(); + int pID = locTriIndices[(trianglenum * 3) + 0] * 3; + int qID = locTriIndices[(trianglenum * 3) + 1] * 3; + int rID = locTriIndices[(trianglenum * 3) + 2] * 3; + + vertices[0] = pID; + vertices[1] = qID; + vertices[2] = rID; + + normals[0] = pID; + normals[1] = qID; + normals[2] = rID; +} + +//--------------------------------------------------------------------------- +//---------------------------------HELPERS----------------------------------- +//--------------------------------------------------------------------------- +void ShapeTesselator::JoinPrimitives() { + int obP = 0; + int obN = 0; + int obTR = 0; + + int advance = 0; + + tot_triangle_count = 0; + tot_invalid_triangle_count = 0; + tot_vertex_count = 0; + tot_normal_count = 0; + tot_invalid_normal_count = 0; + + std::vector::iterator anIterator = facelist.begin(); + + while (anIterator != facelist.end()) { + + aface *myface = *anIterator; + + tot_triangle_count = tot_triangle_count + myface->number_of_triangles; + tot_invalid_triangle_count = tot_invalid_triangle_count + myface->number_of_invalid_triangles; + tot_vertex_count = tot_vertex_count + myface->number_of_coords; + tot_normal_count = tot_normal_count + myface->number_of_normals; + tot_invalid_normal_count = tot_invalid_normal_count + myface->number_of_invalid_normals; + + ++anIterator; + } + + locTriIndices = new Standard_Integer[tot_triangle_count * 3]; + locVertexcoord = new Standard_Real[tot_vertex_count * 3]; + locNormalcoord = new Standard_Real[tot_normal_count * 3]; + + anIterator = facelist.begin(); + while (anIterator != facelist.end()) { + aface *myface = *anIterator; + for (int x = 0; x < myface->number_of_coords; x++) { + locVertexcoord[(obP * 3) + 0] = myface->vertex_coord[(x * 3) + 0]; + locVertexcoord[(obP * 3) + 1] = myface->vertex_coord[(x * 3) + 1]; + locVertexcoord[(obP * 3) + 2] = myface->vertex_coord[(x * 3) + 2]; + obP++; + } + + for (int x = 0; x < myface->number_of_normals; x++) { + locNormalcoord[(obN * 3) + 0] = myface->normal_coord[(x * 3) + 0]; + locNormalcoord[(obN * 3) + 1] = myface->normal_coord[(x * 3) + 1]; + locNormalcoord[(obN * 3) + 2] = myface->normal_coord[(x * 3) + 2]; + obN++; + } + + for (int x = 0; x < myface->number_of_triangles; x++) { + locTriIndices[(obTR * 3) + 0] = myface->tri_indexes[(x * 3) + 0] + advance - 1; + locTriIndices[(obTR * 3) + 1] = myface->tri_indexes[(x * 3) + 1] + advance - 1; + locTriIndices[(obTR * 3) + 2] = myface->tri_indexes[(x * 3) + 2] + advance - 1; + obTR++; + } + + advance = obP; + + delete[] myface->vertex_coord; + myface->vertex_coord = NULL; + + delete[] myface->normal_coord; + myface->normal_coord = NULL; + + delete[] myface->tri_indexes; + myface->tri_indexes = NULL; + + delete myface; + myface = NULL; + + ++anIterator; + } +} diff --git a/src/geom/ShapeTesselator.h b/src/geom/ShapeTesselator.h new file mode 100644 index 0000000..750cfec --- /dev/null +++ b/src/geom/ShapeTesselator.h @@ -0,0 +1,92 @@ +// Copyright 2011 Fotios Sioutis (sfotis@gmail.com) +// +//This file is part of pythonOCC. +// +//pythonOCC is free software: you can redistribute it and/or modify +//it under the terms of the GNU Lesser General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. +// +//pythonOCC is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public License +//along with pythonOCC. If not, see . + +//--------------------------------------------------------------------------- +#ifndef TesselatorH +#define TesselatorH +//--------------------------------------------------------------------------- +#include +#include +//--------------------------------------------------------------------------- +#include +#include +#include +//--------------------------------------------------------------------------- +struct aface { + Standard_Real *vertex_coord; + Standard_Real *normal_coord; + Standard_Integer *tri_indexes; + Standard_Integer number_of_coords; + Standard_Integer number_of_normals; + Standard_Integer number_of_invalid_normals; + Standard_Integer number_of_triangles; + Standard_Integer number_of_invalid_triangles; +}; + +struct aedge { + Standard_Real *vertex_coord; + Standard_Integer number_of_coords; +}; + +class ShapeTesselator +{ + protected: + Standard_Boolean computed; + Standard_Real *locVertexcoord; + Standard_Real *locNormalcoord; + Standard_Integer *locTriIndices; + Standard_Integer tot_vertex_count=0; + Standard_Integer tot_normal_count=0; + Standard_Integer tot_invalid_normal_count=0; + Standard_Integer tot_triangle_count=0; + Standard_Integer tot_invalid_triangle_count=0; + std::vector facelist; + std::vector edgelist; + Standard_Real myDeviation{}; + TopoDS_Shape myShape; + Standard_Real aXmin{}, aYmin{} ,aZmin{} ,aXmax{} ,aYmax{} ,aZmax{}; + Standard_Real aBndBoxSz{}; + + void ComputeDefaultDeviation(); + void ComputeEdges(); + void EnsureMeshIsComputed(); + + public: + ShapeTesselator(TopoDS_Shape aShape); + ~ShapeTesselator(); + void Compute(bool compute_edges=false, float mesh_quality=1.0, bool parallel=false); + void Tesselate(bool compute_edges, float mesh_quality, bool parallel); + void JoinPrimitives(); + void SetDeviation(Standard_Real aDeviation); + void GetVertex(int ivert, float& x, float& y, float& z); + void GetNormal(int inorm, float& x, float& y, float& z); + void GetTriangleIndex(int triangleIdx, int& v1, int& v2, int& v3); + void GetEdgeVertex(int iEdge, int ivert, float& x, float& y, float& z); + Standard_Real* VerticesList(); + Standard_Real* NormalsList(); + Standard_Integer ObjGetTriangleCount(); + Standard_Integer ObjGetInvalidTriangleCount(); + Standard_Integer ObjGetVertexCount(); + Standard_Integer ObjGetNormalCount(); + Standard_Integer ObjGetInvalidNormalCount(); + Standard_Integer ObjGetEdgeCount(); + Standard_Integer ObjEdgeGetVertexCount(int iEdge); + void ObjGetTriangle(int trianglenum, int *vertices, int *normals); + std::vector GetVerticesPositionAsTuple(); + std::vector GetNormalsAsTuple(); +}; +#endif diff --git a/src/geom/tess_helpers.cpp b/src/geom/tess_helpers.cpp new file mode 100644 index 0000000..bbcae69 --- /dev/null +++ b/src/geom/tess_helpers.cpp @@ -0,0 +1,111 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#include +#include +#include "ShapeTesselator.h" +#include "../models/Mesh.h" +#include "../models/OccShape.h" +#include "../models/GroupReference.h" +#include "../models/MeshType.h" +#include "../binding_core.h" +#include "../helpers/helpers.h" + + +Mesh +tessellate_shape(int id, const TopoDS_Shape &shape, bool compute_edges, float mesh_quality, bool parallel_meshing) { + ShapeTesselator shape_tess(shape); + shape_tess.Compute(compute_edges, mesh_quality, parallel_meshing); + std::vector pos = shape_tess.GetVerticesPositionAsTuple(); + int number_of_triangles = shape_tess.ObjGetTriangleCount(); + + std::vector faces(number_of_triangles * 3); + for (size_t i = 0; i < faces.size(); ++i) { + faces[i] = static_cast(i); + } + + Mesh mesh(id, pos, faces); + return mesh; +} + + +Mesh concatenate_meshes(const std::vector> &meshes) { + std::vector groups; + size_t total_positions = 0; + size_t total_indices = 0; + size_t total_normals = 0; + bool has_normal = !meshes[0]->normals.empty(); + + // Calculate the total number of elements in positions, indices, and normals + for (const auto &s: meshes) { + total_positions += s->positions.size(); + total_indices += s->indices.size(); + if (has_normal) { + total_normals += s->normals.size(); + } + } + + // Pre-allocate vector sizes + std::vector position_list(total_positions); + std::vector indices_list(total_indices); + std::vector normal_list(has_normal ? total_normals : 0); + + size_t position_offset = 0; + size_t indices_offset = 0; + size_t normal_offset = 0; + size_t sum_positions = 0; + + for (const auto &s: meshes) { + groups.emplace_back(s->id, indices_offset, s->indices.size()); + + // Copy positions + std::copy(s->positions.begin(), s->positions.end(), position_list.begin() + position_offset); + position_offset += s->positions.size(); + + // Copy and adjust indices + std::transform(s->indices.begin(), s->indices.end(), indices_list.begin() + indices_offset, + [sum_positions](uint32_t index) { return index + sum_positions / 3; }); + indices_offset += s->indices.size(); + + // Copy normals if they exist + if (has_normal) { + std::copy(s->normals.begin(), s->normals.end(), normal_list.begin() + normal_offset); + normal_offset += s->normals.size(); + } + + sum_positions += s->positions.size(); + } + Mesh merged_mesh = Mesh(meshes[0]->id, + position_list, + indices_list, + {}, + has_normal ? normal_list : std::vector{}, + meshes[0]->mesh_type, + meshes[0]->color, + groups); + return merged_mesh; +} + +Mesh get_box_mesh(const std::vector &box_origin, + const std::vector &box_dims) { + + TopoDS_Solid box = create_box(box_origin, box_dims); + Mesh mesh = tessellate_shape(0, box, true, 1.0, false); + return mesh; +} + +nanobind::class_ geom_module(nb::module_ &m) { + m.def("get_box_mesh", &get_box_mesh, "box_origin"_a, "box_dims"_a, "Write a box to a step file"); + { + return nb::class_(m, "Mesh") + .def_ro("id", &Mesh::id, "The id of the mesh") + .def_ro("positions", &Mesh::positions, "The positions of the mesh") + .def_ro("indices", &Mesh::indices, "The indices of the mesh") + .def_ro("normals", &Mesh::normals, "The normals of the mesh") + .def_ro("mesh_type", &Mesh::mesh_type, "The type of mesh", nb::enum_(m, "MeshType")) + .def_ro("color", &Mesh::color, "The color of the mesh", nb::class_(m, "Color")) + .def_ro("groups", &Mesh::group_reference, "The groups of the mesh", + nb::class_(m, "GroupReference")); + }; +} \ No newline at end of file diff --git a/src/geom/tess_helpers.h b/src/geom/tess_helpers.h new file mode 100644 index 0000000..ab3422e --- /dev/null +++ b/src/geom/tess_helpers.h @@ -0,0 +1,17 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_TESS_HELPERS_H +#define NANO_OCCT_TESS_HELPERS_H + +#include +#include "ShapeTesselator.h" +#include "../models/Mesh.h" +#include "../binding_core.h" + +Mesh tessellate_shape(const TopoDS_Shape &shape, bool compute_edges, float mesh_quality, bool parallel_meshing); + +nanobind::class_ geom_module(nb::module_ &m); + +#endif //NANO_OCCT_TESS_HELPERS_H diff --git a/src/helpers/helpers.cpp b/src/helpers/helpers.cpp new file mode 100644 index 0000000..a1f095e --- /dev/null +++ b/src/helpers/helpers.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../models/Color.h" + + +TopoDS_Solid create_box(const std::vector &box_origin, const std::vector &box_dims) { + gp_Pnt aBoxOrigin(box_origin[0], box_origin[1], box_origin[2]); + gp_XYZ aBoxDims(box_dims[0], box_dims[1], box_dims[2]); + + return BRepPrimAPI_MakeBox(aBoxOrigin, aBoxDims.X(), aBoxDims.Y(), aBoxDims.Z()); +} + +// a function that returns a random tuple std::tuple +Color random_color() { + static std::mt19937 generator(std::random_device{}()); + static std::uniform_real_distribution distribution(0.0, 1.0); + return Color(distribution(generator), distribution(generator), distribution(generator)); +} + +void set_name(const TDF_Label &label, const std::optional &name) { + if (!name) { + return; + } + TCollection_ExtendedString ext_name(name->c_str()); + TDataStd_Name::Set(label, ext_name); +} + +void set_color(const TDF_Label &label, const Color &color, + const Handle(XCAFDoc_ColorTool) &tool) { + float r = color.r; + float g = color.g; + float b = color.b; + + Quantity_Color qty_color(r, g, b, Quantity_TOC_RGB); + tool->SetColor(label, qty_color, XCAFDoc_ColorType::XCAFDoc_ColorSurf); +} diff --git a/src/helpers/helpers.h b/src/helpers/helpers.h new file mode 100644 index 0000000..dd198ee --- /dev/null +++ b/src/helpers/helpers.h @@ -0,0 +1,27 @@ +#ifndef NANO_OCCT_HELPERS_H +#define NANO_OCCT_HELPERS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../models/Color.h" + + +TopoDS_Solid create_box(const std::vector &box_origin, const std::vector &box_dims); + +Color random_color(); + +void set_name(const TDF_Label &label, const std::optional &name); + +void set_color(const TDF_Label &label, const Color &color, + const Handle(XCAFDoc_ColorTool) &tool); + +#endif // NANO_OCCT_HELPERS_H diff --git a/src/models/Color.h b/src/models/Color.h new file mode 100644 index 0000000..664a087 --- /dev/null +++ b/src/models/Color.h @@ -0,0 +1,18 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_COLOR_H +#define NANO_OCCT_COLOR_H + +class Color { +public: + float r; + float g; + float b; + float a; + + explicit Color(float r=0.5f, float g=0.5f, float b=0.5f, float a = 1.0f); +}; + +#endif //NANO_OCCT_COLOR_H diff --git a/src/models/GroupReference.h b/src/models/GroupReference.h new file mode 100644 index 0000000..6ac49a2 --- /dev/null +++ b/src/models/GroupReference.h @@ -0,0 +1,17 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_GROUPREFERENCE_H +#define NANO_OCCT_GROUPREFERENCE_H + +class GroupReference { +public: + int node_id; + int start; + int length; + + GroupReference(int node_id, int start, int length); +}; + +#endif //NANO_OCCT_GROUPREFERENCE_H diff --git a/src/models/Mesh.h b/src/models/Mesh.h new file mode 100644 index 0000000..33f63d9 --- /dev/null +++ b/src/models/Mesh.h @@ -0,0 +1,36 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_MESH_H +#define NANO_OCCT_MESH_H + +#include +#include +#include +#include "MeshType.h" +#include "GroupReference.h" +#include "Color.h" + +class Mesh { +public: + Mesh(int id, + std::vector positions, + std::vector faces, + std::vector edges = {}, + std::vector normals = {}, + MeshType mesh_type = MeshType::TRIANGLES, + Color color = Color(), + std::vector group_reference = {}); + + int id; + std::vector positions; + std::vector indices; + std::vector edges; + std::vector normals; + MeshType mesh_type; + Color color; + std::vector group_reference; +}; + +#endif //NANO_OCCT_MESH_H diff --git a/src/models/MeshType.h b/src/models/MeshType.h new file mode 100644 index 0000000..5b559ac --- /dev/null +++ b/src/models/MeshType.h @@ -0,0 +1,20 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_MESHTYPE_H +#define NANO_OCCT_MESHTYPE_H + +enum class MeshType { + POINTS = 0, + LINES = 1, + LINE_LOOP = 2, + LINE_STRIP = 3, + TRIANGLES = 4, + TRIANGLE_STRIP = 5, + TRIANGLE_FAN = 6 +}; + +MeshType from_int(int value); + +#endif //NANO_OCCT_MESHTYPE_H diff --git a/src/models/Models.cpp b/src/models/Models.cpp new file mode 100644 index 0000000..5b445fe --- /dev/null +++ b/src/models/Models.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include +#include "OccShape.h" +#include "MeshType.h" +#include "Mesh.h" +#include "GroupReference.h" +#include "Color.h" + +Color::Color(float r, float g, float b, float a) : r(r), g(g), b(b), a(a) {} + +MeshType from_int(int value) { + if (value < 0 || value > 6) { + throw std::out_of_range("Invalid MeshType value"); + } + return static_cast(value); +} + +Mesh::Mesh(int id, std::vector positions, std::vector faces, + std::vector edges, std::vector normals, + MeshType mesh_type, Color color, std::vector group_reference) + : id(id), + positions(std::move(positions)), + indices(std::move(faces)), + edges(std::move(edges)), + normals(std::move(normals)), + mesh_type(mesh_type), + color(std::move(color)), + group_reference(std::move(group_reference)) {} + +OccShape::OccShape(TopoDS_Shape shape, + Color color, + int num_tot_entities, + std::optional name) + : shape(std::move(shape)), + color(color), + num_tot_entities(num_tot_entities), + name(std::move(name)) {} + +GroupReference::GroupReference(int node_id, int start, int length) + : node_id(node_id), start(start), length(length) {} \ No newline at end of file diff --git a/src/models/OccShape.h b/src/models/OccShape.h new file mode 100644 index 0000000..c644d6f --- /dev/null +++ b/src/models/OccShape.h @@ -0,0 +1,27 @@ +// +// Created by Kristoffer on 07/05/2023. +// + +#ifndef NANO_OCCT_OCCSHAPE_H +#define NANO_OCCT_OCCSHAPE_H + +#include +#include +#include +#include "Color.h" + + +class OccShape { +public: + explicit OccShape(TopoDS_Shape shape, + Color color = Color(), + int num_tot_entities = 0, + std::optional name = std::nullopt); + + TopoDS_Shape shape; + Color color; + int num_tot_entities; + std::optional name; +}; + +#endif //NANO_OCCT_OCCSHAPE_H diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..dc838d0 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +*.stp \ No newline at end of file diff --git a/tests/py/test_basic.py b/tests/py/test_basic.py new file mode 100644 index 0000000..8c1da07 --- /dev/null +++ b/tests/py/test_basic.py @@ -0,0 +1,39 @@ +import random + +import adacpp + + +def create_box_grid(grid_size): + # Randomized box sizes + min_size, max_size = 0.5, 1.0 + origins = [] + dimensions = [] + + for x in range(grid_size): + for y in range(grid_size): + for z in range(grid_size): + width = random.uniform(min_size, max_size) + height = random.uniform(min_size, max_size) + depth = random.uniform(min_size, max_size) + origins.append((x * max_size, y * max_size, z * max_size)) + dimensions.append((width, height, depth)) + + return origins, dimensions + + +def test_basic_write_step(): + adacpp.write_box_to_step("mybox.stp", (0, 0, 0), (10, 10, 10)) + + +def test_basic_write_list_of_boxes_to_step(): + origins, dimensions = create_box_grid(10) + adacpp.write_boxes_to_step("myboxes.stp", origins, dimensions) + + +def test_basic_mesh(): + mesh = adacpp.get_box_mesh((0, 0, 0), (10, 10, 10)) + assert mesh.__class__.__name__ == "Mesh" + assert hasattr(mesh, "positions") + assert hasattr(mesh, "normals") + assert hasattr(mesh, "indices") + print(mesh)