Skip to content

Commit

Permalink
Pyclipr
Browse files Browse the repository at this point in the history
Fixes for addPaths which were not correctly offsetting for polygon regions.
Updated example for clipper offset + visualisation
  • Loading branch information
drlukeparry committed Dec 17, 2023
1 parent 58688b0 commit e9e698c
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 27 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/pythonpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ jobs:
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10', '3.11', '3.12']
os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, macos-13, windows-latest]
exclude:
- os: ubuntu-latest
python-version: '3.12'

steps:
- uses: actions/checkout@v2
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ All notable changes to this project will be documented in this file.

### Changed


## [0.1.5] - 2023-16-12

### Added

### Fixed

- In `ClipperOffset::addPaths`, paths of each polygon are seperately constructed into a list of paths to ensure that these are correctly imported into ClipperOffset.
- Scale factor has been applied when importing paths

### Changed



## [0.1.4] - 2023-11-12

Update release for PyClipr and updated dependencies to ClipperLib2 v.1.2. and Eigen 3.4.0 - dependent on
Expand Down
6 changes: 4 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ by using either `execute` or `execute2` methods, respectively.
po.scaleFactor = int(1000)
# add the path - ensuring to use Polygon for the endType argument
po.addPath(np.array(path), pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)
# addPaths is required when working with polygon - this is a list of correctly orientated paths for exterior
# and interior holes
po.addPaths([np.array(path)], pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)
# Apply the offsetting operation using a delta.
offsetSquare = po.execute(10.0)
Expand Down Expand Up @@ -149,4 +151,4 @@ by using either `execute` or `execute2` methods, respectively.
plt.fill(out[0][:, 0], out[0][:, 1], facecolor='#75507b')
# Plot the open path intersection
plt.plot(openPathsC[0][:,0], openPathsC[0][:,1],color='#222', linewidth=1.0, linestyle='dashed', marker='.',markersize=20.0)
plt.plot(openPathsC[0][:,0], openPathsC[0][:,1],color='#222', linewidth=1.0, linestyle='dashed', marker='.',markersize=20.0)
27 changes: 17 additions & 10 deletions examples/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import pyclipr

# Tuple definition of a path
path = [(0.0, 0.), (0, 105.1234), (100, 105.1234), (100, 0), (0, 0)]
path2 = [(1.0, 1.0), (1.0, 50), (100, 50), (100, 1.0), (1.0, 1.0)]
path = np.array([(0.0, 0.), (0, 105.1234), (100, 105.1234), (100, 0), (0, 0)])
path2 = np.array([(10.0, 10.0), (1.0, 50), (50.0, 50), (50., 10.0), (10.0, 10.0)])
path2 = np.flipud(path2)

path3 = np.array([(-10.0, -10.0), (70.0, -10), (70.0, 70), (-10., 70.0), (-10.0, -10.0)])

"""
Create an offsetting PyClipr tool
Expand All @@ -14,16 +17,20 @@

# Set the scale factor to convert to internal integer representation
# Note the default is set to 1000 units
po.scaleFactor = int(1000)
po.scaleFactor = int(1e4)

# add the path - ensuring to use Polygon for the endType argument
po.addPath(np.array(path), pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)

po.addPaths([path, path2], pyclipr.JoinType.Miter, pyclipr.EndType.Polygon)
"""
Apply the offsetting operation using a delta/offset
NoteL this automatically scaled interally scaled within pyclipr.ClipperOffset
"""
offsetSquare = po.execute(10.0)
offsetOut = po.execute(-1.0)

import pyslm.visualise
handle = pyslm.visualise.plotPolygon([path, path2])
pyslm.visualise.plotPolygon(offsetOut, handle=handle, lineColor='r')


"""
Create a Pyclipr clipping tool
Expand All @@ -35,10 +42,10 @@
Add the paths to the clipping object. Ensure the subject and clip arguments are set to differentiate
the paths during the Boolean operation. The final argument specifies if the path is open.
"""
pc.addPaths(offsetSquare, pyclipr.Subject)
pc.addPaths(offsetOut, pyclipr.Subject)

# Note it is possible to add a path as a numpy array (nx2) or as a list of tuples
pc.addPath(np.array(path2), pyclipr.Clip)
pc.addPath(np.array(path3), pyclipr.Clip)

"""
Perform Polygon Clipping
Expand Down Expand Up @@ -76,7 +83,7 @@
pc2.addPath(((40, -10), (50, 130)), pyclipr.Subject, True)

# The clipping object is usually set to the Polygon
pc2.addPaths(offsetSquare, pyclipr.Clip, False)
pc2.addPaths(offsetOut, pyclipr.Clip, False)

