fastrectpack 0.1.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastrectpack/__init__.py +28 -0
- fastrectpack/_maxrects.py +105 -0
- fastrectpack/_native.cp313-win_amd64.pyd +0 -0
- fastrectpack/_native.cpp +244 -0
- fastrectpack-0.1.0.dist-info/METADATA +164 -0
- fastrectpack-0.1.0.dist-info/RECORD +9 -0
- fastrectpack-0.1.0.dist-info/WHEEL +5 -0
- fastrectpack-0.1.0.dist-info/licenses/LICENSE +201 -0
- fastrectpack-0.1.0.dist-info/licenses/NOTICE +13 -0
fastrectpack/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""fastrectpack: a drop-in accelerator for rectpack (https://github.com/secnot/rectpack).
|
|
2
|
+
|
|
3
|
+
Use it exactly like rectpack:
|
|
4
|
+
|
|
5
|
+
from fastrectpack import newPacker
|
|
6
|
+
packer = newPacker()
|
|
7
|
+
...
|
|
8
|
+
|
|
9
|
+
Everything rectpack exports is re-exported unchanged, except the MaxRects algorithms, which run in C++ when
|
|
10
|
+
all sizes are plain Python ints. Any other input is handled by the original rectpack code.
|
|
11
|
+
"""
|
|
12
|
+
from rectpack import * # noqa: F401,F403 (re-export the full original API)
|
|
13
|
+
from rectpack import SORT_AREA, PackingBin, PackingMode
|
|
14
|
+
from rectpack import newPacker as _newPacker
|
|
15
|
+
|
|
16
|
+
from ._maxrects import NATIVE_AVAILABLE as NATIVE_AVAILABLE
|
|
17
|
+
from ._maxrects import MaxRectsBaf as MaxRectsBaf
|
|
18
|
+
from ._maxrects import MaxRectsBl as MaxRectsBl
|
|
19
|
+
from ._maxrects import MaxRectsBlsf as MaxRectsBlsf
|
|
20
|
+
from ._maxrects import MaxRectsBssf as MaxRectsBssf
|
|
21
|
+
|
|
22
|
+
__version__ = "0.1.0"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def newPacker(mode=PackingMode.Offline, bin_algo=PackingBin.BBF, pack_algo=MaxRectsBssf,
|
|
26
|
+
sort_algo=SORT_AREA, rotation=True):
|
|
27
|
+
"""Same signature and behaviour as rectpack.newPacker; the default algorithm is the accelerated one."""
|
|
28
|
+
return _newPacker(mode=mode, bin_algo=bin_algo, pack_algo=pack_algo, sort_algo=sort_algo, rotation=rotation)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Native-backed drop-in replacements for rectpack's MaxRects* classes.
|
|
2
|
+
|
|
3
|
+
Each class subclasses the original (so isinstance checks and every method we do not override still work),
|
|
4
|
+
runs the algorithm in C++ while all dimensions are plain Python ints, and falls back to the original Python
|
|
5
|
+
code, permanently for that object, the first time it sees anything else (floats, Decimals, numpy ints, bools,
|
|
6
|
+
zero or negative sizes, huge ints). The fallback is the original code, so results and errors are the same.
|
|
7
|
+
"""
|
|
8
|
+
from rectpack import maxrects as _orig
|
|
9
|
+
from rectpack.geometry import Rectangle
|
|
10
|
+
from rectpack.pack_algo import PackingAlgorithm
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from . import _native
|
|
14
|
+
except ImportError: # extension not built: everything below degrades to the original behaviour
|
|
15
|
+
_native = None
|
|
16
|
+
|
|
17
|
+
NATIVE_AVAILABLE = _native is not None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _NativeMaxRects:
|
|
21
|
+
_variant = 0 # overridden by each concrete class
|
|
22
|
+
|
|
23
|
+
def reset(self):
|
|
24
|
+
PackingAlgorithm.reset(self) # self.rectangles = []
|
|
25
|
+
self.__dict__.pop("_max_rects", None)
|
|
26
|
+
core = None
|
|
27
|
+
if _native is not None:
|
|
28
|
+
try:
|
|
29
|
+
core = _native.Core(self.width, self.height, bool(self.rot), self._variant)
|
|
30
|
+
except _native.Deopt:
|
|
31
|
+
core = None
|
|
32
|
+
self._native = core
|
|
33
|
+
if core is None:
|
|
34
|
+
self._max_rects = [Rectangle(0, 0, self.width, self.height)]
|
|
35
|
+
|
|
36
|
+
def _deopt(self):
|
|
37
|
+
"""Hand the current state over to the original Python implementation for good."""
|
|
38
|
+
core = self._native
|
|
39
|
+
self._max_rects = [Rectangle(*t) for t in core.free_rects()]
|
|
40
|
+
self._native = None
|
|
41
|
+
|
|
42
|
+
def __getattr__(self, name):
|
|
43
|
+
# Only reached when normal lookup fails: lets code that peeks at _max_rects keep working.
|
|
44
|
+
if name == "_max_rects":
|
|
45
|
+
core = self.__dict__.get("_native")
|
|
46
|
+
if core is not None:
|
|
47
|
+
return [Rectangle(*t) for t in core.free_rects()]
|
|
48
|
+
raise AttributeError(name)
|
|
49
|
+
|
|
50
|
+
def fitness(self, width, height):
|
|
51
|
+
core = self._native
|
|
52
|
+
if core is not None:
|
|
53
|
+
try:
|
|
54
|
+
return core.fitness(width, height)
|
|
55
|
+
except _native.Deopt:
|
|
56
|
+
self._deopt()
|
|
57
|
+
return super().fitness(width, height)
|
|
58
|
+
|
|
59
|
+
def add_rect(self, width, height, rid=None):
|
|
60
|
+
core = self._native
|
|
61
|
+
if core is not None:
|
|
62
|
+
try:
|
|
63
|
+
placed = core.add_rect(width, height)
|
|
64
|
+
except _native.Deopt:
|
|
65
|
+
self._deopt()
|
|
66
|
+
else:
|
|
67
|
+
if placed is None:
|
|
68
|
+
return None
|
|
69
|
+
rect = Rectangle(*placed)
|
|
70
|
+
rect.rid = rid
|
|
71
|
+
self.rectangles.append(rect)
|
|
72
|
+
return rect
|
|
73
|
+
return super().add_rect(width, height, rid)
|
|
74
|
+
|
|
75
|
+
# pickle / copy.deepcopy: the C++ object cannot be pickled, so serialise as plain Python state
|
|
76
|
+
def __getstate__(self):
|
|
77
|
+
state = dict(self.__dict__)
|
|
78
|
+
core = state.pop("_native", None)
|
|
79
|
+
if core is not None:
|
|
80
|
+
state["_max_rects"] = [Rectangle(*t) for t in core.free_rects()]
|
|
81
|
+
state["_native"] = None
|
|
82
|
+
return state
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_BL = _native.BL if _native else 0
|
|
86
|
+
_BSSF = _native.BSSF if _native else 1
|
|
87
|
+
_BAF = _native.BAF if _native else 2
|
|
88
|
+
_BLSF = _native.BLSF if _native else 3
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class MaxRectsBl(_NativeMaxRects, _orig.MaxRectsBl):
|
|
92
|
+
_variant = _BL
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class MaxRectsBssf(_NativeMaxRects, _orig.MaxRectsBssf):
|
|
96
|
+
"""Best Short Side Fit (the rectpack default)."""
|
|
97
|
+
_variant = _BSSF
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class MaxRectsBaf(_NativeMaxRects, _orig.MaxRectsBaf):
|
|
101
|
+
_variant = _BAF
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MaxRectsBlsf(_NativeMaxRects, _orig.MaxRectsBlsf):
|
|
105
|
+
_variant = _BLSF
|
|
Binary file
|
fastrectpack/_native.cpp
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// Native core for the MaxRects family of rectpack (https://github.com/secnot/rectpack, Apache-2.0).
|
|
2
|
+
//
|
|
3
|
+
// This is a port of rectpack.maxrects (MaxRectsBl / Bssf / Baf / Blsf) for plain Python int dimensions.
|
|
4
|
+
// It reproduces the original's behaviour exactly, including its quirks:
|
|
5
|
+
// * candidates are scanned in free-rectangle order, normal orientation first, then rotated, and the FIRST
|
|
6
|
+
// minimal candidate wins (Python's min() semantics);
|
|
7
|
+
// * MaxRectsBl compares only (y + height); its docstring promises an x tie-break but the code does not do it;
|
|
8
|
+
// * _remove_duplicates removes every free rectangle whose *value* equals that of a contained rectangle, so two
|
|
9
|
+
// identical free rectangles are both dropped.
|
|
10
|
+
//
|
|
11
|
+
// Anything outside the exact-int fast path raises Deopt and the Python wrapper falls back to the original code.
|
|
12
|
+
|
|
13
|
+
#include <pybind11/pybind11.h>
|
|
14
|
+
|
|
15
|
+
#include <algorithm>
|
|
16
|
+
#include <cstdint>
|
|
17
|
+
#include <memory>
|
|
18
|
+
#include <stdexcept>
|
|
19
|
+
#include <tuple>
|
|
20
|
+
#include <vector>
|
|
21
|
+
|
|
22
|
+
namespace py = pybind11;
|
|
23
|
+
using i64 = std::int64_t;
|
|
24
|
+
|
|
25
|
+
namespace {
|
|
26
|
+
|
|
27
|
+
// Keep every intermediate (sums, and products used by Baf) far from int64 overflow.
|
|
28
|
+
constexpr i64 kLimit = i64(1) << 30;
|
|
29
|
+
|
|
30
|
+
struct Deopt : std::runtime_error {
|
|
31
|
+
Deopt() : std::runtime_error("input outside the native fast path") {}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
struct Rect {
|
|
35
|
+
i64 x, y, w, h;
|
|
36
|
+
i64 top() const { return y + h; }
|
|
37
|
+
i64 right() const { return x + w; }
|
|
38
|
+
bool operator==(const Rect& o) const { return x == o.x && y == o.y && w == o.w && h == o.h; }
|
|
39
|
+
bool operator<(const Rect& o) const { return std::tie(x, y, w, h) < std::tie(o.x, o.y, o.w, o.h); }
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
enum Variant : int { kBl = 0, kBssf = 1, kBaf = 2, kBlsf = 3 };
|
|
43
|
+
|
|
44
|
+
// Exact Python int only (not bool, not numpy ints, not floats). Returns false when not representable.
|
|
45
|
+
bool as_i64(PyObject* o, i64& out) {
|
|
46
|
+
if (!PyLong_CheckExact(o)) return false;
|
|
47
|
+
int overflow = 0;
|
|
48
|
+
long long v = PyLong_AsLongLongAndOverflow(o, &overflow);
|
|
49
|
+
if (overflow != 0) return false;
|
|
50
|
+
if (v == -1 && PyErr_Occurred()) {
|
|
51
|
+
PyErr_Clear();
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
out = static_cast<i64>(v);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// rectpack Rectangle.contains(): `a` contains `b`
|
|
59
|
+
inline bool contains(const Rect& a, const Rect& b) {
|
|
60
|
+
return b.y >= a.y && b.x >= a.x && b.top() <= a.top() && b.right() <= a.right();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class Core {
|
|
64
|
+
public:
|
|
65
|
+
Core(i64 width, i64 height, bool rot, int variant) : rot_(rot), variant_(static_cast<Variant>(variant)) {
|
|
66
|
+
free_.push_back(Rect{0, 0, width, height});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Returns fitness (>= 0), or -1 when the rectangle cannot be placed.
|
|
70
|
+
i64 fitness(i64 w, i64 h) const {
|
|
71
|
+
Rect placed;
|
|
72
|
+
size_t idx;
|
|
73
|
+
if (!select(w, h, placed, idx)) return -1;
|
|
74
|
+
return rect_fitness(free_[idx], placed.w, placed.h);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
bool add_rect(i64 w, i64 h, Rect& placed) {
|
|
78
|
+
size_t idx;
|
|
79
|
+
if (!select(w, h, placed, idx)) return false;
|
|
80
|
+
split(placed);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const std::vector<Rect>& free_rects() const { return free_; }
|
|
85
|
+
|
|
86
|
+
private:
|
|
87
|
+
// Mirrors MaxRects*._rect_fitness for the variant. Caller guarantees (cw, ch) fits inside m.
|
|
88
|
+
i64 rect_fitness(const Rect& m, i64 cw, i64 ch) const {
|
|
89
|
+
switch (variant_) {
|
|
90
|
+
case kBssf:
|
|
91
|
+
return std::min(m.w - cw, m.h - ch);
|
|
92
|
+
case kBaf:
|
|
93
|
+
return m.w * m.h - cw * ch;
|
|
94
|
+
case kBlsf:
|
|
95
|
+
return std::max(m.w - cw, m.h - ch);
|
|
96
|
+
default:
|
|
97
|
+
return 0; // MaxRectsBl uses the base-class fitness
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Mirrors MaxRects*._select_position: first minimal candidate, normal orientation before rotated.
|
|
102
|
+
bool select(i64 w, i64 h, Rect& placed, size_t& idx) const {
|
|
103
|
+
bool have = false;
|
|
104
|
+
i64 best_key = 0;
|
|
105
|
+
for (size_t i = 0; i < free_.size(); ++i) {
|
|
106
|
+
const Rect& m = free_[i];
|
|
107
|
+
if (w <= m.w && h <= m.h) {
|
|
108
|
+
i64 key = (variant_ == kBl) ? m.y + h : rect_fitness(m, w, h);
|
|
109
|
+
if (!have || key < best_key) {
|
|
110
|
+
have = true;
|
|
111
|
+
best_key = key;
|
|
112
|
+
placed = Rect{m.x, m.y, w, h};
|
|
113
|
+
idx = i;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (rot_) {
|
|
118
|
+
for (size_t i = 0; i < free_.size(); ++i) {
|
|
119
|
+
const Rect& m = free_[i];
|
|
120
|
+
if (h <= m.w && w <= m.h) {
|
|
121
|
+
i64 key = (variant_ == kBl) ? m.y + w : rect_fitness(m, h, w);
|
|
122
|
+
if (!have || key < best_key) {
|
|
123
|
+
have = true;
|
|
124
|
+
best_key = key;
|
|
125
|
+
placed = Rect{m.x, m.y, h, w};
|
|
126
|
+
idx = i;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return have;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Mirrors MaxRects._split followed by MaxRects._remove_duplicates.
|
|
135
|
+
void split(const Rect& r) {
|
|
136
|
+
std::vector<Rect> next;
|
|
137
|
+
std::vector<char> is_new;
|
|
138
|
+
next.reserve(free_.size() + 8);
|
|
139
|
+
is_new.reserve(free_.size() + 8);
|
|
140
|
+
for (const Rect& m : free_) {
|
|
141
|
+
bool intersects = !(m.y >= r.top() || m.top() <= r.y || m.x >= r.right() || m.right() <= r.x);
|
|
142
|
+
if (!intersects) {
|
|
143
|
+
next.push_back(m);
|
|
144
|
+
is_new.push_back(0);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// _generate_splits, same order: left, right, top, bottom
|
|
148
|
+
if (r.x > m.x) {
|
|
149
|
+
next.push_back(Rect{m.x, m.y, r.x - m.x, m.h});
|
|
150
|
+
is_new.push_back(1);
|
|
151
|
+
}
|
|
152
|
+
if (r.right() < m.right()) {
|
|
153
|
+
next.push_back(Rect{r.right(), m.y, m.right() - r.right(), m.h});
|
|
154
|
+
is_new.push_back(1);
|
|
155
|
+
}
|
|
156
|
+
if (r.top() < m.top()) {
|
|
157
|
+
next.push_back(Rect{m.x, r.top(), m.w, m.top() - r.top()});
|
|
158
|
+
is_new.push_back(1);
|
|
159
|
+
}
|
|
160
|
+
if (r.y > m.y) {
|
|
161
|
+
next.push_back(Rect{m.x, m.y, m.w, r.y - m.y});
|
|
162
|
+
is_new.push_back(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// _remove_duplicates. The original drops every rectangle whose VALUE equals the value of some rectangle
|
|
167
|
+
// that is contained by another one. Because the list it leaves behind never contains a containing pair,
|
|
168
|
+
// only pairs that involve a newly created piece can add to that set, so checking those is equivalent
|
|
169
|
+
// to the original's all-pairs scan (verified against the original by the parity tests).
|
|
170
|
+
std::vector<Rect> flagged;
|
|
171
|
+
const size_t n = next.size();
|
|
172
|
+
for (size_t i = 0; i < n; ++i) {
|
|
173
|
+
if (!is_new[i]) continue;
|
|
174
|
+
for (size_t j = 0; j < n; ++j) {
|
|
175
|
+
if (j == i) continue;
|
|
176
|
+
if (contains(next[i], next[j])) flagged.push_back(next[j]);
|
|
177
|
+
if (contains(next[j], next[i])) flagged.push_back(next[i]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (flagged.empty()) {
|
|
181
|
+
free_.swap(next);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
std::sort(flagged.begin(), flagged.end());
|
|
185
|
+
flagged.erase(std::unique(flagged.begin(), flagged.end()), flagged.end());
|
|
186
|
+
free_.clear();
|
|
187
|
+
for (const Rect& m : next) {
|
|
188
|
+
if (!std::binary_search(flagged.begin(), flagged.end(), m)) free_.push_back(m);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
bool rot_;
|
|
193
|
+
Variant variant_;
|
|
194
|
+
std::vector<Rect> free_;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
inline void need_dim(PyObject* o, i64& out) {
|
|
198
|
+
if (!as_i64(o, out) || out < 1 || out > kLimit) throw Deopt();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
std::unique_ptr<Core> make_core(py::handle width, py::handle height, bool rot, int variant) {
|
|
202
|
+
i64 w, h;
|
|
203
|
+
need_dim(width.ptr(), w);
|
|
204
|
+
need_dim(height.ptr(), h);
|
|
205
|
+
return std::make_unique<Core>(w, h, rot, variant);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
} // namespace
|
|
209
|
+
|
|
210
|
+
PYBIND11_MODULE(_native, m) {
|
|
211
|
+
m.doc() = "Native core for the MaxRects family of rectpack (exact-int fast path)";
|
|
212
|
+
py::register_exception<Deopt>(m, "Deopt");
|
|
213
|
+
|
|
214
|
+
py::class_<Core>(m, "Core")
|
|
215
|
+
.def(py::init(&make_core), py::arg("width"), py::arg("height"), py::arg("rot"), py::arg("variant"))
|
|
216
|
+
.def("fitness",
|
|
217
|
+
[](const Core& c, py::handle w, py::handle h) -> py::object {
|
|
218
|
+
i64 iw, ih;
|
|
219
|
+
need_dim(w.ptr(), iw);
|
|
220
|
+
need_dim(h.ptr(), ih);
|
|
221
|
+
i64 f = c.fitness(iw, ih);
|
|
222
|
+
if (f < 0) return py::none();
|
|
223
|
+
return py::int_(f);
|
|
224
|
+
})
|
|
225
|
+
.def("add_rect",
|
|
226
|
+
[](Core& c, py::handle w, py::handle h) -> py::object {
|
|
227
|
+
i64 iw, ih;
|
|
228
|
+
need_dim(w.ptr(), iw);
|
|
229
|
+
need_dim(h.ptr(), ih);
|
|
230
|
+
Rect placed;
|
|
231
|
+
if (!c.add_rect(iw, ih, placed)) return py::none();
|
|
232
|
+
return py::make_tuple(placed.x, placed.y, placed.w, placed.h);
|
|
233
|
+
})
|
|
234
|
+
.def("free_rects", [](const Core& c) {
|
|
235
|
+
py::list out;
|
|
236
|
+
for (const Rect& r : c.free_rects()) out.append(py::make_tuple(r.x, r.y, r.w, r.h));
|
|
237
|
+
return out;
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
m.attr("BL") = static_cast<int>(kBl);
|
|
241
|
+
m.attr("BSSF") = static_cast<int>(kBssf);
|
|
242
|
+
m.attr("BAF") = static_cast<int>(kBaf);
|
|
243
|
+
m.attr("BLSF") = static_cast<int>(kBlsf);
|
|
244
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastrectpack
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in C++ accelerator for rectpack's MaxRects 2D packing algorithms, with identical output
|
|
5
|
+
Keywords: rectpack,bin packing,rectangle packing,2d packing,nesting,pybind11,performance
|
|
6
|
+
Author: Ramcharan
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
License-File: NOTICE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: C++
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Project-URL: Homepage, https://github.com/p-ramcharan-dev/fastrectpack
|
|
22
|
+
Project-URL: Source, https://github.com/p-ramcharan-dev/fastrectpack
|
|
23
|
+
Project-URL: Issues, https://github.com/p-ramcharan-dev/fastrectpack/issues
|
|
24
|
+
Project-URL: Benchmarks, https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md
|
|
25
|
+
Project-URL: Original library, https://github.com/secnot/rectpack
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Requires-Dist: rectpack==0.2.2
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest; extra == "test"
|
|
30
|
+
Requires-Dist: hypothesis; extra == "test"
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# fastrectpack
|
|
34
|
+
|
|
35
|
+
[](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/ci.yml)
|
|
36
|
+
[](https://github.com/p-ramcharan-dev/fastrectpack/actions/workflows/wheels.yml)
|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
A drop-in accelerator for [rectpack](https://github.com/secnot/rectpack), the pure-Python 2D rectangle packing library. The MaxRects packing algorithms are re-implemented in C++17 and produce **exactly the same packings** as the original, 5x to 246x faster depending on the workload.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# before
|
|
44
|
+
from rectpack import newPacker
|
|
45
|
+
# after: nothing else changes
|
|
46
|
+
from fastrectpack import newPacker
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Headline numbers
|
|
50
|
+
|
|
51
|
+
Default `newPacker()` settings. Times are the best recorded run; full method and all 14 cases are in [BENCHMARKS.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/BENCHMARKS.md).
|
|
52
|
+
|
|
53
|
+
| Workload | Rectangles | Original rectpack | Pure-Python fixes only | fastrectpack (C++) | Speedup |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| random sizes | 1,000 | 3.525 s | 0.473 s | 0.0144 s | 246x |
|
|
56
|
+
| random sizes | 5,000 | 21.123 s | 3.738 s | 0.1364 s | 155x |
|
|
57
|
+
| few large, many small | 1,000 | 1.360 s | 0.243 s | 0.0102 s | 133x |
|
|
58
|
+
| sprite sizes | 2,000 | 1.500 s | 0.322 s | 0.0177 s | 85x |
|
|
59
|
+
| sheet cutting, 100 bins | 2,000 | 0.415 s | 0.214 s | 0.0780 s | 5x |
|
|
60
|
+
|
|
61
|
+
Two things to read from this table:
|
|
62
|
+
|
|
63
|
+
- **Part of the win needs no C++.** A better algorithm in plain Python gives 1.7x to 7.5x on its own (the "Pure-Python fixes only" column). The C++ port supplies the rest.
|
|
64
|
+
- **The gain depends on the workload.** Jobs that spread rectangles over many bins gain about 5x, because the bin-selection loop is still Python. See "What is not faster" below.
|
|
65
|
+
|
|
66
|
+
The benchmark machine is a laptop that drops into slow phases (probably thermal throttling), so absolute seconds vary by up to about 2.3x. Best-time speedup ratios agreed to roughly 10% between full runs.
|
|
67
|
+
|
|
68
|
+
<p align="center">
|
|
69
|
+
<picture>
|
|
70
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-dark.png">
|
|
71
|
+
<img alt="Dumbbell chart of the speedup over the original rectpack for 14 workloads, log scale. Pure-Python fixes alone give 1.7x to 7.5x; the C++ version gives 5x to 246x, with output identical in every case. Jobs that spread rectangles over many bins gain about 5x. The same numbers are in the tables in this README and in BENCHMARKS.md." src="https://raw.githubusercontent.com/p-ramcharan-dev/fastrectpack/main/docs/benchmarks-light.png" width="760">
|
|
72
|
+
</picture>
|
|
73
|
+
</p>
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
pip install fastrectpack
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Prebuilt wheels cover CPython 3.9 to 3.13 on Linux x86_64 (manylinux), Windows x86_64 and macOS arm64 (Apple silicon). Other platforms (Linux ARM, Intel Macs, Alpine/musl) and Python 3.14 are untested and build from the source distribution, which needs CMake 3.15+ and a C++17 compiler (MSVC 2019+, GCC 9+ or Clang 10+). From a checkout: `pip install .`. `rectpack==0.2.2` is installed as a dependency.
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
Identical to rectpack:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from fastrectpack import newPacker
|
|
89
|
+
|
|
90
|
+
packer = newPacker()
|
|
91
|
+
for width, height in [(100, 30), (40, 60), (30, 30), (70, 70), (100, 50), (30, 30)]:
|
|
92
|
+
packer.add_rect(width, height)
|
|
93
|
+
packer.add_bin(300, 450)
|
|
94
|
+
packer.pack()
|
|
95
|
+
|
|
96
|
+
for bin_index, x, y, w, h, rid in packer.rect_list():
|
|
97
|
+
print(bin_index, x, y, w, h)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`fastrectpack` re-exports everything rectpack exports. `MaxRectsBl`, `MaxRectsBssf`, `MaxRectsBaf` and `MaxRectsBlsf` are replaced by accelerated subclasses of the originals; everything else is the original object.
|
|
101
|
+
|
|
102
|
+
## How it works
|
|
103
|
+
|
|
104
|
+
The four MaxRects classes subclass the originals and run the algorithm in a small C++17 core while every size is a plain Python `int`. Anything else falls back, for good, to the original Python code for that object.
|
|
105
|
+
|
|
106
|
+
Profiling the original showed about 90% of its time in one step that compares every pair of free rectangles after each placement. The port compares only the pairs that involve newly created rectangles, which is valid because the list left by the previous step contains no rectangle that contains another. [docs/DESIGN.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/docs/DESIGN.md) covers the design, exactly what "identical" means, and the known gaps.
|
|
107
|
+
|
|
108
|
+
## What is accelerated
|
|
109
|
+
|
|
110
|
+
| | Accelerated |
|
|
111
|
+
|---|---|
|
|
112
|
+
| MaxRects algorithms (Bl, Bssf, Baf, Blsf), with or without rotation | Yes |
|
|
113
|
+
| All bin heuristics (BNF, BFF, BBF, Global), online and offline modes, all sort orders | Yes, when used with a MaxRects algorithm |
|
|
114
|
+
| Sizes given as plain Python `int`, up to 2^30 | Yes |
|
|
115
|
+
| Sizes given as `float`, `Decimal`, numpy integers or `bool` | No: handled by the original code, same result, original speed |
|
|
116
|
+
| Skyline and Guillotine algorithms | No: unchanged original code |
|
|
117
|
+
|
|
118
|
+
When an accelerated object meets an input outside the fast path, it hands its state to the original Python implementation and continues there, so results and error messages stay the same.
|
|
119
|
+
|
|
120
|
+
## What is not faster
|
|
121
|
+
|
|
122
|
+
- **Many-bin workloads.** With 100 open bins, most of the time goes to the Python loop that asks every bin how well a rectangle fits. That loop was left in Python, so the gain there is about 5x.
|
|
123
|
+
- **Non-integer sizes.** Floats and Decimals run at the original speed.
|
|
124
|
+
- **Skyline and Guillotine.** Not ported.
|
|
125
|
+
|
|
126
|
+
## How correctness was checked
|
|
127
|
+
|
|
128
|
+
- **Exact parity.** After every single placement, the tests compare the fitness value, the placement and the complete list of free rectangles (including its order) against the original. 474 tests, including two Hypothesis property tests (300 and 150 generated cases) and all 200 packer configurations that use a MaxRects algorithm.
|
|
129
|
+
- **Edge cases.** Floats, Decimals, NaN, infinity, zero and negative sizes, booleans, strings, `None`, numpy integers, integers beyond the native range, exact fits, pieces larger than the bin. Results and exceptions match the original.
|
|
130
|
+
- **Benchmark outputs.** Every benchmark run hashes the full result of all three variants; all 14 cases are identical.
|
|
131
|
+
- **Sanitizers.** The suite passes under AddressSanitizer and UndefinedBehaviorSanitizer, both locally (GCC 15.2 in WSL) and on a GitHub Actions Ubuntu runner.
|
|
132
|
+
- **Mutation check.** Six deliberate bugs were injected into the C++. The tests caught five; the sixth (treating edge-touching rectangles as overlapping) only adds free rectangles that the next step deletes, so the output cannot change. I reasoned that through rather than proving it formally.
|
|
133
|
+
- **Platforms and Python versions.** In GitHub Actions the test suite ran inside every built wheel: Python 3.9, 3.10, 3.11, 3.12 and 3.13 on Linux x86_64, Windows x86_64 and macOS arm64. Locally it also passes on 3.13 and 3.11 on Windows.
|
|
134
|
+
|
|
135
|
+
Run them yourself:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
pip install ".[test]"
|
|
139
|
+
pytest
|
|
140
|
+
python bench/run_bench.py --quick # about a minute
|
|
141
|
+
python bench/run_bench.py # full benchmark, several minutes
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Behaviour inherited from rectpack
|
|
145
|
+
|
|
146
|
+
These were found while porting. They are reproduced on purpose, not fixed, so that results match ([`research/original_bugs.py`](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/research/original_bugs.py) demonstrates each):
|
|
147
|
+
|
|
148
|
+
- `validate_packing()` never compares the last rectangle with the others, so an overlap involving it goes undetected.
|
|
149
|
+
- `MaxRectsBl` documents a smallest-x tie-break but takes the first candidate.
|
|
150
|
+
- With the BBF bin heuristic, online `add_rect` returns `True`/`False` instead of the rectangle.
|
|
151
|
+
|
|
152
|
+
## Limitations
|
|
153
|
+
|
|
154
|
+
- Speed was measured on one Windows laptop only. Correctness is checked in CI on Linux, Windows and macOS (arm64), but speed on those systems has not been measured.
|
|
155
|
+
- The package subclasses rectpack internals and pins `rectpack==0.2.2`. A new rectpack release would need re-checking.
|
|
156
|
+
- An unpickled or deep-copied packer continues on the original Python code path: correct, but not accelerated.
|
|
157
|
+
|
|
158
|
+
## Contributing and security
|
|
159
|
+
|
|
160
|
+
Issues and pull requests are welcome; see [CONTRIBUTING.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/CONTRIBUTING.md). The most useful report is a real workload where it is still slow. Please report security problems privately, as described in [SECURITY.md](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/SECURITY.md).
|
|
161
|
+
|
|
162
|
+
## Credits and licence
|
|
163
|
+
|
|
164
|
+
Apache-2.0, the same licence as rectpack, of which this is a derivative work. rectpack is by SecNot. The MaxRects algorithms come from Jukka Jylänki, "A Thousand Ways to Pack the Bin" (2010). See [NOTICE](https://github.com/p-ramcharan-dev/fastrectpack/blob/main/NOTICE).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
fastrectpack/__init__.py,sha256=Vl4TSOaJuolSCu-bwAxbl1GlagsL6TuobFVOUgK3Q1U,1225
|
|
2
|
+
fastrectpack/_maxrects.py,sha256=-c5p8wPvp66GodwiMX24__b8Qd9q7TN9wNu6KJhtbkM,3645
|
|
3
|
+
fastrectpack/_native.cp313-win_amd64.pyd,sha256=Va3GgZhXkJFhXHxiMd5gzVqwrZqj5NtdnDs_xBwKWYI,215040
|
|
4
|
+
fastrectpack/_native.cpp,sha256=IEPtqn2ZWco1jxbU6xsqWk1ZNoHovs26Io4HIAL67n4,9118
|
|
5
|
+
fastrectpack-0.1.0.dist-info/METADATA,sha256=qZp0WlZLNKv2sgBu79f4wLSiOnl_oM_4JI4yJwAT_mY,10338
|
|
6
|
+
fastrectpack-0.1.0.dist-info/WHEEL,sha256=bzccfTXe5NE7eB86OiS4hIwvwnRsRQgKw2c3BU4To6A,105
|
|
7
|
+
fastrectpack-0.1.0.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
|
|
8
|
+
fastrectpack-0.1.0.dist-info/licenses/NOTICE,sha256=abWczgp7TPLPxsrkBfJMSZzdqINIno6YzNsVtJbjgMY,708
|
|
9
|
+
fastrectpack-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright {yyyy} {name of copyright owner}
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
fastrectpack
|
|
2
|
+
Copyright 2026 Ramcharan
|
|
3
|
+
|
|
4
|
+
This product is a derivative work of rectpack (https://github.com/secnot/rectpack),
|
|
5
|
+
licensed under the Apache License, Version 2.0, by its author SecNot.
|
|
6
|
+
|
|
7
|
+
Changes made: the MaxRects packing algorithms (MaxRectsBl, MaxRectsBssf, MaxRectsBaf,
|
|
8
|
+
MaxRectsBlsf) were re-implemented in C++17 for plain Python int sizes and are exposed
|
|
9
|
+
through classes that subclass the originals. All other behaviour and all other algorithms
|
|
10
|
+
are the unmodified rectpack code, which is installed as a dependency and re-exported.
|
|
11
|
+
|
|
12
|
+
The MaxRects family itself is described in: Jukka Jylanki, "A Thousand Ways to Pack the
|
|
13
|
+
Bin - A Practical Approach to Two-Dimensional Rectangle Bin Packing" (2010).
|