Skip to content

Knuesel refactor f0 rect #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/CompatHelper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: CompatHelper
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: Pkg.add("CompatHelper")
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
- name: CompatHelper.main()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
run: julia -e 'using CompatHelper; CompatHelper.main()'
14 changes: 14 additions & 0 deletions .github/workflows/RegisterAction.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: RegisterAction
on:
workflow_dispatch:
inputs:
version:
description: Version to register or component to bump
required: true
jobs:
register:
runs-on: ubuntu-latest
steps:
- uses: julia-actions/RegisterAction@latest
with:
token: ${{ secrets.GITHUB_TOKEN }}
8 changes: 6 additions & 2 deletions .github/workflows/TagBot.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
name: TagBot
on:
schedule:
- cron: 0 * * * *
issue_comment:
types:
- created
workflow_dispatch:
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.DOCUMENTER_KEY }}
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@

name: CI
on:
- push
- pull_request
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.4'
- '1.6'
os:
- ubuntu-latest
arch:
- x64
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v1
with:
file: lcov.info
10 changes: 0 additions & 10 deletions .travis.yml

This file was deleted.

2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version = "0.4.1"
GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326"

[compat]
GeometryBasics = "0.2, 0.3"
GeometryBasics = "0.4.1"
julia = "1"

[extras]
Expand Down
60 changes: 30 additions & 30 deletions src/guillotine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ struct GuillotinePacker{T}
merge::Bool
rect_choice::FreeRectChoiceHeuristic
split_method::GuillotineSplitHeuristic
free_rectangles::Vector{Rect2D{T}}
used_rectangles::Vector{Rect2D{T}}
free_rectangles::Vector{Rect2{T}}
used_rectangles::Vector{Rect2{T}}

end

Expand All @@ -34,12 +34,12 @@ function GuillotinePacker(width::T, height::T; merge::Bool=true,
split_method::GuillotineSplitHeuristic=SplitMinimizeArea) where T

packer = GuillotinePacker(width, height, merge, rect_choice, split_method,
Rect2D{T}[], Rect2D{T}[])
push!(packer.free_rectangles, Rect2D(T(0), T(0), width, height))
Rect2{T}[], Rect2{T}[])
push!(packer.free_rectangles, Rect2(T(0), T(0), width, height))
return packer
end

function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}}) where T
function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2{T}}) where T
rects = copy(rects)
free_rectangles = packer.free_rectangles
used_rectangles = packer.used_rectangles
Expand Down Expand Up @@ -104,9 +104,9 @@ function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}})
# Otherwise, we're good to go and do the actual packing.
xy = minimum(free_rectangles[best_free_rect])
if best_flipped
new_node = Rect2D(xy, reverse(widths(rects[best_rect])))
new_node = Rect2(xy, reverse(widths(rects[best_rect])))
else
new_node = Rect2D(xy, widths(rects[best_rect]))
new_node = Rect2(xy, widths(rects[best_rect]))
end

# Remove the free space we lost in the bin.
Expand All @@ -126,7 +126,7 @@ function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}})
end
end

function Base.push!(packer::GuillotinePacker, rect::Rect2D)
function Base.push!(packer::GuillotinePacker, rect::Rect2)
w, h = widths(rect)
free_rectangles = packer.free_rectangles
used_rectangles = packer.used_rectangles
Expand Down Expand Up @@ -164,7 +164,7 @@ function Occupancy(packer::GuillotinePacker)
end

