Repository: aewallin/opencamlib (language: python)

Issue:
Try to use custom delocate script
`delocate-wheel` does not have an exclude option, so we have to DIY.

See: https://github.com/matthew-brett/delocate/issues/77


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 31858bea..f60defb9 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -63,7 +63,12 @@ jobs:
       - name: cxxlib
         shell: bash
         run: |
-          git describe --tags > src/git-tag.txt
+          if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
+            version="${GITHUB_REF##*/}"
+          else
+            version="$(date '+%Y.%-m.%-d').dev${GITHUB_RUN_NUMBER}"
+          fi
+          echo "${version}" > src/git-tag.txt
 
           if [ "${{ matrix.docker_image }}" != "" ]; then
             export OCL_DOCKER_IMAGE="${{ matrix.docker_image }}"
@@ -146,7 +151,12 @@ jobs:
       - name: nodejslib
         shell: bash
         run: |
-          git describe --tags > src/git-tag.txt
+          if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
+            version="${GITHUB_REF##*/}"
+          else
+            version="$(date '+%Y.%-m.%-d')-dev.${GITHUB_RUN_NUMBER}"
+          fi
+          echo "${version}" > src/git-tag.txt
 
           if [ "${{ matrix.docker_image }}" != "" ]; then
             export OCL_DOCKER_IMAGE="${{ matrix.docker_image }}"
@@ -262,7 +272,7 @@ jobs:
             version="$(date '+%Y.%-m.%-d').dev${GITHUB_RUN_NUMBER}"
           fi
           sed -i.bak "s/^version = .*/version = \"${version}\"/g" pyproject.toml && rm pyproject.toml.bak
-          git describe --tags > src/git-tag.txt
+          echo "${version}" > src/git-tag.txt
       - name: Set up QEMU
         uses: docker/setup-qemu-action@v2
         if: matrix.architecture == 'aarch64'
@@ -435,7 +445,11 @@ jobs:
         shell: bash
         run: |
           mkdir dist
-          mv prebuilds/*-cxx-*/* dist
+          zip -r dist/linux-cxx-x86_64.zip prebuilds/linux-cxx-x86_64
+          zip -r dist/macos-cxx-arm64.zip prebuilds/macos-cxx-arm64
+          zip -r dist/macos-cxx-x86_64.zip prebuilds/macos-cxx-x86_64
+          zip -r dist/windows-cxx-ia32.zip prebuilds/windows-cxx-ia32
+          zip -r dist/windows-cxx-x64.zip prebuilds/windows-cxx-x64
       - name: Release
         uses: softprops/action-gh-release@v1
         with:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 965d4d7b..60569de3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,6 @@
-project(OpenCAMLib)
 cmake_minimum_required(VERSION 3.15...3.25)
 
+project(OpenCAMLib LANGUAGES CXX)
+
 set( CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src )
 add_subdirectory( src )
diff --git a/install.sh b/install.sh
index fc1d9a13..3d7f51b0 100755
--- a/install.sh
+++ b/install.sh
@@ -31,7 +31,7 @@ Options:
   --boost-python-version      Set the python version to look for when compiling Boost (only valid when using --install-boost and --boost-with-python)
 
   --python-executable         Set a custom path (or name of) the Python executable (only valid when using --build-library python)
-  --python-prefix             Set the python prefix, this will be passed to CMake as Python_ROOT_DIR, to make sure CMake is using the correct Python installation. (only valid when using --build-library python)
+  --python-prefix             Set the python prefix, this will be passed to CMake as Python3_ROOT_DIR, to make sure CMake is using the correct Python installation. (only valid when using --build-library python)
   --python-pip-install        Uses "pip install ." to compile and install the Python library (only valid when using --build-library python)
 
   --platform                  Set the platform, for when auto-detection doesn't work (one of: windows, macos, linux)
@@ -121,7 +121,7 @@ if [ "${OCL_BUILD_TYPE}" = "debug" ]; then
     build_type="Debug"
     build_type_lower="debug"
 else
-    build_type="Release"
+    build_type="RelWithDebInfo"
     build_type_lower="release"
 fi
 build_dir="${project_dir}/build/${OCL_BUILD_LIBRARY}/${build_type_lower}"
@@ -541,7 +541,7 @@ ${OCL_BOOST_PREFIX:+"-D BOOST_ROOT=${OCL_BOOST_PREFIX} "}"
             -D Boost_ADDITIONAL_VERSIONS="${boost_additional_versions}" \
             -D CMAKE_INSTALL_PREFIX="${OCL_INSTALL_PREFIX:-"${install_prefix_fallback}"}" \
             ${OCL_DISABLE_OPENMP:+"-DUSE_OPENMP=OFF"} \
-            ${OCL_PYTHON_PREFIX:+"-DPython_ROOT_DIR=${OCL_PYTHON_PREFIX}"} \
+            ${OCL_PYTHON_PREFIX:+"-DPython3_ROOT_DIR=${OCL_PYTHON_PREFIX}"} \
             ${OCL_BOOST_PREFIX:+"-DBOOST_ROOT=${OCL_BOOST_PREFIX}"} \
             ../../..
         set +x
diff --git a/pyproject.toml b/pyproject.toml
index 28550fac..20be73f3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,8 +42,8 @@ wheel.packages = ["src/pythonlib/opencamlib"]
 
 [tool.scikit-build.cmake.define]
 BUILD_PY_LIB = "ON"
-USE_OPENMP = "ON"
 Boost_ADDITIONAL_VERSIONS = "1.80.0;1.79.0;1.78.0;1.77.0;1.76.0;1.75.0;1.74.0;1.73.0;1.72.0;1.71.0;1.70.0"
+BUILD_DOC = "OFF"
 
 [tool.cibuildwheel]
 build = ["cp37*", "cp38*", "cp39*", "cp310*", "cp311*"]
@@ -63,3 +63,4 @@ before-build = "cd {package} && bash ./install.sh --install-boost --boost-with-p
 archs = ["x86_64", "arm64"]
 before-all = "cd {package} && bash ./install.sh --install-ci-deps"
 before-build = "cd {package} && bash ./install.sh --install-boost --boost-with-python --python-executable python"
+repair-wheel-command = "python src/pythonlib/delocate-wheel.py --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 124fadb6..4915cea5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,4 @@
-project(OpenCAMLib)
+project(OpenCAMLib LANGUAGES CXX)
 
 set(CMAKE_VERBOSE_MAKEFILE ON)
 
@@ -45,7 +45,7 @@ option(USE_OPENMP
     "Use OpenMP for parallel computation" ON)
 
 option(VERSION_STRING
-    "Set version string" OFF)
+    "Set version string")
 
 option(USE_PY_3
     "Use Python V3" ON)
@@ -90,6 +90,12 @@ if(UNIX)
   endif()
 endif()
 
+include(CheckIPOSupported)
+check_ipo_supported(RESULT got_ipo_support)
+if(got_ipo_support)
+  set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+endif()
+
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
 set(Boost_DEBUG ON CACHE BOOL "boost-debug")
