Skip to content
Open
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
15 changes: 15 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.git
.gitignore
*.o
Makefile
moc
obj
rcc
ui
output
.qmake.stash
brickr
moc_*.cpp
moc_*.o
moc_predefs.h
ui_*.h
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*.o
Makefile
brickr
moc_*.cpp
moc_*.o
moc_predefs.h
ui_*.h
.qmake.stash
models/*_scaled.obj
models/*_scaled.binvox
models/*.binvox
output/*
!output/.gitkeep
63 changes: 63 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
FROM --platform=linux/amd64 ubuntu:22.04 AS build

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
libboost-graph-dev \
libglu1-mesa-dev \
libqt5opengl5-dev \
libqt5svg5-dev \
mesa-common-dev \
qt5-qmake \
qtbase5-dev \
qtbase5-dev-tools \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src

COPY . .

RUN qmake brickr.pro && make -j"$(nproc)"


FROM --platform=linux/amd64 ubuntu:22.04

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libboost-graph1.74.0 \
libgl1-mesa-glx \
libgl1-mesa-dri \
libglu1-mesa \
libqt5core5a \
libqt5gui5 \
libqt5opengl5 \
libqt5svg5 \
libqt5widgets5 \
novnc \
python3-pip \
websockify \
xauth \
x11vnc \
xvfb \
&& rm -rf /var/lib/apt/lists/*

RUN python3 -m pip install --no-cache-dir trimesh

WORKDIR /opt/brickr

COPY --from=build /src/brickr /opt/brickr/brickr
COPY --from=build /src/resources /opt/brickr/resources
COPY --from=build /src/models /opt/brickr/models
COPY tools/obj_to_binvox.py /opt/brickr/tools/obj_to_binvox.py
COPY docker/entrypoint-vnc.sh /opt/brickr/entrypoint-vnc.sh

ENV BINVOX_PATH=/opt/brickr/tools/obj_to_binvox.py
RUN chmod +x /opt/brickr/entrypoint-vnc.sh

EXPOSE 5900 6080

CMD ["/opt/brickr/entrypoint-vnc.sh"]
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# brickr in Docker

This workspace packages [Daekkyn/brickr](https://github.com/Daekkyn/brickr) in Docker so the Qt/OpenGL app can be built reproducibly on Linux.

## What changed

- Added a `Dockerfile` to build and run the app in Ubuntu 22.04.
- Added `docker-compose.yml` for a one-command local setup.
- Replaced the bundled `binvox` runtime path with a Python voxelizer that works on Apple Silicon.
- Added a browser-based noVNC desktop so the Qt GUI can be used on macOS without XQuartz.
- Fixed BINVOX import dimension handling so generated voxel files load correctly.

## Build

```sh
docker build --platform=linux/amd64 -t brickr:local .
```

## Run

This image starts Brickr in a virtual desktop and exposes it in your browser with noVNC:

```sh
docker run --rm -it --platform=linux/amd64 -p 6080:6080 -p 5900:5900 brickr:local
```

Then open:

```txt
http://localhost:6080/vnc.html
```

To work with your own meshes, mount the repo or a models folder:

```sh
docker run --rm -it \
--platform=linux/amd64 \
-p 6080:6080 \
-p 5900:5900 \
-v "$PWD/models:/opt/brickr/models" \
-v "$PWD/output:/opt/brickr/output" \
brickr:local
```

Or with Compose:

```sh
mkdir -p output
docker compose up --build
```

## CLI

You can also run Brickr headlessly from Docker:

```sh
docker run --rm \
--platform=linux/amd64 \
--entrypoint /opt/brickr/brickr \
-e BINVOX_PATH=/opt/brickr/tools/obj_to_binvox.py \
-e QT_QPA_PLATFORM=offscreen \
-v "$PWD/output:/opt/brickr/output" \
brickr:local \
--cli \
--input /opt/brickr/models/toyplane.obj \
--resolution 30 \
--print-stats \
--export-obj /opt/brickr/output/toyplane.obj \
--save-instructions /opt/brickr/output/toyplane.svg
```

CLI flags:

- `--input`: input `.obj` or `.binvox`
- `--resolution`: voxelization resolution for mesh inputs
- `--pre-hollow`: run pre-hollowing before optimization
- `--shell-thickness`: shell thickness used with `--pre-hollow`
- `--auto-optimize`: run Brickr's auto optimizer
- `--finalize`: post-hollow, solve limits, and merge
- `--print-stats`: print LEGO model stats
- `--export-obj`: export the generated brick model as `.obj`
- `--save-instructions`: export one instruction file per layer as `.svg`, `.png`, or `.jpg`

## Notes

- `brickr` is a GUI app, not a command-line converter.
- Mesh input support is currently `.obj`.
- The bundled `binvox` binary is Linux x86_64, so the container is pinned to `linux/amd64`.
- `BINVOX_PATH` is set automatically to the Python voxelizer inside the image.
- The easiest way to see the GUI on macOS is the built-in noVNC desktop at `http://localhost:6080/vnc.html`.
- In the browser container, exports are written into `/opt/brickr/output`, which maps to local `./output`.
2 changes: 2 additions & 0 deletions brickr.pro
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ QMAKE_CXXFLAGS += -std=c++11
# Input
HEADERS += src/AssemblyPlugin.h \
src/AssemblyWidget.h \
src/BrickrCli.h \
src/LegoBrick.h \
src/LegoCloud.h \
src/LegoCloudNode.h \
Expand All @@ -25,6 +26,7 @@ HEADERS += src/AssemblyPlugin.h \
FORMS += forms/AssemblyWidget.ui
SOURCES += src/AssemblyPlugin.cpp \
src/AssemblyWidget.cpp \
src/BrickrCli.cpp \
src/LegoCloud.cpp \
src/LegoCloudNode.cpp \
src/main.cpp \
Expand Down
15 changes: 15 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
brickr:
platform: linux/amd64
build:
context: .
image: brickr:local
environment:
BINVOX_PATH: /opt/brickr/tools/obj_to_binvox.py
BRICKR_OUTPUT_DIR: /opt/brickr/output
volumes:
- ./models:/opt/brickr/models
- ./output:/opt/brickr/output
ports:
- "5900:5900"
- "6080:6080"
41 changes: 41 additions & 0 deletions docker/entrypoint-vnc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail

export DISPLAY="${DISPLAY:-:99}"
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/tmp/runtime-root}"
export BINVOX_PATH="${BINVOX_PATH:-/opt/brickr/tools/obj_to_binvox.py}"
export BRICKR_OPEN_FILE="${BRICKR_OPEN_FILE:-/opt/brickr/models/toyplane.obj}"
export BRICKR_VOX_RES="${BRICKR_VOX_RES:-30}"
export BRICKR_OUTPUT_DIR="${BRICKR_OUTPUT_DIR:-/opt/brickr/output}"
export LIBGL_ALWAYS_SOFTWARE="${LIBGL_ALWAYS_SOFTWARE:-1}"
export GALLIUM_DRIVER="${GALLIUM_DRIVER:-softpipe}"
export MESA_LOADER_DRIVER_OVERRIDE="${MESA_LOADER_DRIVER_OVERRIDE:-llvmpipe}"

mkdir -p "$XDG_RUNTIME_DIR"
chmod 700 "$XDG_RUNTIME_DIR"

Xvfb "$DISPLAY" -screen 0 1440x960x24 -ac +extension GLX +render -noreset &
XVFB_PID=$!

x11vnc -display "$DISPLAY" -forever -shared -nopw -listen 0.0.0.0 -xkb >/tmp/x11vnc.log 2>&1 &
X11VNC_PID=$!

websockify --web=/usr/share/novnc/ 6080 localhost:5900 >/tmp/websockify.log 2>&1 &
WEBSOCKIFY_PID=$!

sleep 2

/opt/brickr/brickr >/tmp/brickr.log 2>&1 &
BRICKR_PID=$!

cleanup() {
kill "$BRICKR_PID" "$WEBSOCKIFY_PID" "$X11VNC_PID" "$XVFB_PID" 2>/dev/null || true
}
trap cleanup EXIT INT TERM

echo "Brickr desktop available at http://localhost:6080/vnc.html"
echo "VNC port available at localhost:5900"
echo "Startup mesh: $BRICKR_OPEN_FILE (resolution $BRICKR_VOX_RES)"
echo "Output directory: $BRICKR_OUTPUT_DIR"

wait "$BRICKR_PID"
1 change: 1 addition & 0 deletions output/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

7 changes: 5 additions & 2 deletions src/AssemblyPlugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,11 @@ bool AssemblyPlugin::parseBinvox(const std::string& filename, LegoCloudNode* leg
}

size = width * height * depth;
legoCloudNode->getLegoCloud()->setVoxelGridDimmension(height, width, depth);
// The binvox stream is decoded into (level, x, y) where x ranges over the
// header's first dimension ("depth") and y ranges over the third ("width").
// The original code only worked for square footprints because it swapped
// those horizontal dimensions when allocating the voxel grid.
legoCloudNode->getLegoCloud()->setVoxelGridDimmension(height, depth, width);

if(colorFile.exists()) {
if(colorFile.open(QIODevice::ReadOnly)) {
Expand Down Expand Up @@ -407,4 +411,3 @@ void AssemblyPlugin::draw()
legoCloudNode_->render();
}
}

Loading