Skip to main content

Command Palette

Search for a command to run...

3차원 박스 적재 최적화를 위한 OptaPlanner 기반 구현기

Updated
3 min read

1. 도입

물류센터, 창고관리(WMS), 배송 박싱 자동화 등의 문제에서 공통적으로 요구되는 기능 중 하나는 여러 물건(Item)을 제한된 공간(Bin)에 효과적으로 적재(Packing) 하는 것입니다.

이를 해결하기 위해서는 여러가지 알고리즘 밑 방법을 사용할 수 있는데 오늘은 Bin-Packing 알고리즘에 대해서 공부하고 이를 실제로 구현해보도록 하겠습니다.

이 글에서는 이러한 문제를 해결하기 위해 Java 기반의 제약 프로그래밍 프레임워크인 OptaPlannerJavaFX 기반 3D 시각화를 활용하여 3D Bin Packing 문제를 해결한 과정을 정리합니다.

2. 목표

  • 다양한 크기와 모양(Shape)의 물건들을 제한된 박스 내부에 겹치지 않도록 배치

  • 회전 가능한 물체의 방향(Rotation) 고려

  • 실제 현실과 유사하게 물리 제약, 무게 제약, 부피 제약, 버퍼 영역 등을 반영

  • 사용 박스 수를 최소화하는 방향으로 최적화

  • 결과를 3D 화면으로 시각화하여 검증 가능하도록 구성

3. 시스템 구성

3.1 Item과 Bin 모델링

data class Item(val id: Int, val width: Long, val height: Long, val length: Long, val shape: Shape, ...)
data class Bin(val id: Int, val width: Long, val height: Long, val length: Long, val buffer: Double, ...)
  • Item은 물건 하나를 나타내며, 회전 가능성과 형태 정보도 포함합니다.

  • Bin은 적재 가능한 공간으로, 버퍼와 최대 무게 제한 등을 포함합니다.

3.2 PlanningEntity: ItemAssignment

@PlanningEntity
data class ItemAssignment(..., var bin: Bin?, var x: Long?, var y: Long?, var z: Long?, var rotation: Rotation?)
  • 각 ItemAssignment는 특정 Item이 어떤 Bin의 어느 위치(x,y,z)에 어떤 방향(rotation)으로 배치될지를 나타냅니다.

4. 제약 조건 구성

4.1 주요 제약 조건

  • itemMustFitInBin: 물건이 박스를 넘지 않도록 보장

  • noOverlap: 두 아이템이 같은 공간을 차지하지 않도록 제한

  • binCapacityExceeded: 버퍼를 고려한 부피 초과 방지

  • binWeightLimitExceeded: 최대 무게 제한

  • minimizeBinUsage: 사용한 박스 수를 줄이기 위한 소프트 제약

4.2 ConstraintPurpose를 통한 점수 구조

enum class ConstraintPurpose(val level: Int, val isHard: Boolean, val description: String)
  • 하드 제약: 충돌, 무게 초과 등 현실적 충족 필수 조건

  • 소프트 제약: 사용 박스 수 최소화, 빈 공간 최소화, 무게 배분 등

5. 솔버 구성