@@ -142,26 +148,10 @@ if(USE_OPENMP)
       set(OpenMP_omp_LIBRARY "$ENV{OPENMP_PREFIX_MACOS}/lib/libomp.dylib")
       include_directories("$ENV{OPENMP_PREFIX_MACOS}/include")
     else()
-      find_program(brew brew)
-      if(brew)
-        message(STATUS "Found homebrew at ${brew}.")
-        execute_process(COMMAND "${brew}" --prefix libomp
-          RESULT_VARIABLE brew_libomp_found
-          OUTPUT_VARIABLE brew_libomp OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
-        if(brew_libomp_found EQUAL 0)
-          message(STATUS "Found libomp at ${brew_libomp}.")
-          set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp")
-          set(OpenMP_C_LIB_NAMES "omp")
-          set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp")
-          set(OpenMP_CXX_LIB_NAMES "omp")
-          set(OpenMP_omp_LIBRARY "${brew_libomp}/lib/libomp.dylib")
-          include_directories("${brew_libomp}/include")
-        endif()
-      else()
-        message(STATUS "Homebrew not found, please install homebrew and run `brew install libomp` for OpenMP support.")
-      endif()
+      list(APPEND CMAKE_PREFIX_PATH "/usr/local/opt/libomp")
+      list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew/opt/libomp")
     endif()
-  endif(APPLE)
+  endif()
   find_package(OpenMP REQUIRED)
   if(OPENMP_FOUND)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
diff --git a/src/algo/adaptivewaterline.cpp b/src/algo/adaptivewaterline.cpp
index 40767c97..393ac507 100644
--- a/src/algo/adaptivewaterline.cpp
+++ b/src/algo/adaptivewaterline.cpp
@@ -51,23 +51,16 @@ AdaptiveWaterline::AdaptiveWaterline() {
     subOp.push_back( new FiberPushCutter() );
     subOp[0]->setXDirection();
     subOp[1]->setYDirection();
-    nthreads=1;
+    nthreads = 1;
 #ifdef _OPENMP
-    nthreads = omp_get_num_procs(); 
-    // omp_set_dynamic(0);
-    // omp_set_nested(1);
+    nthreads = omp_get_num_procs();
 #endif
     sampling = 1.0;
     min_sampling = 0.1;
     cosLimit = 0.999;
 }
 
-AdaptiveWaterline::~AdaptiveWaterline() {
-    // std::cout << "~AdaptiveWaterline(): subOp.size()= " << subOp.size() <<"\n";
-    // delete subOp[1];
-    // delete subOp[0];
-    // subOp.clear();
-}
+AdaptiveWaterline::~AdaptiveWaterline() {}
 
 void AdaptiveWaterline::run() {
     adaptive_sampling_run();
@@ -86,7 +79,6 @@ void AdaptiveWaterline::adaptive_sampling_run() {
     maxy = surf->bb.maxpt.y + 2*cutter->getRadius();
     Line* line = new Line( Point(minx,miny,zh) , Point(maxx,maxy,zh) );
     Span* linespan = new LineSpan(*line);
-    
 #ifdef _WIN32 // OpenMP task not supported with the version 2 of VS2013 OpenMP
 	#pragma omp parallel sections
 	{
@@ -144,7 +136,6 @@ void AdaptiveWaterline::adaptive_sampling_run() {
 
     delete line;
     delete linespan;
-    
 }
 
 
@@ -152,8 +143,9 @@ void AdaptiveWaterline::xfiber_adaptive_sample(const Span* span, double start_t,
     const double mid_t = start_t + (stop_t-start_t)/2.0; // mid point sample
     assert( mid_t > start_t );  assert( mid_t < stop_t );
     //std::cout << "xfiber sample= ( " << start_t << " , " << stop_t << " ) \n";
-    Point mid_p1 = Point( minx, span->getPoint( mid_t ).y,  zh );
-    Point mid_p2 = Point( maxx, span->getPoint( mid_t ).y,  zh );
+    double mid_y = span->getPoint( mid_t ).y;
+    Point mid_p1 = Point( minx, mid_y,  zh );
+    Point mid_p2 = Point( maxx, mid_y,  zh );
     Fiber mid_f = Fiber( mid_p1, mid_p2 );
     subOp[0]->run( mid_f );
     double fw_step = fabs( start_f.p1.y - stop_f.p1.y ) ;
@@ -167,15 +159,16 @@ void AdaptiveWaterline::xfiber_adaptive_sample(const Span* span, double start_t,
         }
     } else {
         xfibers.push_back(stop_f);
-    } 
+    }
 }
 
 void AdaptiveWaterline::yfiber_adaptive_sample(const Span* span, double start_t, double stop_t, Fiber start_f, Fiber stop_f) {
     const double mid_t = start_t + (stop_t-start_t)/2.0; // mid point sample
     assert( mid_t > start_t );  assert( mid_t < stop_t );
     //std::cout << "yfiber sample= ( " << start_t << " , " << stop_t << " ) \n";
-    Point mid_p1 = Point( span->getPoint( mid_t ).x, miny,  zh );
-    Point mid_p2 = Point( span->getPoint( mid_t ).x, maxy, zh );
+    double mid_x = span->getPoint( mid_t ).x;
+    Point mid_p1 = Point( mid_x, miny,  zh );
+    Point mid_p2 = Point( mid_x, maxy, zh );
     Fiber mid_f = Fiber( mid_p1, mid_p2 );
     subOp[1]->run( mid_f );
     double fw_step = fabs( start_f.p1.x - stop_f.p1.x ) ;
@@ -188,7 +181,7 @@ void AdaptiveWaterline::yfiber_adaptive_sample(const Span* span, double start_t,
             yfiber_adaptive_sample( span, mid_t  , stop_t, mid_f  , stop_f );
         }
     } else {
-        yfibers.push_back(stop_f); 
+        yfibers.push_back(stop_f);
     }
 }
 
@@ -213,7 +206,7 @@ bool AdaptiveWaterline::flat( Fiber& start, Fiber& mid, Fiber& stop ) const {
             }
         }
         return true;
-    } 
+    }
 }
 
 
