Add 2-Opt TSP solver
This update introduces a new C++ 2-Opt TSP solver with Python bindings. src/Mod/CAM/App/CMakeLists.txt: - Add build and install rules for the new pybind11-based tsp_solver Python module src/Mod/CAM/App/tsp_solver.cpp: - Add new C++ implementation of a 2-Opt TSP solver with nearest-neighbor initialization src/Mod/CAM/App/tsp_solver.h: - Add TSPPoint struct and TSPSolver class with 2-Opt solve method src/Mod/CAM/App/tsp_solver_pybind.cpp: - Add pybind11 wrapper exposing the TSP solver to Python as tsp_solver.solve src/Mod/CAM/PathScripts/PathUtils.py: - Add sort_locations_tsp Python wrapper for the C++ TSP solver - Use tsp_solver.solve for TSP-based
This commit is contained in:
@@ -138,3 +138,15 @@ SET_BIN_DIR(Path PathApp /Mod/CAM)
|
||||
SET_PYTHON_PREFIX_SUFFIX(Path)
|
||||
|
||||
INSTALL(TARGETS Path DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
# --- TSP Solver Python module (pybind11) ---
|
||||
find_package(pybind11 REQUIRED)
|
||||
|
||||
pybind11_add_module(tsp_solver MODULE tsp_solver_pybind.cpp tsp_solver.cpp)
|
||||
target_include_directories(tsp_solver PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
SET_BIN_DIR(tsp_solver tsp_solver /Mod/CAM)
|
||||
SET_PYTHON_PREFIX_SUFFIX(tsp_solver)
|
||||
|
||||
INSTALL(TARGETS tsp_solver DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
# -------------------------------------------
|
||||
|
||||
105
src/Mod/CAM/App/tsp_solver.cpp
Normal file
105
src/Mod/CAM/App/tsp_solver.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Billy Huddleston <billy@ivdc.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
* as published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* for detail see the LICENCE text file. *
|
||||
* *
|
||||
* This program 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this program; if not, write to the Free Software *
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
* USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "tsp_solver.h"
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <Base/Precision.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Euclidean distance between two points
|
||||
double dist(const TSPPoint& a, const TSPPoint& b)
|
||||
{
|
||||
double dx = a.x - b.x;
|
||||
double dy = a.y - b.y;
|
||||
return std::sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
// Calculate total path length
|
||||
double pathLength(const std::vector<TSPPoint>& points, const std::vector<int>& path)
|
||||
{
|
||||
double total = 0.0;
|
||||
size_t n = path.size();
|
||||
for (size_t i = 0; i < n - 1; ++i) {
|
||||
total += dist(points[path[i]], points[path[i + 1]]);
|
||||
}
|
||||
// Optionally, close the loop: total += dist(points[path[n-1]], points[path[0]]);
|
||||
return total;
|
||||
}
|
||||
|
||||
// 2-Opt swap
|
||||
void twoOptSwap(std::vector<int>& path, size_t i, size_t k)
|
||||
{
|
||||
std::reverse(path.begin() + static_cast<long>(i), path.begin() + static_cast<long>(k) + 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<int> TSPSolver::solve(const std::vector<TSPPoint>& points)
|
||||
{
|
||||
size_t n = points.size();
|
||||
if (n == 0) {
|
||||
return {};
|
||||
}
|
||||
// Start with a simple nearest neighbor path
|
||||
std::vector<bool> visited(n, false);
|
||||
std::vector<int> path;
|
||||
size_t current = 0;
|
||||
path.push_back(static_cast<int>(current));
|
||||
visited[current] = true;
|
||||
for (size_t step = 1; step < n; ++step) {
|
||||
double min_dist = std::numeric_limits<double>::max();
|
||||
size_t next = n; // Use n as an invalid index
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (!visited[i]) {
|
||||
double d = dist(points[current], points[i]);
|
||||
if (d < min_dist) {
|
||||
min_dist = d;
|
||||
next = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
current = next;
|
||||
path.push_back(static_cast<int>(current));
|
||||
visited[current] = true;
|
||||
}
|
||||
|
||||
// 2-Opt optimization
|
||||
bool improved = true;
|
||||
while (improved) {
|
||||
improved = false;
|
||||
for (size_t i = 1; i < n - 1; ++i) {
|
||||
for (size_t k = i + 1; k < n; ++k) {
|
||||
double delta = dist(points[path[i - 1]], points[path[k]])
|
||||
+ dist(points[path[i]], points[path[(k + 1) % n]])
|
||||
- dist(points[path[i - 1]], points[path[i]])
|
||||
- dist(points[path[k]], points[path[(k + 1) % n]]);
|
||||
if (delta < -Base::Precision::Confusion()) {
|
||||
twoOptSwap(path, i, k);
|
||||
improved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
42
src/Mod/CAM/App/tsp_solver.h
Normal file
42
src/Mod/CAM/App/tsp_solver.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Billy Huddleston <billy@ivdc.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
* as published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* for detail see the LICENCE text file. *
|
||||
* *
|
||||
* This program 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this program; if not, write to the Free Software *
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
* USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <limits>
|
||||
#include <cmath>
|
||||
|
||||
struct TSPPoint
|
||||
{
|
||||
double x, y;
|
||||
TSPPoint(double x_, double y_)
|
||||
: x(x_)
|
||||
, y(y_)
|
||||
{}
|
||||
};
|
||||
|
||||
class TSPSolver
|
||||
{
|
||||
public:
|
||||
// Returns a vector of indices representing the visit order using 2-Opt
|
||||
static std::vector<int> solve(const std::vector<TSPPoint>& points);
|
||||
};
|
||||
44
src/Mod/CAM/App/tsp_solver_pybind.cpp
Normal file
44
src/Mod/CAM/App/tsp_solver_pybind.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2025 Billy Huddleston <billy@ivdc.com> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
* as published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* for detail see the LICENCE text file. *
|
||||
* *
|
||||
* This program 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 Library General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Library General Public *
|
||||
* License along with this program; if not, write to the Free Software *
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
* USA *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include "tsp_solver.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
std::vector<int> tspSolvePy(const std::vector<std::pair<double, double>>& points)
|
||||
{
|
||||
std::vector<TSPPoint> pts;
|
||||
for (const auto& p : points) {
|
||||
pts.emplace_back(p.first, p.second);
|
||||
}
|
||||
return TSPSolver::solve(pts);
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(tsp_solver, m)
|
||||
{
|
||||
m.doc() = "Simple TSP solver (2-Opt) for FreeCAD";
|
||||
m.def("solve",
|
||||
&tspSolvePy,
|
||||
py::arg("points"),
|
||||
"Solve TSP for a list of (x, y) points using 2-Opt, returns visit order");
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 Dan Falck <ddfalck@gmail.com> *
|
||||
# * Copyright (c) 2025 Billy Huddleston <billy@ivdc.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
@@ -29,6 +30,7 @@ import Path
|
||||
import Path.Main.Job as PathJob
|
||||
import math
|
||||
from numpy import linspace
|
||||
import tsp_solver
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
@@ -611,6 +613,17 @@ def sort_locations(locations, keys, attractors=None):
|
||||
return out
|
||||
|
||||
|
||||
def sort_locations_tsp(locations, keys, attractors=None):
|
||||
"""
|
||||
Python wrapper for the C++ TSP solver. Takes a list of dicts (locations),
|
||||
a list of keys (e.g. ['x', 'y']), and optional attractors.
|
||||
Returns the sorted list of locations in TSP order, starting at (0,0).
|
||||
"""
|
||||
points = [(loc[keys[0]], loc[keys[1]]) for loc in locations]
|
||||
order = tsp_solver.solve(points)
|
||||
return [locations[i] for i in order]
|
||||
|
||||
|
||||
def guessDepths(objshape, subs=None):
|
||||
"""
|
||||
takes an object shape and optional list of subobjects and returns a depth_params
|
||||
|
||||
Reference in New Issue
Block a user