SolverFactory.create<BinPackingSolution>(
    SolverConfig()
        .withSolutionClass(...)
        .withEntityClasses(...)
        .withConstraintProviderClass(...)
        .withTerminationConfig(
            TerminationConfig().apply {
                unimprovedSecondsSpentLimit = 3L
            }
        )
  • SolverFactory를 통해 제약 조건 기반의 최적해를 탐색

  • 종료 조건은 3초 동안 점수 개선이 없을 경우 자동 종료

6. 시각화

6.1 JavaFX 기반 3D Viewer

주요 특징:

  • 각 Bin은 검정 테두리의 박스로 시각화

  • 각 Item은 반투명하고 색상 지정된 3D Box로 시각화

  • 각 아이템은 Golden Angle 기반 Hue 값을 통해 고유 색상을 유지

  • XYZ 축은 빨강, 초록, 파랑 선 및 라벨로 표현

  • 카메라는 대각선 위에서 XY 평면을 내려다보는 구조로 배치

  • 마우스 회전 및 확대 기능은 이후 단계에서 확장 가능

6.2 예시 화면

실행 시 결과 예시는 다음과 같습니다:

  • 콘솔 화면
=== 결과 ===
Item 1 -> Bin 1 | Rotation: XYZ | X: 0, Y: 0, Z: 0
Item 2 -> Bin 1 | Rotation: YXZ | X: 1, Y: 0, Z: 0
...

Score: 0hard/0soft

Bin 1 [XY 평면 @ Z=0]
| 1 | 2 |   |
| 3 |   |   |
|   |   |   |

Bin 1 [XY 평면 @ Z=1]
| 4 | 5 |   |
|   |   |   |
|   |   |   |
  • JavaFX 화면

콘솔에는 XY 평면 기준으로 z=0부터 아이템이 어떻게 적재되었는지를 확인할 수 있습니다.

7. 학습 및 구현에서의 주요 고려 사항

  • 단순히 부피만 고려하는 것이 아니라, 모양에 따른 buffer 영역까지 고려해야 현실적인 배치가 가능

  • 아이템 회전과 도형별 부피 공식, 특히 원통, 원뿔, 파우치형 등은 단순 박스형보다 복잡

  • 3D 시각화는 디버깅 및 결과 검증에 매우 효과적

8. 마무리

이번 프로젝트를 통해 물류 시스템에서 자주 접하게 되는 Packing 최적화 문제에 대해 제약 기반의 방식으로 접근할 수 있음을 경험할 수 있었습니다.

OptaPlanner는 수많은 상태를 가진 조합 문제를 빠르게 탐색할 수 있게 해주며, JavaFX 기반의 3D 출력은 단지 “작동한다” 이상의 검증 도구로 활용할 수 있었습니다.

다음 확장 방향으로는 다음을 고려하고 있습니다:

  • 사용자 마우스 회전, 확대 조작 추가

  • 다양한 박스 규격 자동 선택 로직

  • 병렬 Bin 추천 기능 및 우선순위 부여 로직

  • 웹기반 3D View 또는 Blender 렌더링 자동화

9. 참고 자료

More from this blog

JVM 객체 할당의 비밀 — TLAB, Bump-the-Pointer, 그리고 할당이 거의 공짜인 이유

Java에서 new를 호출하면 무슨 일이 벌어질까요? "힙에 메모리를 잡는다"는 한 문장 뒤에는 스레드마다 자기만의 분양 구역을 나눠 갖는 정교한 설계가 숨어 있어요. 이 글은 HotSpot JVM이 객체 할당을 어떻게 "거의 공짜"로 만드는지 그 내부를 따라가 보려는 글이에요. JVM 메모리 동작 원리에 관심 있는 분께 권해요. 자바를 쓰다 보면 객체를

May 15, 202614 min read

Java Zero-Copy — FileChannel.transferTo, sendfile, 그리고 Kafka가 디스크를 네트워크로 흘려보내는 방법

"파일을 읽어서 소켓으로 보낸다." 한 줄짜리 요구사항이에요. 그런데 이 한 줄 뒤에서 데이터는 메모리를 네 번이나 복사하고, CPU는 커널과 유저 공간을 네 번이나 들락거려요. Kafka처럼 초당 수십만 건을 흘려보내야 하는 시스템에서 이 비용은 그냥 넘길 수가 없어요. 이 글은 그 복사를 한 겹씩 벗겨내는 zero-copy의 동작 원리를 따라가요. 전통

May 15, 202617 min read

Git merge 내부 동작 — 3-way merge, merge base, 그리고 recursive에서 ort로

git merge를 매일 쓰지만, 그 한 줄이 안에서 무슨 일을 하는지 들여다본 적은 드물어요. 이 글은 merge가 두 갈래의 변경을 어떻게 합치는지, merge base가 왜 필요한지, 그리고 Git이 기본 전략을 recursive에서 ort로 갈아치운 이유를 따라가요. Git을 쓰는 백엔드 개발자를 대상으로 해요. 브랜치 두 개를 합치는 일은 겉보기

May 15, 202612 min read

Java NIO ByteBuffer 내부 구조 — Direct vs Heap, Cleaner, 그리고 off-heap 메모리가 GC를 우회하는 방법

Netty가 빠른 이유, Kafka 클라이언트가 직렬화에 신경 쓰는 이유, MappedByteBuffer로 수 GB짜리 파일을 다루는 이유. 그 한가운데에는 ByteBuffer가 있어요. 이번 글에서는 ByteBuffer의 두 얼굴 — heap과 direct — 가 어떻게 다른지, off-heap 메모리는 어떻게 잡고 어떻게 풀리는지, JVM과 운영체제 사

May 15, 202612 min read

Java Flight Recorder 내부 구조 — Thread-Local Buffer부터 Disk Repository까지

JFR을 켜면 1% 미만 오버헤드로 JVM 내부가 그대로 기록돼요. 어떻게 이렇게 가벼울 수 있는지, 그리고 그 데이터가 어떤 경로를 거쳐 디스크에 쌓이는지 한 번 따라가 봐요. 이 글은 JFR을 "그냥 잘 쓰는 도구"에서 "내부 동작을 아는 도구"로 끌어올리고 싶은 분을 위한 글이에요. 운영 중인 서버에서 갑자기 응답 시간이 튀어요. 메트릭 그래프는 분

May 15, 202614 min read

끄적끄적 테크 블로그

162 posts

물류 회사에 다니고 있는 개발자 블로그입니다. 개발을 너무 좋아해서 정신없이 작업하다가 중간에 끄적거리며 내용들을 몇개 적어봅니다 ㅎㅎ