#/ Returns the heuristic score value for placing a rectangle of size width*height into free_rect. Does not try to rotate.
function score_by_heuristic(width, height, free_rect::Rect2D, rect_choice::FreeRectChoiceHeuristic)
function score_by_heuristic(width, height, free_rect::Rect2, rect_choice::FreeRectChoiceHeuristic)
rect_choice == RectBestAreaFit && return ScoreBestAreaFit(width, height, free_rect)
rect_choice == RectBestShortSideFit && return ScoreBestShortSideFit(width, height, free_rect)
rect_choice == RectBestLongSideFit && return ScoreBestLongSideFit(width, height, free_rect)
Expand All @@ -173,69 +173,69 @@ function score_by_heuristic(width, height, free_rect::Rect2D, rect_choice::FreeR
rect_choice == RectWorstLongSideFit && return ScoreWorstLongSideFit(width, height, free_rect)
end

function ScoreBestAreaFit(w, h, free_rect::Rect2D)
function ScoreBestAreaFit(w, h, free_rect::Rect2)
return width(free_rect) * height(free_rect) - w * h
end

function ScoreBestShortSideFit(w, h, free_rect::Rect2D)
function ScoreBestShortSideFit(w, h, free_rect::Rect2)
leftoverHoriz = abs(width(free_rect) - w)
leftoverVert = abs(height(free_rect) - h)
leftover = min(leftoverHoriz, leftoverVert)
return leftover
end

function ScoreBestLongSideFit(w, h, free_rect::Rect2D)
function ScoreBestLongSideFit(w, h, free_rect::Rect2)
leftoverHoriz = abs(width(free_rect) - w)
leftoverVert = abs(height(free_rect) - h)
leftover = max(leftoverHoriz, leftoverVert)
return leftover

end

function ScoreWorstAreaFit(width, height, free_rect::Rect2D)
function ScoreWorstAreaFit(width, height, free_rect::Rect2)
return -ScoreBestAreaFit(width, height, free_rect)
end

function ScoreWorstShortSideFit(width, height, free_rect::Rect2D)
function ScoreWorstShortSideFit(width, height, free_rect::Rect2)
return -ScoreBestShortSideFit(width, height, free_rect)
end

function ScoreWorstLongSideFit(width, height, free_rect::Rect2D)
function ScoreWorstLongSideFit(width, height, free_rect::Rect2)
return -ScoreBestLongSideFit(width, height, free_rect)
end

function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::FreeRectChoiceHeuristic)
free_rectangles = packer.free_rectangles
best_node = Rect2D(0,0,0,0)
best_node = Rect2(0,0,0,0)
free_node_idx = 0
best_score = typemax(Int)
#/ Try each free rectangle to find the best one for placement.
for i in 1:length(free_rectangles)
rect_i = free_rectangles[i]
# If this is a perfect fit upright, choose it immediately.
if (w == width(rect_i) && h == height(rect_i))
best_node = Rect2D(minimum(rect_i), Vec(w, h))
best_node = Rect2(minimum(rect_i), Vec(w, h))
free_node_idx = i
break
# If this is a perfect fit sideways, choose it.
elseif (h == width(rect_i) && w == height(rect_i))
best_node = Rect2D(minimum(rect_i), Vec(h, w))
best_node = Rect2(minimum(rect_i), Vec(h, w))
best_score = typemin(Int)
free_node_idx = i
break
# Does the rectangle fit upright?
elseif (w <= width(rect_i) && h <= height(rect_i))
score = score_by_heuristic(w, h, rect_i, rect_choice)
if (score < best_score)
best_node = Rect2D(minimum(rect_i), Vec(w, h))
best_node = Rect2(minimum(rect_i), Vec(w, h))
best_score = score
free_node_idx = i
end
# Does the rectangle fit sideways?
elseif (h <= width(rect_i) && w <= height(rect_i))
score = score_by_heuristic(h, w, rect_i, rect_choice)
if (score < best_score)
best_node = Rect2D(minimum(rect_i), Vec(h, w))
best_node = Rect2(minimum(rect_i), Vec(h, w))
best_score = score
free_node_idx = i
end
Expand All @@ -244,7 +244,7 @@ function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::Fre
return best_node, free_node_idx
end

function split_freerect_by_heuristic(packer::GuillotinePacker, free_rect::Rect2D, placed_rect::Rect2D, method::GuillotineSplitHeuristic)
function split_freerect_by_heuristic(packer::GuillotinePacker, free_rect::Rect2, placed_rect::Rect2, method::GuillotineSplitHeuristic)
# Compute the lengths of the leftover area.
w, h = widths(free_rect) .- widths(placed_rect)