"""
Test the return types for open path clipping with option enabled. When The returnOpenPaths argument is set to True
Expand All @@ -100,7 +107,7 @@
plt.fill(pathPoly[:, 0], pathPoly[:, 1], 'b', alpha=0.1, linewidth=1.0, linestyle='dashed', edgecolor='#000')

# Plot the offset square
plt.fill(offsetSquare[0][:, 0], offsetSquare[0][:, 1],
plt.fill(offsetOut[0][:, 0], offsetOut[0][:, 1],
linewidth=1.0, linestyle='dashed', edgecolor='#333', facecolor='none')

# Plot the intersection
Expand Down
50 changes: 37 additions & 13 deletions python/pyclipr/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ typedef Eigen::Matrix<double,Eigen::Dynamic,2> EigenVec2d;
typedef Eigen::Matrix<double,Eigen::Dynamic,3> EigenVec3d;



static void myZCB(const Clipper2Lib::Point64& e1bot, const Clipper2Lib::Point64& e1top,
const Clipper2Lib::Point64& e2bot, const Clipper2Lib::Point64& e2top,
Clipper2Lib::Point64& pt) {
Expand Down Expand Up @@ -112,7 +111,7 @@ void applyScaleFactor(const Clipper2Lib::PolyPath64 & polyPath,
{
Clipper2Lib::Path64 path = polyPath[i]->Polygon();
Clipper2Lib::PolyPathD pathD;
newPath.SetScale(1.0/scaleFactor);
newPath.SetScale(1.0 / double(scaleFactor));

auto newChild = newPath.AddChild(path);

Expand Down Expand Up @@ -194,8 +193,8 @@ class Clipper : public Clipper2Lib::Clipper64 {
EigenVec1d eigPathZ(path.size(), 1);

for (uint64_t i=0; i < path.size(); i++) {
eigPath(i,0) = double(path[i].x) / scaleFactor;
eigPath(i,1) = double(path[i].y) / scaleFactor;
eigPath(i,0) = double(path[i].x) / double(scaleFactor);
eigPath(i,1) = double(path[i].y) / double(scaleFactor);

if(returnZ)
eigPathZ(i, 0) = path[i].z;
Expand All @@ -221,8 +220,8 @@ class Clipper : public Clipper2Lib::Clipper64 {
EigenVec2d eigPath(path.size(), 2);

for (uint64_t i=0; i<path.size(); i++) {
eigPath(i,0) = double(path[i].x) / scaleFactor;
eigPath(i,1) = double(path[i].y) / scaleFactor;
eigPath(i,0) = double(path[i].x) / double(scaleFactor);
eigPath(i,1) = double(path[i].y) / double(scaleFactor);

if(returnZ) {
//std::cout << "OP: assign z" << path[i].z;
Expand Down Expand Up @@ -266,8 +265,8 @@ class Clipper : public Clipper2Lib::Clipper64 {
EigenVec1d eigPathZ(path.size(), 1);

for (uint64_t i=0; i<path.size(); i++) {
eigPath(i,0) = double(path[i].x) / scaleFactor;
eigPath(i,1) = double(path[i].y) / scaleFactor;
eigPath(i,0) = double(path[i].x) / double(scaleFactor);
eigPath(i,1) = double(path[i].y) / double(scaleFactor);

if(returnZ)
eigPathZ(i) = path[i].z;
Expand Down Expand Up @@ -322,7 +321,6 @@ class ClipperOffset : public Clipper2Lib::ClipperOffset {

auto r = path.unchecked<2>();


if(path.shape(1) == 2) {
for(uint64_t i=0; i < path.shape(0); i++)
p.push_back(Clipper2Lib::Point64(r(i,0) * scaleFactor, r(i,1) * scaleFactor));
Expand All @@ -340,9 +338,35 @@ class ClipperOffset : public Clipper2Lib::ClipperOffset {
const Clipper2Lib::EndType endType = Clipper2Lib::EndType::Polygon)
{

for(auto path : paths)
addPath(path, joinType, endType);
std::vector<Clipper2Lib::Path64> closedPaths;

for(auto path : paths) {

Clipper2Lib::Path64 p;

if (path.ndim() != 2)
throw std::runtime_error("Number of dimensions must be two");

if (!(path.shape(1) == 2 || path.shape(1) == 3))
throw std::runtime_error("Path must be nx2, or nx3");

// Resize the path list
p.reserve(path.shape(0));

auto r = path.unchecked<2>();

if(path.shape(1) == 2) {
for(uint64_t i=0; i < path.shape(0); i++)
p.push_back(Clipper2Lib::Point64(r(i,0) * scaleFactor, r(i,1) * scaleFactor));
} else {
for(uint64_t i=0; i < path.shape(0); i++)
p.push_back(Clipper2Lib::Point64(r(i,0) * scaleFactor, r(i,1) * scaleFactor, r(i,2)));
}

closedPaths.push_back(p);

}
this->AddPaths(closedPaths, joinType, endType);
}

void clear() { this->Clear(); }
Expand All @@ -360,8 +384,8 @@ class ClipperOffset : public Clipper2Lib::ClipperOffset {
EigenVec2d eigPath(path.size(), 2);

for (uint64_t i=0; i<path.size(); i++) {
eigPath(i,0) = double(path[i].x) / scaleFactor;
eigPath(i,1) = double(path[i].y) / scaleFactor;
eigPath(i,0) = double(path[i].x) / double(scaleFactor);
eigPath(i,1) = double(path[i].y) / double(scaleFactor);
}

closedOut.push_back(eigPath);
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def build_extension(self, ext):

setup(
name='pyclipr',
version='0.1.4',
version='0.1.5',
author='Luke Parry',
author_email='[email protected]',
url='https://github.com/drlukeparry/pyclipr',
Expand Down

0 comments on commit e9e698c

Please sign in to comment.