diff --git a/src/algo/clsurface.hpp b/src/algo/clsurface.hpp
index 241f7f8d..603eab86 100644
--- a/src/algo/clsurface.hpp
+++ b/src/algo/clsurface.hpp
@@ -238,9 +238,9 @@ class CutterLocationSurface : public Operation {
             // now loop through edges again:
             f_edges = g.face_edges(f);
             assert( f_edges.size() == 8 );
-            BOOST_FOREACH( CLSEdge e, f_edges ) {
-                std::cout << e << "\n";
-            }
+            // BOOST_FOREACH( CLSEdge e, f_edges ) {
+            //     std::cout << e << "\n";
+            // }
         }
         
         virtual void run() {
diff --git a/src/algo/fiber.cpp b/src/algo/fiber.cpp
index cb34bc9e..66ce67b8 100644
--- a/src/algo/fiber.cpp
+++ b/src/algo/fiber.cpp
@@ -82,7 +82,7 @@ void Fiber::addInterval(Interval& i) {
         }
         overlaps.push_back(i);
         // now build a new interval from i and the overlaps
-        Interval sumint;        
+        Interval sumint;
         BOOST_FOREACH(Interval intr, overlaps) {
             sumint.updateLower( intr.lower, intr.lower_cc );
             sumint.updateUpper( intr.upper, intr.upper_cc );
@@ -95,7 +95,8 @@ void Fiber::addInterval(Interval& i) {
 double Fiber::tval(Point& p) const {
     // fiber is  f = p1 + t * (p2-p1)
     // t = (f-p1).dot(p2-p1) / (p2-p1).dot(p2-p1)
-    return  (p-p1).dot(p2-p1) / (p2-p1).dot(p2-p1);
+    Point p2minp1 = p2 - p1;
+    return (p-p1).dot(p2minp1) / (p2minp1).dot(p2minp1);
 }
 
 Point Fiber::point(double t) const {
diff --git a/src/algo/fiberpushcutter.cpp b/src/algo/fiberpushcutter.cpp
index 3665859c..a213d05f 100644
--- a/src/algo/fiberpushcutter.cpp
+++ b/src/algo/fiberpushcutter.cpp
@@ -37,10 +37,6 @@ namespace ocl
 
 FiberPushCutter::FiberPushCutter() {
     nCalls = 0;
-    nthreads = 1;
-#ifdef _OPENMP
-    nthreads = omp_get_num_procs(); // figure out how many cores we have
-#endif
     cutter = NULL;
     bucketSize = 1;
     root = new KDTree<Triangle>();
@@ -96,7 +92,7 @@ void FiberPushCutter::pushCutter2(Fiber& f) {
     for ( it=tris->begin() ; it!=it_end ; ++it) {
 		i = new Interval();
 		cutter->pushCutter(f,*i,*it);
-		f.addInterval(*i); 
+		f.addInterval(*i);
 		++nCalls;
 		delete i;
     }
diff --git a/src/algo/smart_weave.cpp b/src/algo/smart_weave.cpp
index a57dca0a..9167594d 100644
--- a/src/algo/smart_weave.cpp
+++ b/src/algo/smart_weave.cpp
@@ -52,7 +52,7 @@ std::pair<Vertex,Vertex> SmartWeave::find_neighbor_vertices( VertexPair v_pair,
 
 // this is the new smarter build() which uses less RAM
 void SmartWeave::build() {
-    std::cout << " SimpleWeave::build()... \n";
+    // std::cout << " SmartWeave::build()... \n";
     
     // this adds all CL-vertices from x-intervals
     // it also populates the xi.intersections_fibers set of intersecting y-fibers
@@ -296,7 +296,7 @@ void SmartWeave::add_all_edges()
 {
     std::vector<Vertex> vertices = g.vertices(); 
 
-    std::cout << "There are " << vertices.size() << " vertices.\n";
+    // std::cout << "There are " << vertices.size() << " vertices.\n";
     BOOST_FOREACH( Vertex& vertex, vertices ) {
         if( (g[vertex].type == INT) || (g[vertex].type == FULLINT) ) {
             std::vector<Vertex>                adjacent_vertices;
diff --git a/src/algo/zigzag.hpp b/src/algo/zigzag.hpp
index 1b59fa7f..10a87bf7 100644
--- a/src/algo/zigzag.hpp
+++ b/src/algo/zigzag.hpp
@@ -54,9 +54,9 @@ class ZigZag {
             // calculate a reasonable maximum/minimum step-over dist
             Point perp = dir.xyPerp();
             perp.xyNormalize();
-            std::cout << " minpt = " << bb.minpt << std::endl;
-            std::cout << " maxpt = " << bb.maxpt << std::endl;
-            std::cout << " perp = " << perp << std::endl;
+            // std::cout << " minpt = " << bb.minpt << std::endl;
+            // std::cout << " maxpt = " << bb.maxpt << std::endl;
+            // std::cout << " perp = " << perp << std::endl;
             double max_d = (bb.maxpt - origin).dot( perp );
             double min_d = (bb.minpt - origin).dot( perp );
             if ( max_d < min_d ) {
@@ -65,7 +65,7 @@ class ZigZag {
                 min_d = tmp;
             }
             //int n = min_d / stepOver; // some safety margin here... (required?)
-            std::cout << " max_d= " << max_d << " min_d= "<< min_d << std::endl;
+            // std::cout << " max_d= " << max_d << " min_d= "<< min_d << std::endl;
             
             std::vector<double> distances;
             for (double d = min_d ; d <= max_d ; d += stepOver ) {
diff --git a/src/common/brent_zero.hpp b/src/common/brent_zero.hpp
index abe5a60c..e486c40d 100644
--- a/src/common/brent_zero.hpp
+++ b/src/common/brent_zero.hpp
@@ -82,12 +82,12 @@ double brent_zero( double a, double b, double eps, double t, ErrObj* ell) {
                 p = s*(2.0*m*a*(q-r)-(b-a)*(r-1.0));
                 q = (q-1.0)*(r-1.0)*(s-1.0);
             }
-            
+
             if (p>0.0)
                 q=-q;
             else
                 p=-p; // make p negative
-            
+
             s=e;
             e=d;
             if ( (2.0*p < (3.0*m*q-fabs(tol*q))) && (p<fabs(0.5*s*q)) ) {
@@ -95,7 +95,7 @@ double brent_zero( double a, double b, double eps, double t, ErrObj* ell) {
             } else {
                 e = m;
                 d = e;
-            }                
+            }
         }
         a = b; // store the old b-solution in a
         fa = fb;
@@ -106,10 +106,12 @@ double brent_zero( double a, double b, double eps, double t, ErrObj* ell) {
         else
             b = b-tol;
         // std::cout << " brent_zero b=" << b << "\n";
-        
+
         fb = ell->error(b); // f(b);
-        
-        if ( ((fb>0.0) && (fc>0.0)) || ((fb<=0.0) && (fc<=0.0)) ) {
+
+        if (std::signbit(fb) == std::signbit(fc))
+        {
+            // if ((fb > 0.0 && fc > 0.0) || (fb <= 0.0 && fc <= 0.0)) {
             // fb and fc have the same sign
             c = a;  // so change c to a
             fc = fa;
diff --git a/src/common/kdtree.hpp b/src/common/kdtree.hpp
index 97b8f8be..11fc9c25 100644
--- a/src/common/kdtree.hpp
+++ b/src/common/kdtree.hpp
@@ -225,32 +225,45 @@ class KDTree {
                     for (unsigned int m=0;m<dimensions.size();++m) {
                         // dimensions[m] is the dimensions we want to update
                         // t.bb[ dimensions[m] ]   is the update value
+                        double point = dimensions[m];
+                        double tbbpoint = t.bb[point];
                         if (first) {
-                            maxval[ dimensions[m] ] = t.bb[ dimensions[m] ];
-                            minval[ dimensions[m] ] = t.bb[ dimensions[m] ];
+                            maxval[point] = tbbpoint;
+                            minval[point] = tbbpoint;
                             if (m==(dimensions.size()-1) )
                                 first=false;
                         } else {
-                            if (maxval[ dimensions[m] ] < t.bb[ dimensions[m] ] )
-                                maxval[ dimensions[m] ] = t.bb[ dimensions[m] ];
-                            if (minval[ dimensions[m] ] > t.bb[ dimensions[m] ])
-                                minval[ dimensions[m] ] = t.bb[ dimensions[m] ];
+                            if (maxval[point] < tbbpoint )
+                                maxval[point] = tbbpoint;
+                            if (minval[point] > tbbpoint)
+                                minval[point] = tbbpoint;
                         }
                     }
-                } 
-                std::vector<Spread*> spreads;// calculate the spread along each dimension
+                }
+
+                double max = 0;
+                double maxM = 0;
+                // std::vector<Spread*> spreads;// calculate the spread along each dimension
                 for (unsigned int m=0;m<dimensions.size();++m) {   // dim,  spread, start
-                    spreads.push_back( new Spread(dimensions[m] , 
-                                       maxval[dimensions[m]]-minval[dimensions[m]], 
-                                       minval[dimensions[m]] ) );  
+                    double val = maxval[dimensions[m]] - minval[dimensions[m]];
+                    if (val > max) {
+                        max = val;
+                        maxM = m;
+                    }
+                    // spreads.push_back( new Spread(dimensions[m] , 
+                    //                    maxval[dimensions[m]]-minval[dimensions[m]], 
+                    //                    minval[dimensions[m]] ) );  
                 }// priority-queue could also be used ??  
-                assert( !spreads.empty() );
+                // assert( !spreads.empty() );
                 //std::cout << " spreads.size()=" << spreads.size() << "\n";
-                std::sort(spreads.begin(), spreads.end(), Spread::spread_compare); // sort the list
-                Spread* s= new Spread(*spreads[0]); // this is the one we want to return
-                while(!spreads.empty()) delete spreads.back(), spreads.pop_back(); // delete the others
+                // std::sort(spreads.begin(), spreads.end(), Spread::spread_compare); // sort the list
+                // Spread* s= new Spread(*spreads[0]); // this is the one we want to return
+                // while(!spreads.empty()) delete spreads.back(), spreads.pop_back(); // delete the others
                 //std::cout << "calc_spread() done\n";
-                return s; // select the biggest spread and return
+                return new Spread(dimensions[maxM] , 
+                                       maxval[dimensions[maxM]]-minval[dimensions[maxM]], 
+                                       minval[dimensions[maxM]] );
+                // return s; // select the biggest spread and return
             } // end tris->size != 0
         } // end spread();
         
diff --git a/src/common/numeric.cpp b/src/common/numeric.cpp
index 004c3719..99644a12 100644
--- a/src/common/numeric.cpp
+++ b/src/common/numeric.cpp
@@ -150,7 +150,7 @@ bool xy_line_line_intersection( const Point& p1, const Point& p2, double& v,
     // =>
     // [ (p2-p1).x  -(p4-p3).x ] [ v ]  = [ (p3-p1).x ]
     // [ (p2-p1).y  -(p4-p3).y ] [ t ]  = [ (p3-p1).y ]
-    return two_by_two_solver( (p2-p1).x , -(p4-p3).x , (p2-p1).y , -(p4-p3).y,  (p3-p1).x, (p3-p1).y, v, t);
+    return two_by_two_solver(p2.x - p1.x, -(p4.x - p3.x), p2.y - p1.y, -(p4.y - p3.y), (p3.x - p1.x), p3.y - p1.y, v, t);
 }
 
 
diff --git a/src/cutters/ballcutter.cpp b/src/cutters/ballcutter.cpp
index 6316e1a5..20b911bc 100644
--- a/src/cutters/ballcutter.cpp
+++ b/src/cutters/ballcutter.cpp
@@ -120,7 +120,7 @@ bool BallCutter::calcCCandUpdateInterval( double t, const Point& p1, const Point
     CCPoint cc_tmp = cl_center.closestPoint(p1,p2); // cc-point on the edge, point on edge closest to center
     cc_tmp.type = EDGE_BALL;
     // require contact with lower hemishphere
-    return i.update_ifCCinEdgeAndTrue( t, cc_tmp, p1, p2, ((cl_center-cc_tmp).z >=0) );
+    return i.update_ifCCinEdgeAndTrue( t, cc_tmp, p1, p2, (cl_center.z-cc_tmp.z >=0) );
 }
     
 std::string BallCutter::str() const {
diff --git a/src/cutters/ellipse.cpp b/src/cutters/ellipse.cpp
index 76c88a60..9c5c64c7 100644
--- a/src/cutters/ellipse.cpp
+++ b/src/cutters/ellipse.cpp
@@ -193,7 +193,7 @@ int Ellipse::solver_brent() {
         return iters;
     } else if ( fabs( error(bpos) ) < OE_ERROR_TOLERANCE ) { // or bpos might be the solution?
         EllipsePosition1 = bpos;
-        find_EllipsePosition2(); 
+        find_EllipsePosition2();
         return iters;
     }
     // neither apos nor bpos is the solution
@@ -232,41 +232,45 @@ bool AlignedEllipse::aligned_solver( const Fiber& f ) {
     bool found_positive=false;
     bool found_negative=false;
     tmp.setDiangle( xyVectorToDiangle(s1,t1) );
-    if (error(tmp.diangle) > 0) {
+    double err = error(tmp.diangle);
+    if (err > 0) {
         found_positive = true;
         apos = tmp;
-    } else if (error(tmp.diangle) < 0) {
+    } else if (err < 0) {
         found_negative = true;
         bpos = tmp;
     }
     tmp.setDiangle( xyVectorToDiangle(s1,-t1) );
-    if (error(tmp.diangle) > 0) {
+    err = error(tmp.diangle);
+    if (err > 0) {
         found_positive = true;
         apos = tmp;
     }
-    else if (error(tmp.diangle) < 0) {
+    else if (err < 0) {
         found_negative = true;
         bpos = tmp;
-    }    
+    }
     tmp.setDiangle( xyVectorToDiangle(-s1,t1) );
-    if (error(tmp.diangle) > 0) {
+    err = error(tmp.diangle);
+    if (err > 0) {
         found_positive = true;
         apos = tmp;
     }
-    else if (error(tmp.diangle) < 0) {
+    else if (err < 0) {
         found_negative = true;
         bpos = tmp;
     }
     tmp.setDiangle( xyVectorToDiangle(-s1,-t1) );
-    if (error(tmp.diangle) > 0) {
+    err = error(tmp.diangle);
+    if (err > 0) {
         found_positive = true;
         apos = tmp;
     }
-    else if (error(tmp.diangle) < 0) {
+    else if (err < 0) {
         found_negative = true;
         bpos = tmp;
     }
-    
+
     if (found_positive) {
         if (found_negative) {
             assert( this->error(apos.diangle) * this->error(bpos.diangle) < 0.0 ); // root is now bracketed.
diff --git a/src/cxxlib/cxxlib.cmake b/src/cxxlib/cxxlib.cmake
index 12f030cd..7c97e5d3 100644
--- a/src/cxxlib/cxxlib.cmake
+++ b/src/cxxlib/cxxlib.cmake
@@ -33,6 +33,17 @@ target_include_directories(ocl
     $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
 )
 
+# disable /GL and enable /LTCG (see https://github.com/luxonis/depthai-core/issues/334)
+if(WIN32 AND MSVC) # AND CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS
+  get_target_property(_INTER_OPT ocl INTERPROCEDURAL_OPTIMIZATION)
+  if(_INTER_OPT)
+    message(STATUS "Workaround MSVC dll exports with INTERPROCEDURAL_OPTIMIZATION")
+    set_target_properties(ocl PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF)
+    target_link_options(ocl PRIVATE /LTCG)
+  endif()
+  unset(_INTER_OPT)
+endif()
+
 # link with Boost and optionally with OpenMP
 target_link_libraries(ocl PUBLIC Boost::boost)
 if(USE_OPENMP)
@@ -50,6 +61,23 @@ install(
   PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
 )
 
+if(APPLE)
+  target_compile_options(ocl PRIVATE -g)
+  # if(CMAKE_CXX_FLAGS MATCHES "-flto")
+    set(lto_object ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ocl-lto.o)
+    set_property(TARGET ocl APPEND_STRING PROPERTY
+      LINK_FLAGS " -Wl,-object_path_lto -Wl,${lto_object}")
+  # endif()
+  add_custom_command(TARGET ocl POST_BUILD
+    COMMAND xcrun dsymutil $<TARGET_FILE:ocl>
+    COMMAND xcrun strip -Sl $<TARGET_FILE:ocl>)
+  install(
+    FILES $<TARGET_FILE:ocl>.dSYM
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/opencamlib
+    PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
+  )
+endif()
+
 # this install the cmake targets
 install(
   EXPORT ocltargets
@@ -71,17 +99,18 @@ install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
   DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/cmake/${PROJECT_NAME}
 )
 
-if(USE_OPENMP)
-  if(APPLE)
-    # copy libomp into install directory
-    install(
-      FILES ${OpenMP_omp_LIBRARY}
-      DESTINATION ${CMAKE_INSTALL_LIBDIR}/opencamlib
-      PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
-    )
-    # fix loader path
-    add_custom_command(TARGET ocl POST_BUILD
-      COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change `otool -L $<TARGET_FILE:ocl> | grep libomp | cut -d ' ' -f1 | xargs echo` "@loader_path/libomp.dylib" $<TARGET_FILE:ocl>
-    )
-  endif()
+if(USE_OPENMP AND APPLE)
+  # add homebrew libomp paths to the INSTALL_RPATH, and the @loader_path last as a fallback.
+  set_target_properties(ocl PROPERTIES
+    INSTALL_RPATH "/opt/homebrew/opt/libomp/lib;/usr/local/opt/libomp/lib;@loader_path")
+  # copy libomp into install directory
+  install(
+    FILES ${OpenMP_CXX_LIBRARIES}
+    DESTINATION ${CMAKE_INSTALL_LIBDIR}/opencamlib
+    PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
+  )
+  # fix loader path
+  add_custom_command(TARGET ocl POST_BUILD
+    COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change `otool -L $<TARGET_FILE:ocl> | grep libomp | cut -d ' ' -f1 | xargs echo` "@rpath/libomp.dylib" $<TARGET_FILE:ocl>
+  )
 endif()
\ No newline at end of file
diff --git a/src/geo/point.cpp b/src/geo/point.cpp
index daf3d33b..c161c28c 100644
--- a/src/geo/point.cpp
+++ b/src/geo/point.cpp
@@ -73,8 +73,9 @@ double Point::dot(const Point &p) const {
 }
 
 void Point::normalize() {
-    if (this->norm() != 0.0)
-        *this *=(1/this->norm());
+    double norm = this->norm();
+    if (norm != 0.0)
+        *this *=(1/norm);
 }
 
 double Point::xyNorm() const {
@@ -217,30 +218,24 @@ bool Point::isRight(const Point &p1, const Point &p2) const
     if (t > 0.00000000000001) /// \todo FIXME: hardcoded magic number...
         return true;
     else
-        return false;    
+        return false;
 }
 
 bool Point::isInside(const Triangle &t) const {
-    // point in triangle test
-    // http://www.blackpawn.com/texts/pointinpoly/default.html
-    
-    Point v0 = t.p[2] - t.p[0];
-    Point v1 = t.p[1] - t.p[0];
-    Point v2 = *this  - t.p[0];
-    
-    double dot00 = v0.dot(v0);
-    double dot01 = v0.dot(v1);
-    double dot02 = v0.dot(v2);
-    double dot11 = v1.dot(v1);
-    double dot12 = v1.dot(v2);
-    
-    double invD = 1.0 / ( dot00 *dot11 - dot01*dot01 );
-    // barycentric coordinates
-    double u = (dot11 * dot02 - dot01 * dot12) * invD;
-    double v = (dot00 * dot12 - dot01 * dot02) * invD;
+    Point p = *this;
+    Point a = t.p[0];
+    Point b = t.p[1];
+    Point c = t.p[2];
 
-    // Check if point is in triangle
-    return (u > 0.0) && (v > 0.0) && (u + v < 1.0);
+    // Compute barycentric coordinates
+    double u = (a.y * c.x - a.x * c.y + (c.y - a.y) * p.x + (a.x - c.x) * p.y)
+             / (a.y * c.x - a.x * c.y + (c.y - a.y) * b.x + (a.x - c.x) * b.y);
+
+    double v = (a.x * b.y - a.y * b.x + (a.y - b.y) * p.x + (b.x - a.x) * p.y)
+             / (a.x * b.y - a.y * b.x + (a.y - b.y) * c.x + (b.x - a.x) * c.y);
+
+    // Check if point is inside triangle
+    return u > 0.0 && v > 0.0 && (u + v) < 1.0;
 }
 
 bool Point::isInside(const Point& p1, const Point& p2) const {
@@ -248,19 +243,15 @@ bool Point::isInside(const Point& p1, const Point& p2) const {
     // p1 + t*(p2-p1) = p
     // p1*(p2-p1) + t * (p2-p1)*(p2-p1) = p*(p2-p1)
     // t = (p - p1 )*(p2-p1) / (p2-p1)*(p2-p1)
-    double t = (*this - p1).dot( p2-p1 ) / (p2-p1).dot(p2-p1);
-    if (t > 1.0)
-        return false;
-    else if (t < 0.0)
+    Point p2minusp1 = p2 - p1;
+    Point thisminusp1 = (*this - p1);
+    double t = thisminusp1.dot(p2minusp1) / p2minusp1.dot(p2minusp1);
+    if (t > 1.0 || t < 0.0) {
         return false;
-    else
-        return true;
+    }
+    return true;
 }
 
-
-
-
-
 /* **************** Operators ***************  
  *  see
  *  http://www.cs.caltech.edu/courses/cs11/material/cpp/donnie/cpp-ops.html
diff --git a/src/geo/stlreader.cpp b/src/geo/stlreader.cpp
index 48797f80..505cf3b1 100644
--- a/src/geo/stlreader.cpp
+++ b/src/geo/stlreader.cpp
@@ -121,11 +121,7 @@ namespace ocl
 #ifdef WIN32
                         sscanf_s(str, " vertex %f %f %f", &(x[vertex][0]), &(x[vertex][1]), &(x[vertex][2]));
 #else
-                        std::istringstream ss(str);
-                        ss.imbue(std::locale("C"));
-                        while(ss.peek() == ' ') ss.seekg(1, ios_base::cur);
-                        ss.seekg(std::string("vertex").size(), ios_base::cur);
-                        ss >> x[vertex][0] >> x[vertex][1] >> x[vertex][2];
+                        sscanf(str, " vertex %f %f %f", &(x[vertex][0]), &(x[vertex][1]), &(x[vertex][2]));
 #endif
                         vertex++;
                         if(vertex > 2)vertex = 2;
@@ -135,11 +131,7 @@ namespace ocl
 #ifdef WIN32
                         sscanf_s(str, " facet normal %f %f %f", &(n[0]), &(n[1]), &(n[2]));
 #else
-                        std::istringstream ss(str);
-                        ss.imbue(std::locale("C"));
-                        while(ss.peek() == ' ') ss.seekg(1, ios_base::cur);
-                        ss.seekg(std::string("facet normal").size(), ios_base::cur);
-                        ss >> n[0] >> n[1] >> n[2];
+                        sscanf(str, " facet normal %f %f %f", &(n[0]), &(n[1]), &(n[2]));
 #endif
                         vertex = 0;
                     }
diff --git a/src/pythonlib/delocate-wheel.py b/src/pythonlib/delocate-wheel.py
new file mode 100644
index 00000000..ef35709d
--- /dev/null
+++ b/src/pythonlib/delocate-wheel.py
@@ -0,0 +1,147 @@
+#!python
+""" Copy, relink library dependencies for wheel
+Overwrites the wheel in-place by default
+"""
+# vim: ft=python
+from __future__ import absolute_import, division, print_function
+
+import logging
+import os
+import sys
+from optparse import Option, OptionParser
+from os.path import basename, exists, expanduser
+from os.path import join as pjoin
+from typing import List, Optional, Text
+
+from delocate import __version__, delocate_wheel
+
+
+def filter_system_libs(libname):
+   return not (libname.startswith('/usr/lib') or
+               libname.startswith('/System') or
+               libname.find('libomp.dylib') >= 0)
+
+def main() -> None:
+    parser = OptionParser(
+        usage="%s WHEEL_FILENAME\n\n" % sys.argv[0] + __doc__,
+        version="%prog " + __version__,
+    )
+    parser.add_options(
+        [
+            Option(
+                "-L",
+                "--lib-sdir",
+                action="store",
+                type="string",
+                default=".dylibs",
+                help="Subdirectory in packages to store copied libraries",
+            ),
+            Option(
+                "-w",
+                "--wheel-dir",
+                action="store",
+                type="string",
+                help=(
+                    "Directory to store delocated wheels (default is to "
+                    "overwrite input)"
+                ),
+            ),
+            Option(
+                "-v",
+                "--verbose",
+                action="count",
+                help=(
+                    "Show a more verbose report of progress and failure."
+                    "  Additional flags show even more info, up to -vv."
+                ),
+                default=0,
+            ),
+            Option(
+                "-k",
+                "--check-archs",
+                action="store_true",
+                help="Check architectures of depended libraries",
+            ),
+            Option(
+                "-d",
+                "--dylibs-only",
+                action="store_true",
+                help="Only analyze files with known dynamic library extensions",
+            ),
+            Option(
+                "--require-archs",
+                action="store",
+                type="string",
+                help=(
+                    "Architectures that all wheel libraries should "
+                    "have (from 'intel', 'i386', 'x86_64', 'i386,x86_64'"
+                    "'universal2', 'x86_64,arm64')"
+                ),
+            ),
+            Option(
+                "--executable-path",
+                action="store",
+                type="string",
+                default=os.path.dirname(sys.executable),
+                help=(
+                    "The path used to resolve @executable_path in dependencies"
+                ),
+            ),
+            Option(
+                "--ignore-missing-dependencies",
+                action="store_true",
+                help=(
+                    "Skip dependencies which couldn't be found and delocate "
+                    "as much as possible"
+                ),
+            ),
+        ]
+    )
+    (opts, wheels) = parser.parse_args()
+    if len(wheels) < 1:
+        parser.print_help()
+        sys.exit(1)
+    logging.basicConfig(
+        level={0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG}.get(
+            opts.verbose, logging.DEBUG
+        )
+    )
+    multi = len(wheels) > 1
+    if opts.wheel_dir:
+        wheel_dir = expanduser(opts.wheel_dir)
+        if not exists(wheel_dir):
+            os.makedirs(wheel_dir)
+    else:
+        wheel_dir = None
+    require_archs: Optional[List[Text]] = None
+    if opts.require_archs is None:
+        require_archs = [] if opts.check_archs else None
+    elif "," in opts.require_archs:
+        require_archs = [s.strip() for s in opts.require_archs.split(",")]
+    else:
+        require_archs = opts.require_archs
+    # lib_filt_func = "dylibs-only" if opts.dylibs_only else None
+    for wheel in wheels:
+        if multi or opts.verbose:
+            print("Fixing: " + wheel)
+        if wheel_dir:
+            out_wheel = pjoin(wheel_dir, basename(wheel))
+        else:
+            out_wheel = wheel
+        copied = delocate_wheel(
+            wheel,
+            out_wheel,
+            lib_filt_func=filter_system_libs,
+            lib_sdir=opts.lib_sdir,
+            require_archs=require_archs,
+            executable_path=opts.executable_path,
+            ignore_missing=opts.ignore_missing_dependencies,
+        )
+        if opts.verbose and len(copied):
+            print("Copied to package {0} directory:".format(opts.lib_sdir))
+            copy_lines = ["  " + name for name in sorted(copied)]
+            print("\n".join(copy_lines))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/pythonlib/pythonlib.cmake b/src/pythonlib/pythonlib.cmake
index 28a95fbd..87b47e89 100644
--- a/src/pythonlib/pythonlib.cmake
+++ b/src/pythonlib/pythonlib.cmake
@@ -1,5 +1,10 @@
-find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
-find_package(Boost COMPONENTS python${Python_VERSION_MAJOR}${Python_VERSION_MINOR} REQUIRED)
+find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED)
+if(Python3_FOUND)
+  message(STATUS "Found Python: " ${Python3_VERSION})
+  message(STATUS "Python libraries: " ${Python3_LIBRARIES})
+  message(STATUS "Python executable: " ${Python3_EXECUTABLE})
+endif()
+find_package(Boost COMPONENTS python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR} REQUIRED)
 
 # include dirs
 include_directories(${PROJECT_SOURCE_DIR}/cutters)
@@ -10,7 +15,7 @@ include_directories(${PROJECT_SOURCE_DIR}/common)
 include_directories(${PROJECT_SOURCE_DIR})
 
 # this makes the ocl Python module
-Python_add_library(
+Python3_add_library(
   ocl
 MODULE
   pythonlib/ocl_cutters.cpp
@@ -28,8 +33,8 @@ PRIVATE
   ocl_cutters
   ocl_geo
   ocl_algo
-  Boost::python${Python_VERSION_MAJOR}${Python_VERSION_MINOR}
-  Python::Module
+  Boost::python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}
+  Python3::Module
 )
 
 if(USE_OPENMP)
@@ -45,14 +50,17 @@ if(NOT SKBUILD)
 endif()
 
 if(USE_OPENMP AND APPLE)
+  # add homebrew libomp paths to the INSTALL_RPATH, and the @loader_path last as a fallback.
+  set_target_properties(ocl PROPERTIES
+    INSTALL_RPATH "/opt/homebrew/opt/libomp/lib;/usr/local/opt/libomp/lib;@loader_path")
   # copy libomp into install directory
   install(
-    FILES ${OpenMP_omp_LIBRARY}
+    FILES ${OpenMP_CXX_LIBRARIES}
     DESTINATION "opencamlib"
     PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
   )
   # fix loader path
   add_custom_command(TARGET ocl POST_BUILD
-    COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change `otool -L $<TARGET_FILE:ocl> | grep libomp | cut -d ' ' -f1 | xargs echo` "@loader_path/libomp.dylib" $<TARGET_FILE:ocl>
+    COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change `otool -L $<TARGET_FILE:ocl> | grep libomp | cut -d ' ' -f1 | xargs echo` "@rpath/libomp.dylib" $<TARGET_FILE:ocl>
   )
 endif()


Test patch:
diff --git a/examples/cpp/test/test_example.cpp b/examples/cpp/test/test_example.cpp
index cb07ce2c..706c0da0 100644
--- a/examples/cpp/test/test_example.cpp
+++ b/examples/cpp/test/test_example.cpp
@@ -9,19 +9,68 @@
 #include <opencamlib/stlsurf.hpp>
 #include <opencamlib/stlreader.hpp>
 #include <opencamlib/cylcutter.hpp>
+#include <opencamlib/ballcutter.hpp>
+#include <opencamlib/bullcutter.hpp>
+#include <opencamlib/conecutter.hpp>
 #include <opencamlib/point.hpp>
 #include <opencamlib/line.hpp>
 #include <opencamlib/path.hpp>
 
+void printXYZ(ocl::Point point)
+{
+  // printf("X%g ", round(point.x * 100000.0) / 100000.0);
+  // printf("Y%g ", round(point.y * 100000.0) / 100000.0);
+  // printf("Z%g", round(point.z * 100000.0) / 100000.0);
+}
+
+void linear(ocl::Point point)
+{
+  // printf("G01 ");
+  // printXYZ(point);
+  // printf("\n");
+}
+
 void moveSafely(ocl::Point point)
 {
-  printf("G00 Z10\n");
-  printf("G00 ");
-  printf("X%g ", round(point.x * 100000.0) / 100000.0);
-  printf("Y%g\n", round(point.y * 100000.0) / 100000.0);
-  printf("G01 ");
-  printf("Z%g", round(point.z * 100000.0) / 100000.0);
-  printf(" F50\n");
+  // printf("G00 Z10\n");
+  // printf("G00 ");
+  // printf("X%g ", round(point.x * 100000.0) / 100000.0);
+  // printf("Y%g\n", round(point.y * 100000.0) / 100000.0);
+  // printf("G01 ");
+  // printf("Z%g", round(point.z * 100000.0) / 100000.0);
+  // printf(" F50\n");
+}
+
+void printPoints(std::vector<ocl::Point> points)
+{
+  for (auto j = 0; j < points.size(); j++)
+  {
+    auto point = points[j];
+    if (j == 0)
+      moveSafely(point);
+    else
+      linear(point);
+  }
+}
+
+void printPoints(std::vector<ocl::CLPoint> points)
+{
+  for (auto j = 0; j < points.size(); j++)
+  {
+    auto point = points[j];
+    if (j == 0)
+      moveSafely(point);
+    else
+      linear(point);
+  }
+}
+
+void printLoops(std::vector<std::vector<ocl::Point>> loops)
+{
+  for (auto i = 0; i < loops.size(); i++)
+  {
+    printPoints(loops[i]);
+  }
 }
 
 void waterline(ocl::STLSurf surface, ocl::MillingCutter *cutter, double z, double sampling)
@@ -29,25 +78,14 @@ void waterline(ocl::STLSurf surface, ocl::MillingCutter *cutter, double z, doubl
   ocl::Waterline wl = ocl::Waterline();
   wl.setSTL(surface);
   wl.setCutter(cutter);
-  wl.setZ(z);
   wl.setSampling(sampling);
-  wl.run();
-  auto loops = wl.getLoops();
-  // std::cout << "loops: " << loops.size() << "\n";
-  for (auto i = 0; i < loops.size(); i++)
+  for (double h = 0; h < z; h = h + 0.1)
   {
-    for (auto j = 0; j < loops[i].size(); j++)
-    {
-      auto point = loops[i][j];
-      if (j == 0) {
-        moveSafely(point);
-      } else {
-        printf("G01 ");
-        printf("X%g ", round(point.x * 100000.0) / 100000.0);
-        printf("Y%g ", round(point.y * 100000.0) / 100000.0);
-        printf("Z%g\n", round(point.z * 100000.0) / 100000.0);
-      }
-    }
+    wl.reset();
+    wl.setZ(h);
+    wl.run();
+    auto loops = wl.getLoops();
+    printLoops(loops);
   }
 }
 
@@ -56,26 +94,15 @@ void adaptiveWaterline(ocl::STLSurf surface, ocl::MillingCutter *cutter, double
   ocl::AdaptiveWaterline awl = ocl::AdaptiveWaterline();
   awl.setSTL(surface);
   awl.setCutter(cutter);
-  awl.setZ(z);
   awl.setSampling(sampling);
   awl.setMinSampling(minSampling);
-  awl.run();
-  auto loops = awl.getLoops();
-  // std::cout << "loops: " << loops.size() << "\n";
-  for (auto i = 0; i < loops.size(); i++)
+  for (double h = 0; h < z; h = h + 0.1)
   {
-    for (auto j = 0; j < loops[i].size(); j++)
-    {
-      auto point = loops[i][j];
-      if (j == 0) {
-        moveSafely(point);
-      } else {
-        printf("G01 ");
-        printf("X%g ", round(point.x * 100000.0) / 100000.0);
-        printf("Y%g ", round(point.y * 100000.0) / 100000.0);
-        printf("Z%g\n", round(point.z * 100000.0) / 100000.0);
-      }
-    }
+    awl.reset();
+    awl.setZ(h);
+    awl.run();
+    auto loops = awl.getLoops();
+    printLoops(loops);
   }
 }
 
@@ -85,23 +112,12 @@ void pathDropCutter(ocl::STLSurf surface, ocl::MillingCutter *cutter, double sam
   pdc.setSTL(surface);
   pdc.setCutter(cutter);
   pdc.setPath(path);
-  pdc.setZ(0);
   pdc.setSampling(sampling);
+  pdc.reset();
+  pdc.setZ(0);
   pdc.run();
   auto points = pdc.getPoints();
-  // std::cout << "points: " << points.size() << "\n";
-  for (auto i = 0; i < points.size(); i++)
-  {
-    auto point = points[i];
-    if (i == 0) {
-      moveSafely(point);
-    } else {
-      printf("G01 ");
-      printf("X%g ", round(point.x * 100000.0) / 100000.0);
-      printf("Y%g ", round(point.y * 100000.0) / 100000.0);
-      printf("Z%g\n", round(point.z * 100000.0) / 100000.0);
-    }
-  }
+  printPoints(points);
 }
 
 void adaptivePathDropCutter(ocl::STLSurf surface, ocl::MillingCutter *cutter, double sampling, double minSampling, ocl::Path *path)
@@ -110,54 +126,70 @@ void adaptivePathDropCutter(ocl::STLSurf surface, ocl::MillingCutter *cutter, do
   apdc.setSTL(surface);
   apdc.setCutter(cutter);
   apdc.setPath(path);
-  apdc.setZ(0);
   apdc.setSampling(sampling);
   apdc.setMinSampling(minSampling);
+  apdc.reset();
+  apdc.setZ(0);
   apdc.run();
   auto points = apdc.getPoints();
-  // std::cout << "points: " << points.size() << "\n";
-  for (auto i = 0; i < points.size(); i++)
-  {
-    auto point = points[i];
-    if (i == 0) {
-      moveSafely(point);
-    } else {
-      printf("G01 ");
-      printf("X%g ", round(point.x * 100000.0) / 100000.0);
-      printf("Y%g ", round(point.y * 100000.0) / 100000.0);
-      printf("Z%g\n", round(point.z * 100000.0) / 100000.0);
-    }
-  }
+  printPoints(points);
 }
 
 int main()
 {
-  // std::cout << "ocl version: " << ocl::version() << "\n";
-  // std::cout << "max threads: " << ocl::max_threads() << "\n";
+  std::cout << "ocl version: " << ocl::version() << "\n";
+  std::cout << "max threads: " << ocl::max_threads() << "\n";
   ocl::STLSurf surface = ocl::STLSurf();
   std::wstring stlPath = L"../../../../stl/gnu_tux_mod.stl";
   ocl::STLReader(stlPath, surface);
-  // std::cout << "surface size: " << surface.size() << "\n";
-  ocl::CylCutter cutter = ocl::CylCutter(4, 20);
-  double z = 1;
+  std::cout << "surface size: " << surface.size() << "\n";
+
+  ocl::CylCutter cylCutter = ocl::CylCutter(0.4, 10);
+  ocl::BallCutter ballCutter = ocl::BallCutter(4, 20);
+  ocl::BullCutter bullCutter = ocl::BullCutter(4, 0.05, 20);
+  ocl::ConeCutter coneCutter = ocl::ConeCutter(4, 0.05, 20);
+  std::vector<ocl::MillingCutter *> cutters;
+  cutters.push_back(&cylCutter);
+  cutters.push_back(&ballCutter);
+  cutters.push_back(&bullCutter);
+  cutters.push_back(&coneCutter);
+  double z = 0.5;
   double sampling = 0.1;
-  waterline(surface, &cutter, z, sampling);
-  // std::cout << "waterline done.\n";
-  double minSampling = 0.001;
-  ocl::CylCutter cutter2 = ocl::CylCutter(4, 20);
-  adaptiveWaterline(surface, &cutter2, z, sampling, minSampling);
-  // std::cout << "adaptivewaterline done.\n";
+  for (auto cutter : cutters)
+  {
+    std::cout << "WL + Cutter: " << cutter->str() << "\n";
+    waterline(surface, cutter, z, sampling);
+  }
+  double minSampling = 0.01;
+  for (auto cutter : cutters)
+  {
+    std::cout << "AWL + Cutter: " << cutter->str() << "\n";
+    adaptiveWaterline(surface, cutter, z, sampling, minSampling);
+  }
   ocl::Path path = ocl::Path();
-  ocl::Point p1 = ocl::Point(-2, 4, 0);
-  ocl::Point p2 = ocl::Point(11, 4, 0);
-  ocl::Line l = ocl::Line(p1, p2);
-  path.append(l);
-  ocl::CylCutter cutter3 = ocl::CylCutter(4, 20);
-  pathDropCutter(surface, &cutter3, sampling, &path);
-  // std::cout << "pathdropcutter done.\n";
-  ocl::CylCutter cutter4 = ocl::CylCutter(4, 20);
-  adaptivePathDropCutter(surface, &cutter4, sampling, minSampling, &path);
-  // std::cout << "adaptivepathdropcutter done.\n";
-  return 0;
-}
+  int i = 0;
+  for (double y = 0; y <= 0.2; y = y + 0.1)
+  {
+    bool ltr = ((int)i % 2) == 0;
+    ocl::Point p1 = ocl::Point(ltr ? -2 : 11, y, 0);
+    ocl::Point p2 = ocl::Point(ltr ? 11 : -2, y, 0);
+    ocl::Line l = ocl::Line(p1, p2);
+    path.append(l);
+    ocl::Point p3 = ocl::Point(ltr ? 11 : -2, y + 1, 0);
+    ocl::Line l2 = ocl::Line(p2, p3);
+    path.append(l2);
+    i++;
+  }
+  for (auto cutter : cutters)
+  {
+    std::cout << "PDC + Cutter: " << cutter->str() << "\n";
+    pathDropCutter(surface, cutter, sampling, &path);
+  }
+  for (auto cutter : cutters)
+  {
+    std::cout << "APDC: " << cutter->str() << "\n";
+    adaptivePathDropCutter(surface, cutter, sampling, minSampling, &path);
+  }
 
+  return 0;
+}
\ No newline at end of file