Expand Down Expand Up @@ -283,16 +283,16 @@ end

#/ This function will add the two generated rectangles into the free_rectangles array. The caller is expected to
#/ remove the original rectangle from the free_rectangles array after that.
function split_freerect_along_axis(packer::GuillotinePacker, free_rect::Rect2D, placed_rect::Rect2D, split_horizontal::Bool)
function split_freerect_along_axis(packer::GuillotinePacker, free_rect::Rect2, placed_rect::Rect2, split_horizontal::Bool)
free_rectangles = packer.free_rectangles
# Form the two new rectangles.
x, y = minimum(free_rect)
if split_horizontal
bottom = Rect2D(x, y + height(placed_rect), width(free_rect), height(free_rect) - height(placed_rect))
right = Rect2D(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(placed_rect))
bottom = Rect2(x, y + height(placed_rect), width(free_rect), height(free_rect) - height(placed_rect))
right = Rect2(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(placed_rect))
else # Split vertically
bottom = Rect2D(x, y + height(placed_rect), width(placed_rect), height(free_rect) - height(placed_rect))
right = Rect2D(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(free_rect))
bottom = Rect2(x, y + height(placed_rect), width(placed_rect), height(free_rect) - height(placed_rect))
right = Rect2(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(free_rect))
end

# Add the new rectangles into the free rectangle pool if they weren't degenerate.
Expand All @@ -318,25 +318,25 @@ function MergeFreeList(packer::GuillotinePacker)
if (minimum(rect_i)[2] == maximum(free_rectangles[j])[2])
new_y = minimum(rect_i)[2] - height(free_rectangles[j])
new_h = height(rect_i) + height(free_rectangles[j])
free_rectangles[i] = Rect2D(minimum(rect_i)[1], new_y, Vec(width(rect_i), new_h))
free_rectangles[i] = Rect2(minimum(rect_i)[1], new_y, Vec(width(rect_i), new_h))
splice!(free_rectangles, j)
j -= 1
elseif (maximum(rect_i)[2] == minimum(free_rectangles[j])[2])
new_h = height(rect_i) + height(free_rectangles[j])
free_rectangles[i] = Rect2D(minimum(rect_i), Vec(width(rect_i), new_h))
free_rectangles[i] = Rect2(minimum(rect_i), Vec(width(rect_i), new_h))
splice!(free_rectangles, j)
j -= 1
end
elseif (height(rect_i) == height(free_rectangles[j]) && minimum(rect_i)[2] == minimum(free_rectangles[j])[2])
if (minimum(rect_i)[1] == minimum(free_rectangles[j])[1] + width(free_rectangles[j]))
new_x = minimum(rect_i)[1] - width(free_rectangles[j])
new_w = width(rect_i) + width(free_rectangles[j])
free_rectangles[i] = Rect2D(new_x, minimum(rect_i)[2], new_w, height(rect_i))
free_rectangles[i] = Rect2(new_x, minimum(rect_i)[2], new_w, height(rect_i))
splice!(free_rectangles, j)
j -= 1
elseif maximum(rect_i)[1] == minimum(free_rectangles[j])[1]
new_w = Vec(width(rect_i) + width(free_rectangles[j]), height(rect_i))
free_rectangles[i] = Rect2D(minimum(rect_i), new_w)
free_rectangles[i] = Rect2(minimum(rect_i), new_w)
splice!(free_rectangles, j)
j -= 1
end
Expand Down
14 changes: 7 additions & 7 deletions src/rectangle.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ end

mutable struct RectanglePacker{T}
children::BinaryNode{RectanglePacker{T}}
area::Rect2D{T}
area::Rect2{T}
end

left(a::RectanglePacker) = a.children.left
left(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.left = r)
right(a::RectanglePacker) = a.children.right
right(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.right = r)
RectanglePacker(area::Rect2D{T}) where {T} = RectanglePacker{T}(BinaryNode{RectanglePacker{T}}(), area)
RectanglePacker(area::Rect2{T}) where {T} = RectanglePacker{T}(BinaryNode{RectanglePacker{T}}(), area)
isleaf(a::RectanglePacker) = (a.children.left) == nothing && (a.children.right == nothing)
# This is rather append, but it seems odd to use another function here.
# Maybe its a bad idea, to call it push regardless!?
function Base.push!(node::RectanglePacker{T}, areas::Vector{Rect2D{T}}) where T
function Base.push!(node::RectanglePacker{T}, areas::Vector{Rect2{T}}) where T
sort!(areas, by=GeometryBasics.norm ∘ widths)
return RectanglePacker{T}[push!(node, area) for area in areas]
end

function Base.push!(node::RectanglePacker{T}, area::Rect2D{T}) where T
function Base.push!(node::RectanglePacker{T}, area::Rect2{T}) where T
if !isleaf(node)
l = push!(left(node), area)
l !== nothing && return l
Expand All @@ -41,9 +41,9 @@ function Base.push!(node::RectanglePacker{T}, area::Rect2D{T}) where T
oax,oay,oaxw,oayh = xmin + neww, ymin, xmax, ymin + newh
nax,nay,naxw,nayh = xmin, ymin + newh, xmax, ymax
rax,ray,raxw,rayh = xmin, ymin, xmin + neww, ymin + newh
left(node, RectanglePacker(Rect2D(oax, oay, oaxw - oax, oayh - oay)))
right(node, RectanglePacker(Rect2D(nax, nay, naxw - nax, nayh - nay)))
return RectanglePacker(Rect2D(rax, ray, raxw - rax, rayh - ray))
left(node, RectanglePacker(Rect2(oax, oay, oaxw - oax, oayh - oay)))
right(node, RectanglePacker(Rect2(nax, nay, naxw - nax, nayh - nay)))
return RectanglePacker(Rect2(rax, ray, raxw - rax, rayh - ray))
end
return nothing
end
2 changes: 1 addition & 1 deletion test/guillotine.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ push!(packer, rects)
push!(packer, Rect(0, 0, 100, 100))

# using Makie
# linesegments(FRect(0, 0, 1024, 1024))
# linesegments(Rectf(0, 0, 1024, 1024))
# poly!(packer.used_rectangles, color = (:red, 0.1), strokewidth=1, strokecolor=:black)
14 changes: 7 additions & 7 deletions test/test_rectangle.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
root = RectanglePacker(Rect2D(0,0,1024,1024))
push!(root, Rect2D(0,0,20,20))
push!(root, Rect2D(0,0,20,20))
push!(root, [Rect2D(0,0, rand(5:50), rand(5:50)) for i=1:20])
root = RectanglePacker(Rect2(0,0,1024,1024))
push!(root, Rect2(0,0,20,20))
push!(root, Rect2(0,0,20,20))
push!(root, [Rect2(0,0, rand(5:50), rand(5:50)) for i=1:20])


# function get_rectangles(packer::Nothing, rectangles=IRect2D[])
# function get_rectangles(packer::Nothing, rectangles=Rect2i[])
# return rectangles
# end
#
Expand All @@ -15,14 +15,14 @@ push!(root, [Rect2D(0,0, rand(5:50), rand(5:50)) for i=1:20])
# return rectangles
# end
# function get_rectangles(packer)
# rectangles = IRect2D[]
# rectangles = Rect2i[]
# get_rectangles(packer.children.left, rectangles)
# get_rectangles(packer.children.right, rectangles)
# return rectangles
# end
#
# rectangles = get_rectangles(root)
# using Makie
# linesegments(FRect(0, 0, 1024, 1024))
# linesegments(Rectf(0, 0, 1024, 1024))
# poly!(rectangles, color = (:red, 0.1), strokewidth=1, strokecolor=:black)
#