Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fa9194813 | ||
|
|
3fd461c804 | ||
|
|
080addbdab | ||
|
|
b82162bfef | ||
|
|
57101247e9 | ||
|
|
075f9bb542 | ||
|
|
7658e1df81 | ||
|
|
64801faff3 | ||
|
|
7c57d94ac0 | ||
|
|
7443c4bd9b | ||
|
|
72bb69748b | ||
|
|
403fd750c3 | ||
|
|
e8895c16f3 | ||
|
|
79e1060bde | ||
|
|
d84a0a3a55 | ||
|
|
830801e3d2 | ||
|
|
94b8546652 | ||
|
|
e749c37030 | ||
|
|
b1d3e997df | ||
|
|
cfd0e82985 | ||
|
|
2b91bda35b | ||
|
|
e45710023d | ||
|
|
8877504071 | ||
|
|
32723741bc | ||
|
|
57a5442c11 | ||
|
|
d34fdb8efd | ||
|
|
317305f867 | ||
|
|
257da30e47 | ||
|
|
ec54dcedd0 | ||
|
|
468993d5de | ||
|
|
be4b4ab6e1 | ||
|
|
0e1896e3f1 | ||
|
|
2d83918fc5 | ||
|
|
75fe485de7 | ||
|
|
c724a3f5f5 | ||
|
|
d129af20ca | ||
|
|
6187063190 | ||
|
|
b9f93ffd99 | ||
|
|
586118a875 | ||
|
|
ca1f360b83 | ||
|
|
4c73ac2fea | ||
|
|
36b764f735 | ||
|
|
ce1d91024b | ||
|
|
2f3bfd03d5 | ||
|
|
f03c34024d | ||
|
|
6b3f554732 | ||
|
|
325ced958e | ||
|
|
bbbc044e92 | ||
|
|
0c666da430 | ||
|
|
c924b8ed25 | ||
|
|
c8dde0f8ca | ||
|
|
401fb935bc | ||
|
|
7747a6e7a1 | ||
|
|
545cdc74ca | ||
|
|
b0e19d5577 | ||
|
|
df216c5105 | ||
|
|
e5d037c044 | ||
|
|
6e01d38a5c | ||
|
|
37790f2a3d | ||
|
|
6437fb57e8 | ||
|
|
a46c8970bf | ||
|
|
03bc0af038 | ||
|
|
c03fdb4ca0 | ||
|
|
1162b101b8 | ||
|
|
f91971baf9 | ||
|
|
937b356725 | ||
|
|
721ee0ab00 | ||
|
|
f054e3ad9b | ||
|
|
1a57ec13a5 | ||
|
|
c7b65ef498 | ||
|
|
31cb77116c | ||
|
|
74f75bb90d | ||
|
|
a3c067cd3b | ||
|
|
90cd00382a | ||
|
|
deef8b107d | ||
|
|
478f1fa117 | ||
|
|
3f82902ebe | ||
|
|
1c42f67000 | ||
|
|
b45c7a8112 | ||
|
|
da8be684dc | ||
|
|
dc27345b05 | ||
|
|
10cfab0501 | ||
|
|
4146066f76 | ||
|
|
8aa43d1ef7 | ||
|
|
04bf8b6bb9 | ||
|
|
84b5906ffa | ||
|
|
318b7742cc | ||
|
|
b68fd2dc21 | ||
|
|
f2cc134a8f | ||
|
|
31d9c9d087 | ||
|
|
c9ffc5a699 | ||
|
|
d37be2f4f5 | ||
|
|
4cc4da8868 | ||
|
|
9f5214d14d | ||
|
|
7e63fce706 | ||
|
|
380f4b4a18 | ||
|
|
240cf9ab5d | ||
|
|
05cb8b4d89 | ||
|
|
897a3e915f | ||
|
|
586e96479a | ||
|
|
0879b2079b | ||
|
|
c4801250ea | ||
|
|
f0386d981f | ||
|
|
cd5c8f2ad0 | ||
|
|
2d0a0568e7 |
5
.github/workflows/build_orca.yml
vendored
5
.github/workflows/build_orca.yml
vendored
@@ -26,8 +26,8 @@ jobs:
|
||||
date:
|
||||
ver:
|
||||
ver_pure:
|
||||
ubuntu-ver: '2404'
|
||||
ubuntu-ver-str: '_Ubuntu2404'
|
||||
ubuntu-ver: '2204'
|
||||
ubuntu-ver-str: '_Ubuntu2204'
|
||||
ORCA_UPDATER_SIG_KEY: ${{ secrets.ORCA_UPDATER_SIG_KEY }}
|
||||
|
||||
steps:
|
||||
@@ -382,6 +382,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
./build_linux.sh -istrlL
|
||||
./scripts/check_appimage_libs.sh ./build/package ./build/package/bin/orca-slicer
|
||||
mv -n ./build/OrcaSlicer_Linux_V${{ env.ver_pure }}.AppImage ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
|
||||
chmod +x ./build/OrcaSlicer_Linux_AppImage${{ env.ubuntu-ver-str }}_${{ env.ver }}.AppImage
|
||||
tar -cvpf build_tests.tar build/tests
|
||||
|
||||
@@ -929,7 +929,7 @@ elseif (SLIC3R_FHS)
|
||||
install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/ DESTINATION ${SLIC3R_FHS_RESOURCES}
|
||||
PATTERN "*/udev" EXCLUDE
|
||||
)
|
||||
install(FILES src/dev-utils/platform/unix/OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
|
||||
install(FILES src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
|
||||
foreach(SIZE 32 128 192)
|
||||
install(FILES ${SLIC3R_RESOURCES_DIR}/images/OrcaSlicer_${SIZE}px.png
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME OrcaSlicer.png
|
||||
@@ -938,7 +938,7 @@ elseif (SLIC3R_FHS)
|
||||
elseif (CMAKE_MACOSX_BUNDLE)
|
||||
# install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/OrcaSlicer.app/Contents/resources")
|
||||
else ()
|
||||
install(FILES src/dev-utils/platform/unix/OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
|
||||
install(FILES src/dev-utils/platform/unix/com.orcaslicer.OrcaSlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
|
||||
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}/resources")
|
||||
endif ()
|
||||
|
||||
|
||||
308
build_linux.sh
308
build_linux.sh
@@ -8,7 +8,7 @@ SCRIPT_PATH=$(dirname "$(readlink -f "${0}")")
|
||||
pushd "${SCRIPT_PATH}" > /dev/null
|
||||
|
||||
function usage() {
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
|
||||
echo "Usage: ./${SCRIPT_NAME} [-1][-b][-c][-d][-D][-e][-F][-g][-h][-i][-j N][-p][-r][-s][-t][-u][-l][-L]"
|
||||
echo " -1: limit builds to one core (where possible)"
|
||||
echo " -j N: limit builds to N cores (where possible)"
|
||||
echo " -b: build in Debug mode"
|
||||
@@ -17,6 +17,8 @@ function usage() {
|
||||
echo " -d: download and build dependencies in ./deps/ (build prerequisite)"
|
||||
echo " -D: dry run"
|
||||
echo " -e: build in RelWithDebInfo mode"
|
||||
echo " -F: rebuild the cached Docker/Podman runner image from scratch when used with -g"
|
||||
echo " -g: run the requested build steps inside a Docker/Podman Ubuntu 24.04 container similar to the GitHub Actions Linux runner"
|
||||
echo " -h: prints this help text"
|
||||
echo " -i: build the Orca Slicer AppImage (optional)"
|
||||
echo " -p: boost ccache hit rate by disabling precompiled headers (default: ON)"
|
||||
@@ -28,6 +30,9 @@ function usage() {
|
||||
echo " -L: use ld.lld as linker (if available)"
|
||||
echo "For a first use, you want to './${SCRIPT_NAME} -u'"
|
||||
echo " and then './${SCRIPT_NAME} -dsi'"
|
||||
echo "For a GitHub Actions-like Linux build locally, use './${SCRIPT_NAME} -g -istrlL'"
|
||||
echo "Use './${SCRIPT_NAME} -gF -istrlL' to rebuild the cached runner image first."
|
||||
echo "Set ORCA_CONTAINER_CLI, ORCA_DOCKER_IMAGE, ORCA_DOCKER_BASE_IMAGE, or ORCA_DOCKER_CMAKE_VERSION to override the container runtime, cached image tag, base image, or CMake version."
|
||||
}
|
||||
|
||||
SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||
@@ -35,60 +40,83 @@ SLIC3R_PRECOMPILED_HEADERS="ON"
|
||||
unset name
|
||||
BUILD_DIR=build
|
||||
BUILD_CONFIG=Release
|
||||
while getopts ":1j:bcCdDehiprstulL" opt ; do
|
||||
FORWARDED_ARGS=()
|
||||
while getopts ":1j:bcCdDeFghiprstulL" opt ; do
|
||||
case ${opt} in
|
||||
1 )
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||
FORWARDED_ARGS+=("-1")
|
||||
;;
|
||||
j )
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=$OPTARG
|
||||
FORWARDED_ARGS+=("-j" "$OPTARG")
|
||||
;;
|
||||
b )
|
||||
BUILD_DIR=build-dbg
|
||||
BUILD_CONFIG=Debug
|
||||
FORWARDED_ARGS+=("-b")
|
||||
;;
|
||||
c )
|
||||
CLEAN_BUILD=1
|
||||
FORWARDED_ARGS+=("-c")
|
||||
;;
|
||||
C )
|
||||
COLORED_OUTPUT="-DCOLORED_OUTPUT=ON"
|
||||
FORWARDED_ARGS+=("-C")
|
||||
;;
|
||||
d )
|
||||
BUILD_DEPS="1"
|
||||
FORWARDED_ARGS+=("-d")
|
||||
;;
|
||||
D )
|
||||
DRY_RUN="1"
|
||||
FORWARDED_ARGS+=("-D")
|
||||
;;
|
||||
e )
|
||||
BUILD_DIR=build-dbginfo
|
||||
BUILD_CONFIG=RelWithDebInfo
|
||||
FORWARDED_ARGS+=("-e")
|
||||
;;
|
||||
F )
|
||||
CLEAN_DOCKER_IMAGE="1"
|
||||
;;
|
||||
g )
|
||||
USE_DOCKER="1"
|
||||
;;
|
||||
h ) usage
|
||||
exit 1
|
||||
;;
|
||||
i )
|
||||
BUILD_IMAGE="1"
|
||||
FORWARDED_ARGS+=("-i")
|
||||
;;
|
||||
p )
|
||||
SLIC3R_PRECOMPILED_HEADERS="OFF"
|
||||
FORWARDED_ARGS+=("-p")
|
||||
;;
|
||||
r )
|
||||
SKIP_RAM_CHECK="1"
|
||||
FORWARDED_ARGS+=("-r")
|
||||
;;
|
||||
s )
|
||||
BUILD_ORCA="1"
|
||||
FORWARDED_ARGS+=("-s")
|
||||
;;
|
||||
t )
|
||||
BUILD_TESTS="1"
|
||||
FORWARDED_ARGS+=("-t")
|
||||
;;
|
||||
u )
|
||||
export UPDATE_LIB="1"
|
||||
FORWARDED_ARGS+=("-u")
|
||||
;;
|
||||
l )
|
||||
USE_CLANG="1"
|
||||
FORWARDED_ARGS+=("-l")
|
||||
;;
|
||||
L )
|
||||
USE_LLD="1"
|
||||
FORWARDED_ARGS+=("-L")
|
||||
;;
|
||||
* )
|
||||
echo "Unknown argument '${opt}', aborting."
|
||||
@@ -102,6 +130,11 @@ if [ ${OPTIND} -eq 1 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] && [[ -z "${USE_DOCKER}" ]] ; then
|
||||
echo "Error: -F requires -g."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
function check_available_memory_and_disk() {
|
||||
FREE_MEM_GB=$(free --gibi --total | grep 'Mem' | rev | cut --delimiter=" " --fields=1 | rev)
|
||||
MIN_MEM_GB=10
|
||||
@@ -139,6 +172,277 @@ function print_and_run() {
|
||||
fi
|
||||
}
|
||||
|
||||
function resolve_container_cli() {
|
||||
if [[ -n "${ORCA_CONTAINER_CLI}" ]] ; then
|
||||
if ! command -v "${ORCA_CONTAINER_CLI}" >/dev/null 2>&1 ; then
|
||||
echo "Error: container runtime '${ORCA_CONTAINER_CLI}' was not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${ORCA_CONTAINER_CLI}"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v docker >/dev/null 2>&1 ; then
|
||||
echo "docker"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v podman >/dev/null 2>&1 ; then
|
||||
echo "podman"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Error: neither docker nor podman is available. Install one of them or set ORCA_CONTAINER_CLI." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
function get_docker_runner_image() {
|
||||
local base_image
|
||||
local docker_cmake_version
|
||||
local recipe_hash
|
||||
local sanitized_base_image
|
||||
local sanitized_cmake_version
|
||||
|
||||
if [[ -n "${ORCA_DOCKER_IMAGE}" ]] ; then
|
||||
echo "${ORCA_DOCKER_IMAGE}"
|
||||
return
|
||||
fi
|
||||
|
||||
base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}"
|
||||
docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}"
|
||||
recipe_hash=$(find "${SCRIPT_PATH}/build_linux.sh" "${SCRIPT_PATH}/scripts/linux.d" -type f -print0 | sort -z | xargs -0 cat | sha256sum | cut -c1-12)
|
||||
sanitized_base_image=$(echo "${base_image}" | tr '/:@' '---' | tr -cs 'A-Za-z0-9_.-' '-')
|
||||
sanitized_cmake_version=$(echo "${docker_cmake_version:-system}" | tr -cs 'A-Za-z0-9_.-' '-')
|
||||
echo "orcaslicer-linux-builder:${sanitized_base_image}-cmake-${sanitized_cmake_version}-${recipe_hash}"
|
||||
}
|
||||
|
||||
function docker_runner_dockerfile() {
|
||||
cat <<'EOF'
|
||||
ARG BASE_IMAGE=ubuntu:24.04
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
ARG CMAKE_VERSION=4.3.0
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
RUN apt-get update && apt-get install -y sudo ca-certificates curl tar
|
||||
|
||||
COPY build_linux.sh /tmp/orcaslicer/build_linux.sh
|
||||
COPY scripts/linux.d /tmp/orcaslicer/scripts/linux.d
|
||||
|
||||
WORKDIR /tmp/orcaslicer
|
||||
|
||||
RUN chmod +x ./build_linux.sh
|
||||
RUN ./build_linux.sh -ur
|
||||
RUN if [[ -n "${CMAKE_VERSION}" ]] ; then \
|
||||
case "$(uname -m)" in \
|
||||
x86_64|amd64) cmake_arch="x86_64" ;; \
|
||||
aarch64|arm64) cmake_arch="aarch64" ;; \
|
||||
*) cmake_arch="" ;; \
|
||||
esac ; \
|
||||
if [[ -n "${cmake_arch}" ]] ; then \
|
||||
cmake_root="/opt/cmake-${CMAKE_VERSION}-linux-${cmake_arch}" ; \
|
||||
if ! curl -fsSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-linux-${cmake_arch}.tar.gz" | tar -xz -C /opt ; then \
|
||||
echo "Warning: failed to install CMake ${CMAKE_VERSION}; falling back to the distro cmake package." ; \
|
||||
elif [[ -d "${cmake_root}" ]] ; then \
|
||||
ln -sf "${cmake_root}/bin/"* /usr/local/bin/ ; \
|
||||
fi ; \
|
||||
else \
|
||||
echo "Skipping GitHub Actions CMake install for unsupported architecture $(uname -m)." ; \
|
||||
fi ; \
|
||||
fi
|
||||
RUN rm -rf /var/lib/apt/lists/* /tmp/orcaslicer
|
||||
EOF
|
||||
}
|
||||
|
||||
function ensure_docker_runner_image() {
|
||||
local container_cli
|
||||
local base_image
|
||||
local runner_image
|
||||
local docker_cmake_version
|
||||
local image_exists="0"
|
||||
local force_rebuild="0"
|
||||
local -a build_cmd
|
||||
|
||||
container_cli="$1"
|
||||
runner_image="$2"
|
||||
base_image="${ORCA_DOCKER_BASE_IMAGE:-ubuntu:24.04}"
|
||||
docker_cmake_version="${ORCA_DOCKER_CMAKE_VERSION-4.3.0}"
|
||||
|
||||
if "${container_cli}" image inspect "${runner_image}" >/dev/null 2>&1 ; then
|
||||
image_exists="1"
|
||||
fi
|
||||
|
||||
if [[ -n "${CLEAN_DOCKER_IMAGE}" ]] ; then
|
||||
force_rebuild="1"
|
||||
if [[ "${image_exists}" == "1" ]] ; then
|
||||
echo "Removing cached container image ${runner_image} ..."
|
||||
if [[ -z "${DRY_RUN}" ]] ; then
|
||||
"${container_cli}" image rm -f "${runner_image}" >/dev/null
|
||||
else
|
||||
printf '%q ' "${container_cli}" image rm -f "${runner_image}"
|
||||
echo
|
||||
fi
|
||||
image_exists="0"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${image_exists}" == "1" ]] ; then
|
||||
echo "Using cached container image ${runner_image}"
|
||||
return
|
||||
fi
|
||||
|
||||
build_cmd=(
|
||||
"${container_cli}" build --pull
|
||||
-t "${runner_image}"
|
||||
--build-arg "BASE_IMAGE=${base_image}"
|
||||
--build-arg "CMAKE_VERSION=${docker_cmake_version}"
|
||||
)
|
||||
if [[ "${force_rebuild}" == "1" ]] ; then
|
||||
build_cmd+=(--no-cache)
|
||||
fi
|
||||
build_cmd+=(-f - "${SCRIPT_PATH}")
|
||||
|
||||
printf '%q ' "${build_cmd[@]}"
|
||||
echo
|
||||
if [[ -n "${DRY_RUN}" ]] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
docker_runner_dockerfile | "${build_cmd[@]}"
|
||||
}
|
||||
|
||||
function run_in_docker() {
|
||||
local container_cli
|
||||
local runner_image
|
||||
local container_workspace
|
||||
local host_uid
|
||||
local host_gid
|
||||
local host_user
|
||||
local -a build_args
|
||||
local -a container_env
|
||||
|
||||
container_cli=$(resolve_container_cli)
|
||||
runner_image=$(get_docker_runner_image)
|
||||
host_uid=$(id -u)
|
||||
host_gid=$(id -g)
|
||||
host_user="${USER:-orca}"
|
||||
container_workspace="/__w/OrcaSlicer/OrcaSlicer"
|
||||
build_args=()
|
||||
for item in "${FORWARDED_ARGS[@]}" ; do
|
||||
if [[ "${item}" == "-u" ]] || [[ "${item}" == "-D" ]] ; then
|
||||
continue
|
||||
fi
|
||||
|
||||
build_args+=("${item}")
|
||||
done
|
||||
|
||||
container_env=(
|
||||
-e "CI=true"
|
||||
-e "GITHUB_ACTIONS=true"
|
||||
-e "GITHUB_WORKSPACE=${container_workspace}"
|
||||
-e "GIT_CEILING_DIRECTORIES=${container_workspace}/deps/build"
|
||||
-e "RUNNER_OS=Linux"
|
||||
-e "RUNNER_TEMP=/tmp"
|
||||
-e "HOST_UID=${host_uid}"
|
||||
-e "HOST_GID=${host_gid}"
|
||||
-e "HOST_USER=${host_user}"
|
||||
)
|
||||
if [[ -n "${CMAKE_BUILD_PARALLEL_LEVEL}" ]] ; then
|
||||
container_env+=( -e "CMAKE_BUILD_PARALLEL_LEVEL=${CMAKE_BUILD_PARALLEL_LEVEL}" )
|
||||
fi
|
||||
if [[ -n "${ORCA_UPDATER_SIG_KEY}" ]] ; then
|
||||
container_env+=( -e "ORCA_UPDATER_SIG_KEY=${ORCA_UPDATER_SIG_KEY}" )
|
||||
fi
|
||||
|
||||
ensure_docker_runner_image "${container_cli}" "${runner_image}"
|
||||
|
||||
printf '%q ' "${container_cli}" run --rm -i \
|
||||
-v "${SCRIPT_PATH}:${container_workspace}" \
|
||||
-w "${container_workspace}" \
|
||||
"${container_env[@]}" \
|
||||
"${runner_image}" \
|
||||
bash -s -- "${build_args[@]}"
|
||||
echo
|
||||
if [[ -n "${DRY_RUN}" ]] ; then
|
||||
return
|
||||
fi
|
||||
|
||||
"${container_cli}" run --rm -i \
|
||||
-v "${SCRIPT_PATH}:${container_workspace}" \
|
||||
-w "${container_workspace}" \
|
||||
"${container_env[@]}" \
|
||||
"${runner_image}" \
|
||||
bash -s -- "${build_args[@]}" <<'EOF'
|
||||
set -e
|
||||
|
||||
function create_builder_user() {
|
||||
if [[ "${HOST_UID}" == "0" ]] ; then
|
||||
HOST_USER=root
|
||||
return
|
||||
fi
|
||||
|
||||
if getent group "${HOST_GID}" >/dev/null 2>&1 ; then
|
||||
HOST_GROUP=$(getent group "${HOST_GID}" | cut -d: -f1)
|
||||
else
|
||||
HOST_GROUP="orca-builder"
|
||||
if getent group "${HOST_GROUP}" >/dev/null 2>&1 ; then
|
||||
HOST_GROUP="orca-builder-${HOST_GID}"
|
||||
fi
|
||||
groupadd -g "${HOST_GID}" "${HOST_GROUP}"
|
||||
fi
|
||||
|
||||
if getent passwd "${HOST_UID}" >/dev/null 2>&1 ; then
|
||||
HOST_USER=$(getent passwd "${HOST_UID}" | cut -d: -f1)
|
||||
usermod -g "${HOST_GROUP}" "${HOST_USER}"
|
||||
elif id -u "${HOST_USER}" >/dev/null 2>&1 ; then
|
||||
usermod -u "${HOST_UID}" -g "${HOST_GROUP}" "${HOST_USER}"
|
||||
else
|
||||
useradd -m -u "${HOST_UID}" -g "${HOST_GROUP}" -s /bin/bash "${HOST_USER}"
|
||||
fi
|
||||
|
||||
echo "${HOST_USER} ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/orcaslicer-builder
|
||||
chmod 0440 /etc/sudoers.d/orcaslicer-builder
|
||||
}
|
||||
|
||||
create_builder_user
|
||||
mkdir -p "${GITHUB_WORKSPACE}/deps/build/destdir"
|
||||
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/deps/build"
|
||||
if [[ -d "${GITHUB_WORKSPACE}/build" ]] ; then
|
||||
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build"
|
||||
fi
|
||||
if [[ -d "${GITHUB_WORKSPACE}/build-dbg" ]] ; then
|
||||
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbg"
|
||||
fi
|
||||
if [[ -d "${GITHUB_WORKSPACE}/build-dbginfo" ]] ; then
|
||||
chown -R "${HOST_UID}:${HOST_GID}" "${GITHUB_WORKSPACE}/build-dbginfo"
|
||||
fi
|
||||
|
||||
sudo -H -u "${HOST_USER}" env \
|
||||
CMAKE_BUILD_PARALLEL_LEVEL="${CMAKE_BUILD_PARALLEL_LEVEL-}" \
|
||||
GITHUB_WORKSPACE="${GITHUB_WORKSPACE}" \
|
||||
GIT_CEILING_DIRECTORIES="${GIT_CEILING_DIRECTORIES-}" \
|
||||
ORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY-}" \
|
||||
bash -c '
|
||||
set -e
|
||||
cd "${GITHUB_WORKSPACE}"
|
||||
if [[ "$#" -gt 0 ]] ; then
|
||||
./build_linux.sh "$@"
|
||||
else
|
||||
echo "No build steps were requested after container setup."
|
||||
fi
|
||||
' bash "$@"
|
||||
EOF
|
||||
}
|
||||
|
||||
if [[ -n "${USE_DOCKER}" ]] ; then
|
||||
run_in_docker
|
||||
popd > /dev/null # ${SCRIPT_PATH}
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# cmake 4.x compatibility workaround
|
||||
export CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ echo build type set to %build_type%
|
||||
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
cd deps
|
||||
mkdir %build_dir%
|
||||
if not exist %build_dir% mkdir %build_dir%
|
||||
cd %build_dir%
|
||||
set "SIG_FLAG="
|
||||
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%"
|
||||
@@ -57,12 +57,14 @@ if "%1"=="deps" exit /b 0
|
||||
:slicer
|
||||
echo "building Orca Slicer..."
|
||||
cd %WP%
|
||||
mkdir %build_dir%
|
||||
if not exist %build_dir% mkdir %build_dir%
|
||||
cd %build_dir%
|
||||
|
||||
echo on
|
||||
set CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||
cmake .. -G "Visual Studio 17 2022" -A x64 -DORCA_TOOLS=ON %SIG_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
|
||||
set "WX_FLAG="
|
||||
if exist "%WP%\deps\%build_dir%\OrcaSlicer_dep\usr\local\lib\cmake\wxWidgets-3.3" set "WX_FLAG=-DwxWidgets_DIR=%WP%\deps\%build_dir%\OrcaSlicer_dep\usr\local\lib\cmake\wxWidgets-3.3"
|
||||
cmake .. -G "Visual Studio 17 2022" -A x64 -DORCA_TOOLS=ON %SIG_FLAG% %WX_FLAG% -DCMAKE_BUILD_TYPE=%build_type%
|
||||
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
@echo off
|
||||
cd ..
|
||||
|
||||
1
deps/Boost/Boost.cmake
vendored
1
deps/Boost/Boost.cmake
vendored
@@ -18,6 +18,7 @@ orcaslicer_add_cmake_project(Boost
|
||||
-DBOOST_EXCLUDE_LIBRARIES:STRING=contract|fiber|numpy|stacktrace|wave|test
|
||||
-DBOOST_LOCALE_ENABLE_ICU:BOOL=OFF # do not link to libicu, breaks compatibility between distros
|
||||
-DBUILD_TESTING:BOOL=OFF
|
||||
-DBOOST_IOSTREAMS_ENABLE_BZIP2:BOOL=OFF # avoid libbz2 soname differences in AppImage builds
|
||||
-DBOOST_IOSTREAMS_ENABLE_ZSTD:BOOL=OFF
|
||||
"${_context_abi_line}"
|
||||
"${_context_arch_line}"
|
||||
|
||||
4
deps/GLFW/GLFW.cmake
vendored
4
deps/GLFW/GLFW.cmake
vendored
@@ -7,9 +7,9 @@ else()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(_glfw_use_wayland "-DGLFW_USE_WAYLAND=ON")
|
||||
set(_glfw_use_wayland "-DGLFW_USE_WAYLAND=OFF")
|
||||
else()
|
||||
set(_glfw_use_wayland "-DGLFW_USE_WAYLAND=FF")
|
||||
set(_glfw_use_wayland "-DGLFW_USE_WAYLAND=OFF")
|
||||
endif()
|
||||
|
||||
orcaslicer_add_cmake_project(GLFW
|
||||
|
||||
29
deps/wxWidgets/wxWidgets.cmake
vendored
29
deps/wxWidgets/wxWidgets.cmake
vendored
@@ -32,6 +32,10 @@ endif()
|
||||
orcaslicer_add_cmake_project(
|
||||
wxWidgets
|
||||
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
|
||||
# KX/stable: pin to v3.3.2 (provides wxWebRequest used by 2.3.2 code).
|
||||
# Upstream 2.3.2 left this untagged, so a shallow clone of the moving
|
||||
# default branch broke the build over time.
|
||||
GIT_TAG v3.3.2
|
||||
GIT_SHALLOW ON
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
|
||||
CMAKE_ARGS
|
||||
@@ -62,6 +66,31 @@ orcaslicer_add_cmake_project(
|
||||
-DwxUSE_LIBJPEG=sys
|
||||
-DwxUSE_LIBTIFF=OFF
|
||||
-DwxUSE_EXPAT=sys
|
||||
# wx 3.3 bundles nanosvg; OrcaSlicer's BitmapCache also defines nsvg
|
||||
# symbols -> ODR/LNK2005 on MSVC. Disable wx's copy (as in 2.4).
|
||||
-DwxUSE_NANOSVG=OFF
|
||||
)
|
||||
|
||||
# wxWidgets 3.3 cmake install doesn't include private headers.
|
||||
# OrcaSlicer uses some of the private headers (markuptext, jsscriptwrapper,
|
||||
# accessibility support). Copy the private headers directory after install.
|
||||
if(MSVC)
|
||||
set(_wx_inc_dest ${DESTDIR}/include/wx)
|
||||
else()
|
||||
set(_wx_inc_dest ${DESTDIR}/include/wx-3.3/wx)
|
||||
endif()
|
||||
ExternalProject_Add_Step(dep_wxWidgets copy_private_headers
|
||||
DEPENDEES install
|
||||
COMMENT "Copying wxWidgets private headers"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/private
|
||||
${_wx_inc_dest}/private
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/generic/private
|
||||
${_wx_inc_dest}/generic/private
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include/wx/gtk/private
|
||||
${_wx_inc_dest}/gtk/private
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
|
||||
356
docs/filament-preset-bridge-guide.md
Normal file
356
docs/filament-preset-bridge-guide.md
Normal file
@@ -0,0 +1,356 @@
|
||||
# Eigene Filament-Presets anlegen, prüfen und mit KX-Bridge verknüpfen
|
||||
|
||||
> **Gilt für:** OrcaSlicer-KX v2.4.0-alpha-kx2 oder neuer
|
||||
|
||||
---
|
||||
|
||||
## Was ist die `filament_id` und warum ist sie wichtig?
|
||||
|
||||
Jedes Filament-Preset in OrcaSlicer hat eine interne `filament_id`. Diese ID wird von der KX-Bridge genutzt, um beim AMS-Sync das richtige Preset zuzuordnen.
|
||||
|
||||
- System-Presets (z.B. "Polymaker PolyTerra PLA") haben eine feste ID wie `GFL99` oder `OGFL04`.
|
||||
- **Eigene (User-)Presets** bekommen in OrcaSlicer-KX automatisch eine eindeutige ID, die mit `P` beginnt (z.B. `P3a7f2c1`).
|
||||
|
||||
Ohne eindeutige ID zeigt OrcaSlicer beim Sync immer "Generic PLA" — auch wenn das Preset existiert.
|
||||
|
||||
> **Achtung — abgeleitete Presets (Issue #52):** Wenn du dein Preset von einem **Hersteller-Preset** ableitest (z.B. "Anycubic PLA Matte"), übernimmt es zunächst die feste Hersteller-ID (z.B. `GFA001`). Beim Sync wird dann fälschlicherweise das Hersteller-Preset statt deines eigenen ausgewählt.
|
||||
> Ab OrcaSlicer-KX **v2.4.0-alpha-kx3** wird beim Speichern automatisch eine eigene `P...`-ID vergeben — auch für abgeleitete Presets. Hast du das Preset mit einer älteren Version angelegt, **öffne es einmal und speichere es erneut** (Save), damit die `P`-ID generiert wird.
|
||||
|
||||
---
|
||||
|
||||
## 1. Eigenes Filament-Preset anlegen
|
||||
|
||||
1. OrcaSlicer-KX starten
|
||||
2. Rechts oben im **Filament-Dropdown** ein passendes Basis-Preset wählen (z.B. "Generic PLA" oder ein Hersteller-Preset)
|
||||
3. Einstellungen nach Wunsch anpassen (Temperaturen, Kühlung, etc.)
|
||||
4. Auf das **Speichern-Symbol** (Diskette) klicken → **"Save as new preset"**
|
||||
5. Namen eingeben — z.B. `SUNLU PLA+ 2.0`
|
||||
> Der Name muss später exakt so in der Bridge eingetragen werden.
|
||||
6. Drucker auswählen: **Anycubic Kobra X 0.4 nozzle** — wichtig für die Kompatibilität!
|
||||
7. **Speichern** klicken
|
||||
8. OrcaSlicer **einmal neu starten** — erst dann wird die `filament_id` dauerhaft gespeichert.
|
||||
|
||||
---
|
||||
|
||||
## 2. Eindeutige ID prüfen
|
||||
|
||||
Nach dem Neustart prüfen, ob die ID korrekt gesetzt wurde:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
Die Datei öffnen und nach `filament_id` suchen:
|
||||
|
||||
```json
|
||||
{
|
||||
"filament_id": "P3a7f2c1",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
✅ Korrekt: ID beginnt mit `P` gefolgt von 7 Hex-Zeichen
|
||||
❌ Fehlt oder leer: OrcaSlicer-KX zu alt — Update auf v2.4.0-alpha-kx2 oder neuer
|
||||
|
||||
---
|
||||
|
||||
## 3. Preset auf einen anderen PC übertragen (Import)
|
||||
|
||||
### Exportieren (Quell-PC)
|
||||
|
||||
Die Preset-Datei einfach kopieren:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
### Importieren (Ziel-PC)
|
||||
|
||||
**Methode A — Datei direkt kopieren:**
|
||||
1. Die `.json`-Datei in das gleiche Verzeichnis auf dem Ziel-PC kopieren
|
||||
2. OrcaSlicer neu starten → Preset erscheint im Dropdown
|
||||
|
||||
**Methode B — OrcaSlicer Import-Funktion:**
|
||||
1. In OrcaSlicer: **File → Import → Import Configs...**
|
||||
2. Die `.json`-Datei auswählen
|
||||
3. OrcaSlicer neu starten
|
||||
|
||||
> **Wichtig:** Die `filament_id` in der Datei bleibt erhalten — das Preset wird auf dem Ziel-PC genauso erkannt wie auf dem Quell-PC.
|
||||
|
||||
---
|
||||
|
||||
## 4. Preset in KX-Bridge verknüpfen
|
||||
|
||||
1. KX-Bridge UI öffnen
|
||||
2. **Filament-Verwaltung** → AMS-Slot auswählen
|
||||
3. Im Feld **Filament-Name** exakt den OrcaSlicer-Preset-Namen eintragen:
|
||||
```
|
||||
SUNLU PLA+ 2.0
|
||||
```
|
||||
4. Speichern
|
||||
|
||||
Die Bridge sendet beim Sync `filament_name: "SUNLU PLA+ 2.0"` → OrcaSlicer findet das Preset anhand von Name und `filament_id` → zeigt es korrekt an.
|
||||
|
||||
---
|
||||
|
||||
## Wichtige Hinweise
|
||||
|
||||
| Was | Warum |
|
||||
|-----|-------|
|
||||
| Name in OrcaSlicer und Bridge müssen **exakt** übereinstimmen | Groß-/Kleinschreibung und Sonderzeichen werden verglichen |
|
||||
| Preset muss für **Anycubic Kobra X 0.4 nozzle** kompatibel sein | Beim Speichern den richtigen Drucker auswählen |
|
||||
| Nach dem ersten Speichern OrcaSlicer **neu starten** | Erst dann wird die `filament_id` persistent geschrieben |
|
||||
| **OrcaSlicer-KX v2.4.0-alpha-kx2** oder neuer verwenden | Ältere Versionen generieren keine eindeutige `filament_id` für User-Presets |
|
||||
| Bei von Hersteller-Presets abgeleiteten Presets: **v2.4.0-alpha-kx3** oder neuer | Erst ab dieser Version wird die geerbte Hersteller-ID beim Speichern durch eine eigene `P`-ID ersetzt (Issue #52) |
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
# How to Create, Verify and Import Custom Filament Presets for KX-Bridge
|
||||
|
||||
> **Requires:** OrcaSlicer-KX v2.4.0-alpha-kx2 or newer
|
||||
|
||||
---
|
||||
|
||||
## What is the `filament_id` and why does it matter?
|
||||
|
||||
Every filament preset in OrcaSlicer has an internal `filament_id`. The KX-Bridge uses this ID to match the correct preset during AMS sync.
|
||||
|
||||
- System presets (e.g. "Polymaker PolyTerra PLA") have a fixed ID like `GFL99` or `OGFL04`.
|
||||
- **Custom (user) presets** automatically receive a unique ID starting with `P` (e.g. `P3a7f2c1`) in OrcaSlicer-KX.
|
||||
|
||||
Without a unique ID, OrcaSlicer will always show "Generic PLA" during sync — even if the preset exists.
|
||||
|
||||
> **Caution — derived presets (Issue #52):** If you derive your preset from a **vendor preset** (e.g. "Anycubic PLA Matte"), it initially inherits the fixed vendor ID (e.g. `GFA001`). During sync the vendor preset is then incorrectly selected instead of your own.
|
||||
> As of OrcaSlicer-KX **v2.4.0-alpha-kx3**, a unique `P...` ID is generated automatically on save — including for derived presets. If you created the preset with an older version, **open it once and save it again** so the `P` ID gets generated.
|
||||
|
||||
---
|
||||
|
||||
## 1. Create a Custom Filament Preset
|
||||
|
||||
1. Launch OrcaSlicer-KX
|
||||
2. Select a suitable base preset from the **filament dropdown** (e.g. "Generic PLA" or a vendor preset)
|
||||
3. Adjust settings as needed (temperatures, cooling, etc.)
|
||||
4. Click the **save icon** (floppy disk) → **"Save as new preset"**
|
||||
5. Enter a name — e.g. `SUNLU PLA+ 2.0`
|
||||
> This name must be entered in the bridge exactly as typed here.
|
||||
6. Select printer: **Anycubic Kobra X 0.4 nozzle** — required for compatibility!
|
||||
7. Click **Save**
|
||||
8. **Restart OrcaSlicer once** — the `filament_id` is only written permanently after a restart.
|
||||
|
||||
---
|
||||
|
||||
## 2. Verify the Unique ID
|
||||
|
||||
After restarting, check that the ID was set correctly:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
Open the file and look for `filament_id`:
|
||||
|
||||
```json
|
||||
{
|
||||
"filament_id": "P3a7f2c1",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
✅ Correct: ID starts with `P` followed by 7 hex characters
|
||||
❌ Missing or empty: Your OrcaSlicer-KX version is too old — update to v2.4.0-alpha-kx2 or newer
|
||||
|
||||
---
|
||||
|
||||
## 3. Transfer a Preset to Another PC (Import)
|
||||
|
||||
### Export (source PC)
|
||||
|
||||
Simply copy the preset file:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
### Import (target PC)
|
||||
|
||||
**Method A — Copy file directly:**
|
||||
1. Copy the `.json` file to the same directory on the target PC
|
||||
2. Restart OrcaSlicer → preset appears in the dropdown
|
||||
|
||||
**Method B — OrcaSlicer import function:**
|
||||
1. In OrcaSlicer: **File → Import → Import Configs...**
|
||||
2. Select the `.json` file
|
||||
3. Restart OrcaSlicer
|
||||
|
||||
> **Note:** The `filament_id` inside the file is preserved — the preset will be recognized on the target PC exactly as on the source PC.
|
||||
|
||||
---
|
||||
|
||||
## 4. Link the Preset in KX-Bridge
|
||||
|
||||
1. Open the KX-Bridge UI
|
||||
2. Go to **Filament Management** → select the AMS slot
|
||||
3. In the **Filament Name** field, enter the OrcaSlicer preset name exactly:
|
||||
```
|
||||
SUNLU PLA+ 2.0
|
||||
```
|
||||
4. Save
|
||||
|
||||
The bridge sends `filament_name: "SUNLU PLA+ 2.0"` during sync → OrcaSlicer matches by name and `filament_id` → displays the preset correctly.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| What | Why |
|
||||
|------|-----|
|
||||
| Name in OrcaSlicer and Bridge must match **exactly** | Case and special characters are compared |
|
||||
| Preset must be compatible with **Anycubic Kobra X 0.4 nozzle** | Select the correct printer when saving |
|
||||
| **Restart OrcaSlicer** after saving for the first time | The `filament_id` is only written persistently after a restart |
|
||||
| Use **OrcaSlicer-KX v2.4.0-alpha-kx2** or newer | Older versions do not generate a unique `filament_id` for user presets |
|
||||
| For presets derived from vendor presets: **v2.4.0-alpha-kx3** or newer | Only from this version is the inherited vendor ID replaced with a unique `P` ID on save (Issue #52) |
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
# Cómo crear, verificar e importar perfiles de filamento personalizados para KX-Bridge
|
||||
|
||||
> **Requiere:** OrcaSlicer-KX v2.4.0-alpha-kx2 o superior
|
||||
|
||||
---
|
||||
|
||||
## ¿Qué es el `filament_id` y por qué es importante?
|
||||
|
||||
Cada perfil de filamento en OrcaSlicer tiene un `filament_id` interno. KX-Bridge usa este ID para asignar el perfil correcto durante la sincronización AMS.
|
||||
|
||||
- Los perfiles del sistema (p. ej. "Polymaker PolyTerra PLA") tienen un ID fijo como `GFL99` o `OGFL04`.
|
||||
- Los **perfiles personalizados (usuario)** reciben automáticamente un ID único que empieza por `P` (p. ej. `P3a7f2c1`) en OrcaSlicer-KX.
|
||||
|
||||
Sin un ID único, OrcaSlicer mostrará siempre "Generic PLA" durante la sincronización, aunque el perfil exista.
|
||||
|
||||
> **Atención — perfiles derivados (Issue #52):** Si derivas tu perfil de un **perfil de fabricante** (p. ej. "Anycubic PLA Matte"), inicialmente hereda el ID fijo del fabricante (p. ej. `GFA001`). Durante la sincronización se selecciona entonces por error el perfil del fabricante en lugar del tuyo.
|
||||
> A partir de OrcaSlicer-KX **v2.4.0-alpha-kx3**, se genera automáticamente un ID `P...` único al guardar — también para perfiles derivados. Si creaste el perfil con una versión anterior, **ábrelo una vez y vuelve a guardarlo** (Save) para que se genere el ID `P`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Crear un perfil de filamento personalizado
|
||||
|
||||
1. Iniciar OrcaSlicer-KX
|
||||
2. Seleccionar un perfil base adecuado en el **menú desplegable de filamento** (p. ej. "Generic PLA" o un perfil de fabricante)
|
||||
3. Ajustar la configuración según sea necesario (temperaturas, refrigeración, etc.)
|
||||
4. Hacer clic en el **icono de guardar** (disquete) → **"Save as new preset"**
|
||||
5. Introducir un nombre — p. ej. `SUNLU PLA+ 2.0`
|
||||
> Este nombre debe introducirse en la bridge exactamente igual.
|
||||
6. Seleccionar impresora: **Anycubic Kobra X 0.4 nozzle** — ¡necesario para la compatibilidad!
|
||||
7. Hacer clic en **Guardar**
|
||||
8. **Reiniciar OrcaSlicer una vez** — el `filament_id` solo se escribe de forma permanente tras un reinicio.
|
||||
|
||||
---
|
||||
|
||||
## 2. Verificar el ID único
|
||||
|
||||
Tras reiniciar, comprobar que el ID se ha establecido correctamente:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
Abrir el archivo y buscar `filament_id`:
|
||||
|
||||
```json
|
||||
{
|
||||
"filament_id": "P3a7f2c1",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
✅ Correcto: el ID empieza por `P` seguido de 7 caracteres hexadecimales
|
||||
❌ Falta o está vacío: la versión de OrcaSlicer-KX es demasiado antigua — actualizar a v2.4.0-alpha-kx2 o superior
|
||||
|
||||
---
|
||||
|
||||
## 3. Transferir un perfil a otro PC (importar)
|
||||
|
||||
### Exportar (PC de origen)
|
||||
|
||||
Simplemente copiar el archivo del perfil:
|
||||
|
||||
**Windows:**
|
||||
```
|
||||
%APPDATA%\OrcaSlicer\user\default\filament\SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```
|
||||
~/.config/OrcaSlicer/user/default/filament/SUNLU PLA+ 2.0.json
|
||||
```
|
||||
|
||||
### Importar (PC de destino)
|
||||
|
||||
**Método A — Copiar el archivo directamente:**
|
||||
1. Copiar el archivo `.json` al mismo directorio en el PC de destino
|
||||
2. Reiniciar OrcaSlicer → el perfil aparece en el menú desplegable
|
||||
|
||||
**Método B — Función de importación de OrcaSlicer:**
|
||||
1. En OrcaSlicer: **File → Import → Import Configs...**
|
||||
2. Seleccionar el archivo `.json`
|
||||
3. Reiniciar OrcaSlicer
|
||||
|
||||
> **Nota:** El `filament_id` dentro del archivo se conserva — el perfil se reconocerá en el PC de destino exactamente igual que en el de origen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Vincular el perfil en KX-Bridge
|
||||
|
||||
1. Abrir la interfaz de KX-Bridge
|
||||
2. Ir a **Gestión de filamentos** → seleccionar la ranura AMS
|
||||
3. En el campo **Nombre de filamento**, introducir el nombre exacto del perfil de OrcaSlicer:
|
||||
```
|
||||
SUNLU PLA+ 2.0
|
||||
```
|
||||
4. Guardar
|
||||
|
||||
La bridge envía `filament_name: "SUNLU PLA+ 2.0"` durante la sincronización → OrcaSlicer busca por nombre y `filament_id` → muestra el perfil correctamente.
|
||||
|
||||
---
|
||||
|
||||
## Referencia rápida
|
||||
|
||||
| Qué | Por qué |
|
||||
|-----|---------|
|
||||
| El nombre en OrcaSlicer y en Bridge debe coincidir **exactamente** | Se comparan mayúsculas, minúsculas y caracteres especiales |
|
||||
| El perfil debe ser compatible con **Anycubic Kobra X 0.4 nozzle** | Seleccionar la impresora correcta al guardar |
|
||||
| **Reiniciar OrcaSlicer** tras guardar por primera vez | El `filament_id` solo se escribe de forma permanente tras un reinicio |
|
||||
| Usar **OrcaSlicer-KX v2.4.0-alpha-kx2** o superior | Las versiones anteriores no generan un `filament_id` único para perfiles de usuario |
|
||||
| Para perfiles derivados de perfiles de fabricante: **v2.4.0-alpha-kx3** o superior | Solo a partir de esta versión se reemplaza el ID heredado del fabricante por un ID `P` único al guardar (Issue #52) |
|
||||
@@ -25,5 +25,6 @@
|
||||
],
|
||||
"retraction_minimum_travel": [
|
||||
"3"
|
||||
]
|
||||
],
|
||||
"printer_agent": "qidi"
|
||||
}
|
||||
52
scripts/appimage_lib_policy.sh
Executable file
52
scripts/appimage_lib_policy.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Libraries that are safer to resolve from the host than bundle into the AppImage.
|
||||
# Keep this list focused on the glibc/runtime loader and host-specific graphics/audio stacks.
|
||||
appimage_is_host_library() {
|
||||
local lib_name
|
||||
lib_name="$(basename "$1")"
|
||||
|
||||
case "$lib_name" in
|
||||
linux-vdso.so.*|linux-gate.so.*|ld-linux*.so*|ld64.so*|ld-musl-*.so*|libc.so*|libpthread.so*|libm.so*|libdl.so*|librt.so*|libresolv.so*|libutil.so*|libanl.so*|libnsl.so*|libBrokenLocale.so*|libcrypt.so*|libnss_*.so*|\
|
||||
libGL.so*|libOpenGL.so*|libGLX*.so*|libGLU.so*|libEGL.so*|libGLES*.so*|libGLdispatch.so*|libdrm.so*|libdrm_*.so*|libgbm.so*|libwayland-*.so*|libxcb*.so*|libX11.so*|libX11-xcb.so*|libXau.so*|libXdmcp.so*|libXext.so*|libXdamage.so*|libXfixes.so*|libXcomposite.so*|libXrender.so*|libXrandr.so*|libXcursor.so*|libXi.so*|libXinerama.so*|libxshmfence.so*|libxkbcommon.so*|libxkbcommon-x11.so*|libSM.so*|libICE.so*|libudev.so*|libasound.so*|libpulse.so*|libpulsecommon*.so*|libjack.so*|libpipewire-*.so*|libvulkan.so*|libva.so*|libva-*.so*|\
|
||||
libgtk-*.so*|libgdk-*.so*|libpango*.so*|libatk-bridge-*.so*|libatk*.so*|libatspi.so*|libcairo*.so*|libgdk_pixbuf-*.so*|libgio-2.0.so*|libgmodule-2.0.so*|libgobject-2.0.so*|libglib-2.0.so*|\
|
||||
libgstreamer-1.0.so*|libgst*.so*|libsoup-*.so*|libwebkit2gtk-*.so*|libjavascriptcoregtk-*.so*|libsecret-1.so*|libmanette-0.2.so*|libenchant-2.so*|libhyphen.so*|libtasn1.so*|\
|
||||
libfontconfig.so*|libfreetype.so*|libharfbuzz*.so*|libfribidi.so*|libgraphite2.so*|libthai.so*|libdatrie.so*|libepoxy.so*|libpixman-1.so*|\
|
||||
libstdc++.so*|libgcc_s.so*|libatomic.so*|libdbus-1.so*|libuuid.so*|libffi.so*|libselinux.so*|libmount.so*|libblkid.so*|libpcre2-*.so*|libsystemd.so*|libcap.so*|libseccomp.so*|\
|
||||
libexpat.so*|libz.so*|liblzma.so*|libdeflate.so*|libzstd.so*|libpng*.so*|libjpeg.so*|libtiff.so*|libwebp*.so*|libwoff2*.so*|libjbig.so*|libLerc.so*|liblcms2.so*|libjxl*.so*|libhwy.so*|libsharpyuv.so*|\
|
||||
libxml2.so*|libxslt.so*|libsqlite3.so*|libicu*.so*|libpsl.so*|libnghttp2.so*|libbrotli*.so*|libidn2.so*|libunistring.so*|libgcrypt.so*|libgpg-error.so*|\
|
||||
libgssapi_krb5.so*|libkrb5.so*|libkrb5support.so*|libk5crypto.so*|libcom_err.so*|libkeyutils.so*|liborc-0.4.so*|libgudev-1.0.so*)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
appimage_is_elf_file() {
|
||||
file -b "$1" 2>/dev/null | grep -q '^ELF '
|
||||
}
|
||||
|
||||
appimage_list_direct_dependencies() {
|
||||
local target="$1"
|
||||
local line dep
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == *"=> not found"* ]]; then
|
||||
echo "MISSING:${line%% *}"
|
||||
continue
|
||||
fi
|
||||
|
||||
dep=""
|
||||
if [[ "$line" == *"=>"* ]]; then
|
||||
dep="$(printf '%s\n' "$line" | sed -n 's/.*=> \(\/[^ ]*\).*/\1/p')"
|
||||
elif [[ "$line" =~ ^[[:space:]]/ ]]; then
|
||||
dep="$(printf '%s\n' "$line" | awk '{print $1}')"
|
||||
fi
|
||||
|
||||
if [[ -n "$dep" ]]; then
|
||||
echo "$dep"
|
||||
fi
|
||||
done < <(ldd "$target" 2>/dev/null || true)
|
||||
}
|
||||
99
scripts/check_appimage_libs.sh
Executable file
99
scripts/check_appimage_libs.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=/dev/null
|
||||
source "${SCRIPT_DIR}/appimage_lib_policy.sh"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 <appdir> [entrypoint]"
|
||||
}
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPDIR="$1"
|
||||
ENTRYPOINT="${2:-}"
|
||||
|
||||
if [[ ! -d "$APPDIR" ]]; then
|
||||
echo "Error: AppDir does not exist: $APPDIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APPDIR="$(cd -- "$APPDIR" && pwd)"
|
||||
|
||||
if [[ -n "$ENTRYPOINT" ]]; then
|
||||
if [[ ! -e "$ENTRYPOINT" ]]; then
|
||||
echo "Error: entrypoint does not exist: $ENTRYPOINT"
|
||||
exit 1
|
||||
fi
|
||||
ENTRYPOINT="$(cd -- "$(dirname -- "$ENTRYPOINT")" && pwd)/$(basename -- "$ENTRYPOINT")"
|
||||
fi
|
||||
|
||||
declare -a lib_paths=(
|
||||
"$APPDIR/lib/orca-runtime"
|
||||
"$APPDIR/lib"
|
||||
"$APPDIR/bin"
|
||||
)
|
||||
|
||||
for candidate in \
|
||||
"$APPDIR/lib/gstreamer-1.0" \
|
||||
"$APPDIR/lib/gio/modules" \
|
||||
"$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders"; do
|
||||
if [[ -d "$candidate" ]]; then
|
||||
lib_paths+=("$candidate")
|
||||
fi
|
||||
done
|
||||
|
||||
audit_ld_library_path="$(IFS=:; printf '%s' "${lib_paths[*]}")"
|
||||
|
||||
declare -a targets=()
|
||||
declare -A seen_unresolved=()
|
||||
declare -A seen_host=()
|
||||
|
||||
if [[ -n "$ENTRYPOINT" ]]; then
|
||||
targets+=("$ENTRYPOINT")
|
||||
fi
|
||||
|
||||
while IFS= read -r -d '' file; do
|
||||
if appimage_is_elf_file "$file"; then
|
||||
targets+=("$file")
|
||||
fi
|
||||
done < <(find "$APPDIR" -type f -print0)
|
||||
|
||||
for target in "${targets[@]}"; do
|
||||
while IFS= read -r dep; do
|
||||
if [[ "$dep" == MISSING:* ]]; then
|
||||
seen_unresolved["$target -> ${dep#MISSING:}"]=1
|
||||
continue
|
||||
fi
|
||||
|
||||
dep="$(readlink -f "$dep" 2>/dev/null || printf '%s' "$dep")"
|
||||
if [[ "$dep" == "$APPDIR"* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if appimage_is_host_library "$dep"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
seen_host["$target -> $dep"]=1
|
||||
done < <(LD_LIBRARY_PATH="$audit_ld_library_path${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" appimage_list_direct_dependencies "$target")
|
||||
done
|
||||
|
||||
if (( ${#seen_unresolved[@]} > 0 )); then
|
||||
echo "AppImage dependency audit failed: unresolved runtime libraries detected"
|
||||
printf '%s\n' "${!seen_unresolved[@]}" | LC_ALL=C sort
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( ${#seen_host[@]} > 0 )); then
|
||||
echo "AppImage dependency audit failed: unexpected host libraries are still required"
|
||||
printf '%s\n' "${!seen_host[@]}" | LC_ALL=C sort
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "AppImage dependency audit passed: $APPDIR"
|
||||
@@ -2,9 +2,6 @@
|
||||
<component type="desktop">
|
||||
<id>com.orcaslicer.OrcaSlicer</id>
|
||||
<launchable type="desktop-id">com.orcaslicer.OrcaSlicer.desktop</launchable>
|
||||
<provides>
|
||||
<id>com.orcaslicer.OrcaSlicer.desktop</id>
|
||||
</provides>
|
||||
<name>OrcaSlicer</name>
|
||||
<summary>Get even more perfect prints!</summary>
|
||||
<developer id="com.orcaslicer">
|
||||
@@ -27,11 +24,11 @@
|
||||
</recommends>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/scripts/flatpak/images/1.png</image>
|
||||
<image>https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/v2.3.2/scripts/flatpak/images/1.png</image>
|
||||
<caption>A model ready to be sliced on a buildplate.</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/scripts/flatpak/images/2.png</image>
|
||||
<image>https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/v2.3.2/scripts/flatpak/images/2.png</image>
|
||||
<caption>A calibration test ready to be printed out.</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
@@ -42,17 +39,14 @@
|
||||
<p>Key features include advanced calibration tools, adaptive layer heights, tree supports,
|
||||
multi-material support, and an intuitive interface for both beginners and experts.
|
||||
OrcaSlicer also provides built-in network printing capabilities for compatible printers.</p>
|
||||
<p>Originally forked from Bambu Studio and PrusaSlicer, OrcaSlicer builds on a strong
|
||||
foundation with community-driven improvements and optimizations for print quality
|
||||
and reliability.</p>
|
||||
</description>
|
||||
<branding>
|
||||
<color type="primary" scheme_preference="light">#009688</color>
|
||||
<color type="primary" scheme_preference="dark">#00695C</color>
|
||||
</branding>
|
||||
<releases>
|
||||
<release version="2.3.2-rc" date="2025-03-01">
|
||||
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/v2.3.2-rc</url>
|
||||
<release version="2.3.2" date="2025-03-23">
|
||||
<url type="details">https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/v2.3.2</url>
|
||||
<description>
|
||||
<p>See the release page for detailed changelog.</p>
|
||||
</description>
|
||||
|
||||
@@ -327,7 +327,6 @@ modules:
|
||||
- | # Desktop integration files
|
||||
install -Dm644 -t /app/share/icons/hicolor/scalable/apps/ resources/images/OrcaSlicer.svg
|
||||
install -Dm644 ${FLATPAK_ID}.metainfo.xml /app/share/metainfo/${FLATPAK_ID}.metainfo.xml
|
||||
mv /app/share/applications/OrcaSlicer.desktop /app/share/applications/${FLATPAK_ID}.desktop
|
||||
desktop-file-edit --set-key=Exec --set-value="entrypoint %U" /app/share/applications/${FLATPAK_ID}.desktop
|
||||
install -Dm755 entrypoint /app/bin
|
||||
install -Dm755 umount /app/bin
|
||||
|
||||
@@ -36,14 +36,21 @@ if (SLIC3R_GUI)
|
||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS base core adv html gl aui net media webview)
|
||||
endif ()
|
||||
else ()
|
||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS html adv gl core base webview aui net media)
|
||||
# wxWidgets 3.3 ships CMake config files; use CONFIG mode so wxWidgets_DIR
|
||||
# is honored. The legacy FindwxWidgets module ignores wxWidgets_DIR and
|
||||
# only probes WXWIN/wx-config, which is unset on the Windows builder.
|
||||
find_package(wxWidgets 3.3 CONFIG REQUIRED COMPONENTS html adv gl core base webview aui net media)
|
||||
endif ()
|
||||
|
||||
if(UNIX)
|
||||
message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}")
|
||||
endif()
|
||||
|
||||
include(${wxWidgets_USE_FILE})
|
||||
# CONFIG mode (Windows, wx 3.3) does not set wxWidgets_USE_FILE; only the
|
||||
# legacy module mode (Linux wx-config) does. Guard the include accordingly.
|
||||
if(wxWidgets_USE_FILE)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
endif()
|
||||
|
||||
find_package(JPEG QUIET)
|
||||
|
||||
@@ -121,6 +128,8 @@ endif (MINGW)
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
# Binary name on unix like systems (Linux, Unix)
|
||||
set_target_properties(OrcaSlicer PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
# wx 3.3 bundles nanosvg internally causing duplicate symbols; allow them.
|
||||
target_link_options(OrcaSlicer PRIVATE "-Wl,--allow-multiple-definition")
|
||||
set(SLIC3R_APP_CMD "orca-slicer")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/dev-utils/platform/unix/build_linux_image.sh.in ${CMAKE_CURRENT_BINARY_DIR}/build_linux_image.sh USE_SOURCE_PERMISSIONS @ONLY)
|
||||
endif ()
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <wchar.h>
|
||||
// wxWidgets 3.3 wrapcctl.h needs HDITEM etc. from commctrl.h pre-included.
|
||||
#include <commctrl.h>
|
||||
#ifdef SLIC3R_GUI
|
||||
extern "C"
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
APPIMAGETOOLURL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage"
|
||||
|
||||
|
||||
@@ -11,14 +11,13 @@ if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
||||
sed '0,/AI\x02/{s|AI\x02|\x00\x00\x00|}' -i ../appimagetool.AppImage
|
||||
fi
|
||||
|
||||
sed -i -e 's#/usr#././#g' bin/@SLIC3R_APP_CMD@
|
||||
mv @SLIC3R_APP_CMD@ AppRun
|
||||
chmod +x AppRun
|
||||
|
||||
cp resources/images/@SLIC3R_APP_KEY@_192px.png @SLIC3R_APP_KEY@.png
|
||||
mkdir -p usr/share/icons/hicolor/192x192/apps
|
||||
cp resources/images/@SLIC3R_APP_KEY@_192px.png usr/share/icons/hicolor/192x192/apps/@SLIC3R_APP_KEY@.png
|
||||
cat <<EOF > @SLIC3R_APP_KEY@.desktop
|
||||
cat <<EOF > com.orcaslicer.@SLIC3R_APP_KEY@.desktop
|
||||
[Desktop Entry]
|
||||
Name=@SLIC3R_APP_KEY@
|
||||
Exec=AppRun %F
|
||||
@@ -28,13 +27,19 @@ Categories=Utility;
|
||||
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;
|
||||
EOF
|
||||
|
||||
mkdir -p usr/share/applications
|
||||
cp com.orcaslicer.@SLIC3R_APP_KEY@.desktop usr/share/applications/
|
||||
|
||||
mkdir -p usr/share/metainfo
|
||||
cp @CMAKE_CURRENT_SOURCE_DIR@/scripts/flatpak/com.orcaslicer.@SLIC3R_APP_KEY@.metainfo.xml usr/share/metainfo/
|
||||
|
||||
|
||||
export ARCH=$(uname -m)
|
||||
|
||||
if [ -f /.dockerenv ] ; then # Only run if inside of a Docker Container
|
||||
../appimagetool.AppImage --appimage-extract-and-run . $([ ! -z "${container}" ] && echo '--appimage-extract-and-run')
|
||||
../appimagetool.AppImage --appimage-extract-and-run . $([ -n "${container}" ] && echo '--appimage-extract-and-run')
|
||||
else
|
||||
../appimagetool.AppImage . $([ ! -z "${container}" ] && echo '--appimage-extract-and-run')
|
||||
../appimagetool.AppImage . $([ -n "${container}" ] && echo '--appimage-extract-and-run')
|
||||
fi
|
||||
|
||||
mv @SLIC3R_APP_KEY@-$(uname -m).AppImage ${APP_IMAGE}
|
||||
|
||||
@@ -3,10 +3,25 @@
|
||||
# Despite the script name, this doesn't necessarily create an "image";
|
||||
# it sets up a compatibility script wrapper for the binary and arranges some resources.
|
||||
|
||||
set -e
|
||||
|
||||
export ROOT=$(echo $ROOT | grep . || pwd)
|
||||
export NCORES=`nproc --all`
|
||||
export NCORES=$(nproc --all)
|
||||
CONFIG=Release
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
|
||||
POLICY_FILE="${REPO_ROOT}/scripts/appimage_lib_policy.sh"
|
||||
CHECK_SCRIPT="${REPO_ROOT}/scripts/check_appimage_libs.sh"
|
||||
|
||||
if [ ! -f "${POLICY_FILE}" ]; then
|
||||
echo "Error: missing AppImage helper ${POLICY_FILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck source=/dev/null
|
||||
source "${POLICY_FILE}"
|
||||
|
||||
while getopts ":ihR:" opt; do
|
||||
case ${opt} in
|
||||
i )
|
||||
@@ -23,35 +38,324 @@ while getopts ":ihR:" opt; do
|
||||
esac
|
||||
done
|
||||
|
||||
echo -n "[9/9] Generating Linux app..."
|
||||
#{
|
||||
# create directory and copy into it
|
||||
if [ -d "package" ]; then
|
||||
rm -rf package
|
||||
fi
|
||||
mkdir -p package/bin
|
||||
copy_directory_if_present() {
|
||||
local src_dir="$1"
|
||||
local dst_dir="$2"
|
||||
|
||||
if [ -n "$src_dir" ] && [ -d "$src_dir" ]; then
|
||||
mkdir -p "$dst_dir"
|
||||
cp -a "$src_dir"/. "$dst_dir"/
|
||||
fi
|
||||
}
|
||||
|
||||
find_pkg_config_dir() {
|
||||
local variable="$1"
|
||||
shift
|
||||
|
||||
local module
|
||||
for module in "$@"; do
|
||||
if pkg-config --exists "$module" 2>/dev/null; then
|
||||
pkg-config --variable="$variable" "$module" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
find_first_existing_file() {
|
||||
local path
|
||||
for path in "$@"; do
|
||||
if [ -f "$path" ]; then
|
||||
printf '%s\n' "$path"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
copy_shared_object_to_dir() {
|
||||
local src="$1"
|
||||
local dst_dir="$2"
|
||||
local src_real dst_name soname
|
||||
|
||||
src_real="$(readlink -f "$src")"
|
||||
dst_name="$(basename "$src_real")"
|
||||
mkdir -p "$dst_dir"
|
||||
cp -fL "$src_real" "$dst_dir/$dst_name"
|
||||
|
||||
if [ -L "$src" ]; then
|
||||
ln -snf "$dst_name" "$dst_dir/$(basename "$src")"
|
||||
fi
|
||||
|
||||
soname="$(objdump -p "$src_real" 2>/dev/null | awk '$1 == "SONAME" { print $2; exit }')"
|
||||
if [ -n "$soname" ] && [ "$soname" != "$dst_name" ]; then
|
||||
ln -snf "$dst_name" "$dst_dir/$soname"
|
||||
fi
|
||||
}
|
||||
|
||||
bundle_dependency_closure() {
|
||||
local dst_dir="$1"
|
||||
shift
|
||||
|
||||
local -a queue=("$@")
|
||||
local target dep dep_real copied_path
|
||||
declare -A seen=()
|
||||
|
||||
while [ ${#queue[@]} -gt 0 ]; do
|
||||
target="${queue[0]}"
|
||||
queue=("${queue[@]:1}")
|
||||
|
||||
if [ ! -e "$target" ] || ! appimage_is_elf_file "$target"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r dep; do
|
||||
if [[ "$dep" == MISSING:* ]]; then
|
||||
echo "Error: missing runtime dependency ${dep#MISSING:} while bundling $target"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dep_real="$(readlink -f "$dep" 2>/dev/null || printf '%s' "$dep")"
|
||||
if appimage_is_host_library "$dep_real"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "${seen[$dep_real]}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
seen[$dep_real]=1
|
||||
copy_shared_object_to_dir "$dep" "$dst_dir"
|
||||
copied_path="$dst_dir/$(basename "$dep_real")"
|
||||
if [ -e "$copied_path" ]; then
|
||||
queue+=("$copied_path")
|
||||
fi
|
||||
done < <(appimage_list_direct_dependencies "$target")
|
||||
done
|
||||
}
|
||||
|
||||
echo -n "[9/9] Generating Linux app..."
|
||||
# {
|
||||
if [ -d "package" ]; then
|
||||
rm -rf package
|
||||
fi
|
||||
|
||||
APPDIR="package"
|
||||
BIN_DIR="$APPDIR/bin"
|
||||
LIB_DIR="$APPDIR/lib"
|
||||
PRIVATE_LIB_DIR="$LIB_DIR/orca-runtime"
|
||||
SHARE_DIR="$APPDIR/share"
|
||||
LIBEXEC_DIR="$APPDIR/libexec"
|
||||
BUNDLE_DESKTOP_STACK="${ORCA_BUNDLE_DESKTOP_STACK:-0}"
|
||||
GST_PLUGIN_DIR="$LIB_DIR/gstreamer-1.0"
|
||||
GIO_MODULE_DIR="$LIB_DIR/gio/modules"
|
||||
GDK_PIXBUF_DIR="$LIB_DIR/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
|
||||
mkdir -p "$BIN_DIR" "$LIB_DIR" "$PRIVATE_LIB_DIR" "$SHARE_DIR" "$LIBEXEC_DIR"
|
||||
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
|
||||
mkdir -p "$GST_PLUGIN_DIR" "$GIO_MODULE_DIR" "$GDK_PIXBUF_DIR"
|
||||
fi
|
||||
|
||||
cp -Rf ../resources "$APPDIR/resources"
|
||||
|
||||
# Copy Resources
|
||||
cp -Rf ../resources package/resources
|
||||
|
||||
# Find and hard link the @SLIC3R_APP_CMD@ binary from Multi-Config build
|
||||
ORIGINAL_BINARY_LOCATION=""
|
||||
if [ -f "src/${CONFIG}/@SLIC3R_APP_CMD@" ]; then
|
||||
ORIGINAL_BINARY_LOCATION="$(realpath "src/${CONFIG}/@SLIC3R_APP_CMD@")"
|
||||
ORIGINAL_BINARY_LOCATION="$(realpath "src/${CONFIG}/@SLIC3R_APP_CMD@")"
|
||||
elif [ -f "src/@SLIC3R_APP_CMD@" ]; then
|
||||
# Fallback for single-config builds
|
||||
ORIGINAL_BINARY_LOCATION="$(realpath "src/@SLIC3R_APP_CMD@")"
|
||||
else
|
||||
echo "Error: @SLIC3R_APP_CMD@ binary not found in any configuration directory"
|
||||
exit 1
|
||||
fi
|
||||
cp -fl "${ORIGINAL_BINARY_LOCATION}" package/bin/@SLIC3R_APP_CMD@
|
||||
cp -fl "${ORIGINAL_BINARY_LOCATION}" "$BIN_DIR/@SLIC3R_APP_CMD@"
|
||||
|
||||
if [ "$BUNDLE_DESKTOP_STACK" = "1" ]; then
|
||||
GSTREAMER_PLUGIN_SOURCE_DIR="$(find_pkg_config_dir pluginsdir gstreamer-1.0 || true)"
|
||||
if [ -z "$GSTREAMER_PLUGIN_SOURCE_DIR" ]; then
|
||||
GSTREAMER_PLUGIN_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 4 -type d -name gstreamer-1.0 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GSTREAMER_PLUGIN_SOURCE_DIR" "$GST_PLUGIN_DIR"
|
||||
|
||||
GIO_MODULE_SOURCE_DIR="$(find_pkg_config_dir giomoduledir gio-2.0 || true)"
|
||||
if [ -z "$GIO_MODULE_SOURCE_DIR" ]; then
|
||||
GIO_MODULE_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 5 -type d -path '*/gio/modules' 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GIO_MODULE_SOURCE_DIR" "$GIO_MODULE_DIR"
|
||||
|
||||
GDK_PIXBUF_SOURCE_DIR="$(find_pkg_config_dir gdk_pixbuf_moduledir gdk-pixbuf-2.0 || true)"
|
||||
if [ -z "$GDK_PIXBUF_SOURCE_DIR" ]; then
|
||||
GDK_PIXBUF_SOURCE_DIR="$(find /usr/lib /usr/lib64 -maxdepth 6 -type d -path '*/gdk-pixbuf-2.0/*/loaders' 2>/dev/null | head -n1)"
|
||||
fi
|
||||
copy_directory_if_present "$GDK_PIXBUF_SOURCE_DIR" "$GDK_PIXBUF_DIR"
|
||||
|
||||
GLIB_SCHEMAS_SOURCE_DIR="$(find_pkg_config_dir schemasdir gio-2.0 || true)"
|
||||
if [ -z "$GLIB_SCHEMAS_SOURCE_DIR" ]; then
|
||||
GLIB_SCHEMAS_SOURCE_DIR="/usr/share/glib-2.0/schemas"
|
||||
fi
|
||||
copy_directory_if_present "$GLIB_SCHEMAS_SOURCE_DIR" "$SHARE_DIR/glib-2.0/schemas"
|
||||
if [ -d "$SHARE_DIR/glib-2.0/schemas" ] && command -v glib-compile-schemas >/dev/null 2>&1; then
|
||||
glib-compile-schemas "$SHARE_DIR/glib-2.0/schemas"
|
||||
fi
|
||||
|
||||
# Distros disagree on where helper executables live: pkg-config may point to libexec,
|
||||
# Debian/Ubuntu often ship the scanner under a multiarch libdir, and RPM distros may use lib64.
|
||||
GST_PLUGIN_SCANNER_SOURCE="$(find_first_existing_file \
|
||||
"$(find_pkg_config_dir pluginscannerdir gstreamer-1.0 || true)/gst-plugin-scanner" \
|
||||
/usr/libexec/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib/*/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner \
|
||||
/usr/lib64/gstreamer1.0/gstreamer-1.0/gst-plugin-scanner)" || true
|
||||
if [ -n "$GST_PLUGIN_SCANNER_SOURCE" ]; then
|
||||
mkdir -p "$LIBEXEC_DIR/gstreamer-1.0"
|
||||
cp -fL "$GST_PLUGIN_SCANNER_SOURCE" "$LIBEXEC_DIR/gstreamer-1.0/gst-plugin-scanner"
|
||||
fi
|
||||
|
||||
# Prefer the canonical pkg-config-reported helper path, but keep PATH-based fallbacks
|
||||
# for distros exposing gdk-pixbuf-query-loaders or gdk-pixbuf-query-loaders-64 directly.
|
||||
GDK_PIXBUF_QUERY_LOADERS_SOURCE="$(find_first_existing_file \
|
||||
"$(pkg-config --variable=gdk_pixbuf_query_loaders gdk-pixbuf-2.0 2>/dev/null || true)" \
|
||||
"$(command -v gdk-pixbuf-query-loaders 2>/dev/null || true)" \
|
||||
"$(command -v gdk-pixbuf-query-loaders-64 2>/dev/null || true)")" || true
|
||||
if [ -n "$GDK_PIXBUF_QUERY_LOADERS_SOURCE" ]; then
|
||||
cp -fL "$GDK_PIXBUF_QUERY_LOADERS_SOURCE" "$LIBEXEC_DIR/gdk-pixbuf-query-loaders"
|
||||
fi
|
||||
fi
|
||||
|
||||
# WebKitGTK helper binaries are resolved from distro-specific absolute paths
|
||||
# baked into libwebkit2gtk. Bundling Ubuntu's WebKitGTK inside an AppImage
|
||||
# built with -g makes those helper paths unusable on Fedora and vice versa,
|
||||
# so rely on the host WebKitGTK runtime instead of copying the helper stack.
|
||||
|
||||
mapfile -d '' BUNDLE_TARGETS < <(find "$BIN_DIR" "$LIB_DIR" "$LIBEXEC_DIR" -type f -print0 2>/dev/null)
|
||||
bundle_dependency_closure "$PRIVATE_LIB_DIR" "${BUNDLE_TARGETS[@]}"
|
||||
|
||||
cat << EOF >"$LIBEXEC_DIR/@SLIC3R_APP_CMD@-env"
|
||||
#!/bin/sh
|
||||
set -e
|
||||
SELF_DIR=\$(CDPATH= cd -- "\$(dirname -- "\$0")" && pwd)
|
||||
APPDIR="\${APPDIR:-\$(CDPATH= cd -- "\$SELF_DIR/.." && pwd)}"
|
||||
USE_BUNDLED_WEBKITGTK_STACK=0
|
||||
if [ -e "\$APPDIR/lib/libwebkit2gtk-4.1.so.0" ] || [ -e "\$APPDIR/lib/libwebkit2gtk-4.0.so.0" ]; then
|
||||
USE_BUNDLED_WEBKITGTK_STACK=1
|
||||
fi
|
||||
|
||||
export APPDIR
|
||||
PRIVATE_LIB_DIR="\$APPDIR/lib/orca-runtime"
|
||||
if [ -d "\$PRIVATE_LIB_DIR" ]; then
|
||||
export LD_LIBRARY_PATH="\$PRIVATE_LIB_DIR:\$APPDIR/bin\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
else
|
||||
export LD_LIBRARY_PATH="\$APPDIR/bin\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
fi
|
||||
|
||||
has_host_runtime_library() {
|
||||
local lib_name path
|
||||
|
||||
if command -v ldconfig >/dev/null 2>&1; then
|
||||
if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
for path in \
|
||||
/lib /lib64 /usr/lib /usr/lib64 \
|
||||
/usr/lib/* /usr/lib64/* /lib/* /lib64/*; do
|
||||
if [ -e "\$path/\$lib_name" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
target_missing_runtime_library() {
|
||||
local target_bin="\$1"
|
||||
local lib_name="\$2"
|
||||
|
||||
if [ -z "\$target_bin" ] || [ ! -e "\$target_bin" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if command -v ldd >/dev/null 2>&1; then
|
||||
if ldd "\$target_bin" 2>/dev/null | grep -Fq "\$lib_name => not found"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
TARGET_BIN="\$1"
|
||||
|
||||
if target_missing_runtime_library "\$TARGET_BIN" "libOpenGL.so.0" || ! has_host_runtime_library "libOpenGL.so.0"; then
|
||||
echo "Error: missing host OpenGL runtime library libOpenGL.so.0." >&2
|
||||
echo "On Ubuntu/Pop!_OS/Debian, install: libopengl0 and libglu1-mesa" >&2
|
||||
echo "On Arch/CachyOS, install: libglvnd" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "\$USE_BUNDLED_WEBKITGTK_STACK" = "1" ]; then
|
||||
export LD_LIBRARY_PATH="\$APPDIR/lib\${LD_LIBRARY_PATH:+:\$LD_LIBRARY_PATH}"
|
||||
export GST_PLUGIN_SYSTEM_PATH=""
|
||||
export GST_PLUGIN_PATH="\$APPDIR/lib/gstreamer-1.0\${GST_PLUGIN_PATH:+:\$GST_PLUGIN_PATH}"
|
||||
export GIO_EXTRA_MODULES="\$APPDIR/lib/gio/modules\${GIO_EXTRA_MODULES:+:\$GIO_EXTRA_MODULES}"
|
||||
export XDG_DATA_DIRS="\$APPDIR/share\${XDG_DATA_DIRS:+:\$XDG_DATA_DIRS}"
|
||||
|
||||
if [ -d "\$APPDIR/share/glib-2.0/schemas" ]; then
|
||||
export GSETTINGS_SCHEMA_DIR="\$APPDIR/share/glib-2.0/schemas"
|
||||
fi
|
||||
|
||||
if [ -x "\$APPDIR/libexec/gstreamer-1.0/gst-plugin-scanner" ]; then
|
||||
export GST_PLUGIN_SCANNER="\$APPDIR/libexec/gstreamer-1.0/gst-plugin-scanner"
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders" ]; then
|
||||
export GDK_PIXBUF_MODULEDIR="\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders"
|
||||
if [ -x "\$APPDIR/libexec/gdk-pixbuf-query-loaders" ]; then
|
||||
PIXBUF_CACHE_DIR="\${XDG_CACHE_HOME:-\$HOME/.cache}/@SLIC3R_APP_KEY@"
|
||||
PIXBUF_CACHE_FILE="\$PIXBUF_CACHE_DIR/gdk-pixbuf-loaders.cache"
|
||||
mkdir -p "\$PIXBUF_CACHE_DIR"
|
||||
if [ ! -s "\$PIXBUF_CACHE_FILE" ] || find "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders" -type f -newer "\$PIXBUF_CACHE_FILE" | grep -q .; then
|
||||
"\$APPDIR/libexec/gdk-pixbuf-query-loaders" "\$APPDIR/lib/gdk-pixbuf-2.0/2.10.0/loaders"/*.so > "\$PIXBUF_CACHE_FILE" 2>/dev/null || true
|
||||
fi
|
||||
if [ -s "\$PIXBUF_CACHE_FILE" ]; then
|
||||
export GDK_PIXBUF_MODULE_FILE="\$PIXBUF_CACHE_FILE"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/webkit2gtk-4.1" ]; then
|
||||
export WEBKIT_EXEC_PATH="\$APPDIR/lib/webkit2gtk-4.1"
|
||||
elif [ -d "\$APPDIR/lib/webkit2gtk-4.0" ]; then
|
||||
export WEBKIT_EXEC_PATH="\$APPDIR/lib/webkit2gtk-4.0"
|
||||
fi
|
||||
|
||||
if [ -d "\$APPDIR/lib/webkit2gtk-4.1/injected-bundle" ]; then
|
||||
export WEBKIT_INJECTED_BUNDLE_PATH="\$APPDIR/lib/webkit2gtk-4.1/injected-bundle"
|
||||
elif [ -d "\$APPDIR/lib/webkit2gtk-4.0/injected-bundle" ]; then
|
||||
export WEBKIT_INJECTED_BUNDLE_PATH="\$APPDIR/lib/webkit2gtk-4.0/injected-bundle"
|
||||
fi
|
||||
else
|
||||
if target_missing_runtime_library "\$TARGET_BIN" "libwebkit2gtk-4.1.so.0" || \
|
||||
target_missing_runtime_library "\$TARGET_BIN" "libjavascriptcoregtk-4.1.so.0" || \
|
||||
! has_host_runtime_library "libwebkit2gtk-4.1.so.0" || \
|
||||
! has_host_runtime_library "libjavascriptcoregtk-4.1.so.0"; then
|
||||
echo "Error: missing host WebKitGTK 4.1 runtime libraries." >&2
|
||||
echo "Install the distro package providing libwebkit2gtk-4.1.so.0 and libjavascriptcoregtk-4.1.so.0." >&2
|
||||
echo "On Arch/CachyOS, install: webkit2gtk-4.1" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "\$@"
|
||||
EOF
|
||||
chmod ug+x "$LIBEXEC_DIR/@SLIC3R_APP_CMD@-env"
|
||||
|
||||
# create bin
|
||||
cat << EOF >@SLIC3R_APP_CMD@
|
||||
#!/bin/bash
|
||||
DIR=\$(readlink -f "\$0" | xargs dirname)
|
||||
export LD_LIBRARY_PATH="\$DIR/bin:\$LD_LIBRARY_PATH"
|
||||
DIR=\$(dirname "\$(readlink -f "\$0")")
|
||||
export APPDIR="\${APPDIR:-\$DIR}"
|
||||
|
||||
# FIXME: OrcaSlicer segfault workarounds
|
||||
# 1) OrcaSlicer will segfault on systems where locale info is not as expected (i.e. Holo-ISO arch-based distro)
|
||||
@@ -76,38 +380,33 @@ if [ "\$XDG_SESSION_TYPE" = "wayland" ] && [ "\$ZINK_DISABLE_OVERRIDE" != "1" ];
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
exec "\$DIR/bin/@SLIC3R_APP_CMD@" "\$@"
|
||||
exec "\$DIR/libexec/@SLIC3R_APP_CMD@-env" "\$DIR/bin/@SLIC3R_APP_CMD@" "\$@"
|
||||
EOF
|
||||
|
||||
chmod ug+x @SLIC3R_APP_CMD@
|
||||
cp -fl @SLIC3R_APP_CMD@ package/@SLIC3R_APP_CMD@
|
||||
# Nothing uses this tar? Remove if nobody has complained and it's been a while since this comment added.
|
||||
# Original commit was https://github.com/OrcaSlicer/OrcaSlicer/commit/f5a4862da52fc68f77b5201ddf330a9800d83228
|
||||
# and it doesn't appear to have been used there either.
|
||||
#pushd package > /dev/null
|
||||
#tar -cvf ../@SLIC3R_APP_KEY@.tar . &>/dev/null
|
||||
#popd > /dev/null
|
||||
#} &> $ROOT/Build.log # Capture all command output
|
||||
cp -fl @SLIC3R_APP_CMD@ "$APPDIR/@SLIC3R_APP_CMD@"
|
||||
|
||||
if [ -x "${CHECK_SCRIPT}" ] && [ -z "${ORCA_SKIP_APPIMAGE_AUDIT}" ]; then
|
||||
"${CHECK_SCRIPT}" "$APPDIR" "$BIN_DIR/@SLIC3R_APP_CMD@"
|
||||
fi
|
||||
# } &> $ROOT/Build.log # Capture all command output
|
||||
echo "done"
|
||||
|
||||
if [[ -n "$BUILD_IMAGE" ]]
|
||||
then
|
||||
echo -n "Creating Appimage for distribution..."
|
||||
#{
|
||||
# AppImage script modifies files in place, so make a copy and clean up after.
|
||||
# {
|
||||
rm -rf package_appimage
|
||||
cp -Rf package package_appimage
|
||||
pushd package_appimage > /dev/null
|
||||
chmod +x ../build_appimage.sh
|
||||
if ../build_appimage.sh; then
|
||||
# Clean up on success.
|
||||
popd > /dev/null
|
||||
mv package_appimage/"@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage" "@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage"
|
||||
rm -fR package_appimage
|
||||
popd > /dev/null
|
||||
mv package_appimage/"@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage" "@SLIC3R_APP_KEY@_Linux_V@SoftFever_VERSION@.AppImage"
|
||||
rm -fR package_appimage
|
||||
else
|
||||
# Leave files on failure so you can debug.
|
||||
popd > /dev/null
|
||||
popd > /dev/null
|
||||
fi
|
||||
#} &> $ROOT/Build.log # Capture all command output
|
||||
# } &> $ROOT/Build.log # Capture all command output
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/uuid/detail/md5.hpp>
|
||||
#include <boost/algorithm/hex.hpp>
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Utils.hpp"
|
||||
@@ -615,6 +617,11 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
opt_dst->set(opt_src);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filament_id.empty()) {
|
||||
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
|
||||
}
|
||||
|
||||
temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string());
|
||||
} else if (!filament_id.empty() && inherits().empty()) {
|
||||
DynamicPrintConfig temp_config = config;
|
||||
@@ -1500,9 +1507,9 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map<std
|
||||
key_values[BBL_JSON_KEY_BASE_ID] = preset.base_id;
|
||||
} else {
|
||||
key_values.erase(BBL_JSON_KEY_BASE_ID);
|
||||
if (get_preset_base(preset) == &preset && !preset.filament_id.empty()) {
|
||||
key_values[BBL_JSON_KEY_FILAMENT_ID] = preset.filament_id;
|
||||
}
|
||||
}
|
||||
if (!preset.filament_id.empty()) {
|
||||
key_values[BBL_JSON_KEY_FILAMENT_ID] = preset.filament_id;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " uploading user preset name is: " << preset.name << "and create filament_id is: " << preset.filament_id
|
||||
<< " and base_id is: " << preset.base_id;
|
||||
@@ -2417,7 +2424,7 @@ std::map<std::string, std::vector<Preset const *>> PresetCollection::get_filamen
|
||||
std::map<std::string, std::vector<Preset const *>> filament_presets;
|
||||
for (auto &preset : m_presets) {
|
||||
if (preset.is_user()) {
|
||||
if (preset.inherits() == "") { filament_presets[preset.filament_id].push_back(&preset); }
|
||||
if (preset.inherits() == "" || !preset.filament_id.empty()) { filament_presets[preset.filament_id].push_back(&preset); }
|
||||
continue;
|
||||
}
|
||||
if (get_preset_base(preset) == &preset) { filament_presets[preset.filament_id].push_back(&preset); }
|
||||
@@ -2511,8 +2518,18 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
|
||||
preset.is_project_embedded = false;
|
||||
if (m_type == Preset::TYPE_PRINT)
|
||||
preset.config.option<ConfigOptionString>("print_settings_id", true)->value = new_name;
|
||||
else if (m_type == Preset::TYPE_FILAMENT)
|
||||
else if (m_type == Preset::TYPE_FILAMENT) {
|
||||
preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = new_name;
|
||||
|
||||
boost::uuids::detail::md5 hash;
|
||||
boost::uuids::detail::md5::digest_type digest;
|
||||
hash.process_bytes(new_name.data(), new_name.size());
|
||||
hash.get_digest(digest);
|
||||
const auto char_digest = reinterpret_cast<const char *>(&digest);
|
||||
std::string result;
|
||||
boost::algorithm::hex(char_digest, char_digest + sizeof(boost::uuids::detail::md5::digest_type), std::back_inserter(result));
|
||||
preset.filament_id = "P" + result.substr(0, 7);
|
||||
}
|
||||
else if (m_type == Preset::TYPE_PRINTER)
|
||||
preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = new_name;
|
||||
//BBS: add lock logic for sync preset in background
|
||||
|
||||
@@ -2307,7 +2307,7 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
|
||||
continue;
|
||||
}
|
||||
auto iter = std::find_if(filaments.begin(), filaments.end(),
|
||||
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
|
||||
[this, &filament_id](auto &f) { return f.is_compatible && (filaments.get_preset_base(f) == &f || (f.is_user() && !f.filament_id.empty())) && f.filament_id == filament_id; });
|
||||
if (iter == filaments.end()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
|
||||
auto filament_type = ams.opt_string("filament_type", 0u);
|
||||
@@ -2409,7 +2409,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
|
||||
auto filament_type = ams.opt_string("filament_type", 0u);
|
||||
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
|
||||
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
|
||||
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
|
||||
return f.is_compatible && (filaments.get_preset_base(f) == &f || (f.is_user() && !f.filament_id.empty())) && f.filament_id == filament_id; });
|
||||
if (iter == filaments.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
|
||||
if (!filament_type.empty()) {
|
||||
|
||||
@@ -274,6 +274,7 @@ AboutDialog::AboutDialog()
|
||||
text_list.push_back(_L("Open-source slicing stands on a tradition of collaboration and attribution. Slic3r, created by Alessandro Ranellucci and the RepRap community, laid the foundation. PrusaSlicer by Prusa Research built on that work, Bambu Studio forked from PrusaSlicer, and SuperSlicer extended it with community-driven enhancements. Each project carried the work of its predecessors forward, crediting those who came before."));
|
||||
text_list.push_back(_L("OrcaSlicer began in that same spirit, drawing from PrusaSlicer, BambuStudio, SuperSlicer, and CuraSlicer. But it has since grown far beyond its origins — introducing advanced calibration tools, precise wall and seam control and hundreds of other features."));
|
||||
text_list.push_back(_L("Today, OrcaSlicer is the most widely used and actively developed open-source slicer in the 3D printing community. Many of its innovations have been adopted by other slicers, making it a driving force for the entire industry."));
|
||||
text_list.push_back(_L("This build includes additional patches for KX-Bridge (Anycubic Kobra X Moonraker bridge). GNU AGPL-3.0: upstream https://github.com/SoftFever/OrcaSlicer + KX patches https://gitea.it-drui.de/viewit/OrcaSlicer-KX/src/branch/stable"));
|
||||
|
||||
text_sizer->Add( 0, 0, 0, wxTOP, FromDIP(33));
|
||||
bool is_zh = wxGetApp().app_config->get("language") == "zh_CN";
|
||||
@@ -293,7 +294,10 @@ AboutDialog::AboutDialog()
|
||||
find_txt += text_list[i][o];
|
||||
count_txt += text_list[i][o];
|
||||
} else {
|
||||
find_txt += std::string("\n") + text_list[i][o];
|
||||
// wxString::operator[] yields wxUniCharRef in wx 3.3.2, which has no
|
||||
// operator+ with std::string — append in two steps (matches dev branch).
|
||||
find_txt += "\n";
|
||||
find_txt += text_list[i][o];
|
||||
count_txt = text_list[i][o];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ void AuFile::on_input_enter(wxCommandEvent &evt)
|
||||
auto new_fullname = new_file_name + m_file_path.extension().string();
|
||||
|
||||
|
||||
wxString new_fullname_path = dir.wstring() + "/" + new_fullname;
|
||||
wxString new_fullname_path = wxString(dir.wstring()) + "/" + new_fullname;
|
||||
fs::path new_dir_path(new_fullname_path.ToStdWstring());
|
||||
|
||||
|
||||
@@ -955,7 +955,7 @@ void AuxiliaryPanel::on_import_file(wxCommandEvent &event)
|
||||
}
|
||||
|
||||
if (!is_exist) {
|
||||
dir_path += "/" + src_bfs_path.filename().generic_wstring();
|
||||
dir_path += wxString("/") + src_bfs_path.filename().generic_wstring();
|
||||
} else {
|
||||
time_t t1 = time(0);
|
||||
char ch1[64];
|
||||
@@ -965,7 +965,7 @@ void AuxiliaryPanel::on_import_file(wxCommandEvent &event)
|
||||
wxString name = src_bfs_path.filename().generic_wstring();
|
||||
auto before_name = replaceSpace(name.ToStdString(), src_bfs_path.extension().string(), "");
|
||||
time_text = replaceSpace(time_text, ":", "_");
|
||||
dir_path += "/" + before_name + "_" + time_text + src_bfs_path.extension().wstring();
|
||||
dir_path += wxString("/") + before_name + "_" + time_text + src_bfs_path.extension().wstring();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -334,7 +334,7 @@ wxDataViewItemArray AuxiliaryModel::ImportFile(AuxiliaryModelNode* sel, wxArrayS
|
||||
wxString dir_path = m_root_dir;
|
||||
if (sel != m_root)
|
||||
dir_path += "\\" + sel->name;
|
||||
dir_path += "\\" + src_bfs_path.filename().generic_wstring();
|
||||
dir_path += wxString("\\") + src_bfs_path.filename().generic_wstring();
|
||||
|
||||
boost::system::error_code ec;
|
||||
if (!fs::copy_file(src_bfs_path, fs::path(dir_path.ToStdWstring()), fs::copy_options::overwrite_existing, ec))
|
||||
|
||||
@@ -209,7 +209,7 @@ bool DesktopIntegrationDialog::is_integrated()
|
||||
if (path.empty())
|
||||
return false;
|
||||
|
||||
// confirmation that OrcaSlicer.desktop exists
|
||||
// confirmation that com.orcaslicer.OrcaSlicer.desktop exists
|
||||
struct stat buffer;
|
||||
return (stat (path.c_str(), &buffer) == 0);
|
||||
}
|
||||
@@ -342,13 +342,13 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
"StartupNotify=false\n"
|
||||
"StartupWMClass=orca-slicer\n", name_suffix, version_suffix, excutable_path);
|
||||
|
||||
std::string path = GUI::format("%1%/applications/OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
std::string path = GUI::format("%1%/applications/com.orcaslicer.OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(path, desktop_file)){
|
||||
BOOST_LOG_TRIVIAL(debug) << "OrcaSlicer.desktop file installation success.";
|
||||
BOOST_LOG_TRIVIAL(debug) << "com.orcaslicer.OrcaSlicer.desktop file installation success.";
|
||||
break;
|
||||
} else {
|
||||
// write failed - try another path
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to OrcaSlicer.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to com.orcaslicer.OrcaSlicer.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
target_dir_desktop.clear();
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
@@ -357,7 +357,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
create_path(into_u8(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
// create desktop file
|
||||
target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
|
||||
std::string path = GUI::format("%1%/applications/OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
std::string path = GUI::format("%1%/applications/com.orcaslicer.OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!create_desktop_file(path, desktop_file)) {
|
||||
// Desktop file not written - end desktop integration
|
||||
@@ -379,7 +379,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
return;
|
||||
}
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix));
|
||||
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/com.orcaslicer.OrcaSlicer%2%.desktop", target_dir_desktop, version_suffix));
|
||||
|
||||
// Repeat for Gcode viewer - use same paths as for slicer files
|
||||
// Do NOT add gcode viewer desktop file on ChromeOS
|
||||
|
||||
@@ -18,7 +18,7 @@ public:
|
||||
|
||||
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
|
||||
|
||||
// returns true if path to OrcaSlicer.desktop is stored in App Config and existence of desktop file.
|
||||
// returns true if path to com.orcaslicer.OrcaSlicer.desktop is stored in App Config and existence of desktop file.
|
||||
// Does not check if desktop file leads to this binary or existence of icons and viewer desktop file.
|
||||
static bool is_integrated();
|
||||
// true if appimage
|
||||
|
||||
@@ -612,9 +612,10 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
{
|
||||
curr_tray->remain = -1;
|
||||
}
|
||||
if (tray_it->contains("tray_slot_placeholder")) {
|
||||
curr_tray->is_slot_placeholder = true;
|
||||
}
|
||||
// Reset the placeholder flag every update so a slot that
|
||||
// was empty before but is now loaded does not stay marked
|
||||
// as a placeholder (and vice versa).
|
||||
curr_tray->is_slot_placeholder = tray_it->contains("tray_slot_placeholder");
|
||||
int ams_id_int = 0;
|
||||
int tray_id_int = 0;
|
||||
try
|
||||
@@ -638,6 +639,21 @@ void DevFilaSystemParser::ParseV1_0(const json& jj, MachineObject* obj, DevFilaS
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
// An empty slot (placeholder or exist-bit not set) must not
|
||||
// keep the previously loaded filament's name/type/color from
|
||||
// an earlier update. The Moonraker/AMS-Lite bridge reports
|
||||
// empty slots with a placeholder + cleared fields, but the
|
||||
// tray object is merged (not rebuilt) between updates, so the
|
||||
// stale values would otherwise survive and the slot would
|
||||
// render as loaded. Reset them explicitly.
|
||||
if (curr_tray->is_slot_placeholder || !curr_tray->is_exists)
|
||||
{
|
||||
curr_tray->setting_id = "";
|
||||
curr_tray->m_fila_type = "";
|
||||
curr_tray->color = "";
|
||||
curr_tray->sub_brands = "";
|
||||
curr_tray->cols.clear();
|
||||
}
|
||||
if (tray_it->contains("setting_id"))
|
||||
{
|
||||
curr_tray->filament_setting_id = (*tray_it)["setting_id"].get<std::string>();
|
||||
|
||||
@@ -461,7 +461,9 @@ unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
|
||||
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
||||
// wx 3.3.2: wxComboPopup no longer derives from wxObject, so wxDynamicCast
|
||||
// (which static_casts to wxObject*) is invalid here — use C++ dynamic_cast.
|
||||
wxCheckListBoxComboPopup* popup = dynamic_cast<wxCheckListBoxComboPopup*>(comboCtrl->GetPopupControl());
|
||||
if (popup != nullptr) {
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
|
||||
if (popup->IsChecked(i))
|
||||
@@ -474,7 +476,9 @@ unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
|
||||
|
||||
void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags)
|
||||
{
|
||||
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
||||
// wx 3.3.2: wxComboPopup no longer derives from wxObject, so wxDynamicCast
|
||||
// (which static_casts to wxObject*) is invalid here — use C++ dynamic_cast.
|
||||
wxCheckListBoxComboPopup* popup = dynamic_cast<wxCheckListBoxComboPopup*>(comboCtrl->GetPopupControl());
|
||||
if (popup != nullptr) {
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
|
||||
popup->Check(i, (flags & (1 << i)) != 0);
|
||||
|
||||
@@ -4313,8 +4313,11 @@ void GUI_App::force_colors_update()
|
||||
#ifdef _MSW_DARK_MODE
|
||||
#ifdef __WINDOWS__
|
||||
NppDarkMode::SetDarkMode(dark_mode());
|
||||
#if wxVERSION_NUMBER < 3300
|
||||
// wxToolTip::GetToolTipCtrl() became private in wxWidgets 3.3.
|
||||
if (WXHWND wxHWND = wxToolTip::GetToolTipCtrl())
|
||||
NppDarkMode::SetDarkExplorerTheme((HWND)wxHWND);
|
||||
#endif
|
||||
NppDarkMode::SetDarkTitleBar(mainframe->GetHWND());
|
||||
|
||||
|
||||
|
||||
@@ -430,7 +430,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
||||
// Always fill in the "printhost_port" combo box from the config and select it.
|
||||
{
|
||||
Choice* choice = dynamic_cast<Choice*>(m_optgroup->get_field("printhost_port"));
|
||||
choice->set_values({ m_config->opt_string("printhost_port") });
|
||||
choice->set_values(std::vector<std::string>{ m_config->opt_string("printhost_port") });
|
||||
choice->set_selection();
|
||||
}
|
||||
|
||||
|
||||
@@ -3258,7 +3258,8 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
|
||||
tray_config.set_key_value("slot_id", new ConfigOptionStrings{slot_id});
|
||||
tray_config.set_key_value("filament_type", new ConfigOptionStrings{tray.m_fila_type});
|
||||
tray_config.set_key_value("tray_name", new ConfigOptionStrings{ name });
|
||||
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX))});
|
||||
const std::string filament_color = into_u8(wxColour("#" + tray.color).GetAsString(wxC2S_HTML_SYNTAX));
|
||||
tray_config.set_key_value("filament_colour", new ConfigOptionStrings{filament_color});
|
||||
tray_config.set_key_value("filament_multi_colour", new ConfigOptionStrings{});
|
||||
tray_config.set_key_value("filament_colour_type", new ConfigOptionStrings{std::to_string(tray.ctype)});
|
||||
tray_config.set_key_value("filament_exist", new ConfigOptionBools{tray.is_exists});
|
||||
@@ -3271,12 +3272,19 @@ std::map<int, DynamicPrintConfig> Sidebar::build_filament_ams_list(MachineObject
|
||||
for (int i = 0; i < tray.cols.size(); ++i) {
|
||||
tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.push_back(into_u8(wxColour("#" + tray.cols[i]).GetAsString(wxC2S_HTML_SYNTAX)));
|
||||
}
|
||||
if (tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.empty() && !filament_color.empty()) {
|
||||
tray_config.opt<ConfigOptionStrings>("filament_multi_colour")->values.push_back(filament_color);
|
||||
}
|
||||
return tray_config;
|
||||
};
|
||||
|
||||
if (obj->ams_support_virtual_tray) {
|
||||
int extruder = 0x10000; // Main (first) extruder at right
|
||||
for (auto & vt_tray : obj->vt_slot) {
|
||||
if (!vt_tray.is_exists && vt_tray.setting_id.empty() && vt_tray.m_fila_type.empty() && vt_tray.color.empty()) {
|
||||
extruder = 0;
|
||||
continue;
|
||||
}
|
||||
filament_ams_list.emplace(extruder + stoi(vt_tray.id), build_tray_config(vt_tray, "Ext",vt_tray.id, "0"));//254 or 255
|
||||
extruder = 0;
|
||||
}
|
||||
@@ -3433,7 +3441,9 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
p->plater->pop_warning_and_go_to_device_page("", Plater::PrinterWarningType::EMPTY_FILAMENT, _L("Sync printer information"));
|
||||
return;
|
||||
}
|
||||
if (!wxGetApp().plater()->is_same_printer_for_connected_and_selected()) {
|
||||
auto* agent = wxGetApp().getDeviceManager()->get_agent();
|
||||
const bool direct_pull_sync = agent && agent->get_filament_sync_mode() == FilamentSyncMode::pull;
|
||||
if (!direct_pull_sync && !wxGetApp().plater()->is_same_printer_for_connected_and_selected()) {
|
||||
return;
|
||||
}
|
||||
std::string ams_filament_ids = wxGetApp().app_config->get("ams_filament_ids", p->ams_list_device);
|
||||
@@ -3441,31 +3451,39 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn)
|
||||
if (!ams_filament_ids.empty()) {
|
||||
boost::algorithm::split(list2, ams_filament_ids, boost::algorithm::is_any_of(","));
|
||||
}
|
||||
wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg
|
||||
SyncAmsInfoDialog::SyncInfo temp_info;
|
||||
temp_info.use_dialog_pos = false;
|
||||
temp_info.cancel_text_to_later = is_from_big_sync_btn;
|
||||
if (m_sync_dlg == nullptr) {
|
||||
m_sync_dlg = new SyncAmsInfoDialog(this, temp_info);
|
||||
SyncAmsInfoDialog::SyncResult sync_result;
|
||||
int dlg_res{(int) wxID_YES};
|
||||
if (direct_pull_sync) {
|
||||
sync_result.direct_sync = true;
|
||||
sync_result.is_same_printer = true;
|
||||
} else {
|
||||
m_sync_dlg->set_info(temp_info);
|
||||
}
|
||||
int dlg_res{(int) wxID_CANCEL};
|
||||
if (m_sync_dlg->is_need_show()) {
|
||||
m_sync_dlg->deal_only_exist_ext_spool(obj);
|
||||
if (m_sync_dlg->is_dirty_filament()) {
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true);
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
dynamic_filament_list.update();
|
||||
wxGetApp().plater()->update_all_plate_thumbnails(true);//preview thumbnail for sync_dlg
|
||||
SyncAmsInfoDialog::SyncInfo temp_info;
|
||||
temp_info.use_dialog_pos = false;
|
||||
temp_info.cancel_text_to_later = is_from_big_sync_btn;
|
||||
if (m_sync_dlg == nullptr) {
|
||||
m_sync_dlg = new SyncAmsInfoDialog(this, temp_info);
|
||||
} else {
|
||||
m_sync_dlg->set_info(temp_info);
|
||||
}
|
||||
dlg_res = (int) wxID_CANCEL;
|
||||
if (m_sync_dlg->is_need_show()) {
|
||||
m_sync_dlg->deal_only_exist_ext_spool(obj);
|
||||
if (m_sync_dlg->is_dirty_filament()) {
|
||||
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filament_presets[0], false, "", false, true);
|
||||
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
|
||||
dynamic_filament_list.update();
|
||||
}
|
||||
m_sync_dlg->set_check_dirty_fialment(false);
|
||||
dlg_res = m_sync_dlg->ShowModal();
|
||||
} else {
|
||||
dlg_res =(int) wxID_YES;
|
||||
}
|
||||
m_sync_dlg->set_check_dirty_fialment(false);
|
||||
dlg_res = m_sync_dlg->ShowModal();
|
||||
} else {
|
||||
dlg_res =(int) wxID_YES;
|
||||
}
|
||||
if (dlg_res == wxID_CANCEL)
|
||||
return;
|
||||
auto sync_result = m_sync_dlg->get_result();
|
||||
if (!direct_pull_sync)
|
||||
sync_result = m_sync_dlg->get_result();
|
||||
if (!sync_result.is_same_printer) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "check error: sync_result.is_same_printer value is false";
|
||||
return;
|
||||
@@ -12110,7 +12128,7 @@ void Plater::import_model_id(wxString download_info)
|
||||
/* load project */
|
||||
// Orca: If download is a zip file, treat it as if file has been drag and dropped on the plater
|
||||
if (target_path.extension() == ".zip")
|
||||
this->load_files(wxArrayString(1, target_path.string()));
|
||||
{ wxString s(target_path.string()); this->load_files(wxArrayString(1, &s)); }
|
||||
else
|
||||
this->load_project(target_path.wstring());
|
||||
/*BBS set project info after load project, project info is reset in load project */
|
||||
|
||||
@@ -284,7 +284,7 @@ wxBoxSizer *PreferencesDialog::create_item_language_combobox(wxString title, wxS
|
||||
if (combobox->GetSelection() == m_current_language_selected)
|
||||
return;
|
||||
|
||||
if (e.GetString().mb_str() != app_config->get(param)) {
|
||||
if (e.GetString().ToStdString() != app_config->get(param)) {
|
||||
{
|
||||
//check if the project has changed
|
||||
if (wxGetApp().plater()->is_project_dirty()) {
|
||||
|
||||
@@ -256,6 +256,11 @@ int PresetComboBox::update_ams_color()
|
||||
auto color_pack = static_cast<ConfigOptionStrings *>(cfg->option("filament_multi_colour")->clone()); // multi color (all colors in all kinds of filament)
|
||||
auto color_type = static_cast<ConfigOptionStrings*>(cfg->option("filament_colour_type")->clone()); // color type
|
||||
|
||||
if (m_filament_idx >= color_head->values.size()) color_head->values.resize(m_filament_idx + 1);
|
||||
if (m_filament_idx >= color_pack->values.size()) color_pack->values.resize(m_filament_idx + 1);
|
||||
if (m_filament_idx >= color_type->values.size()) color_type->values.resize(m_filament_idx + 1);
|
||||
if (ctype.empty()) ctype = "1";
|
||||
|
||||
color_head->values[m_filament_idx] = color;
|
||||
color_type->values[m_filament_idx] = ctype;
|
||||
std::string color_str = ""; // Translate multi color info to config storage format
|
||||
|
||||
@@ -213,7 +213,7 @@ bool OptionsSearcher::search(const std::string &search, bool force /* = false*/,
|
||||
};
|
||||
|
||||
auto get_tooltip = [this, &sep](const Option &opt) {
|
||||
return marker_by_type(opt.type, printer_technology) + opt.category_local + sep + opt.group_local + sep + opt.label_local;
|
||||
return wxString(1, marker_by_type(opt.type, printer_technology)) + opt.category_local + sep + opt.group_local + sep + opt.label_local;
|
||||
};
|
||||
|
||||
std::vector<uint16_t> matches, matches2;
|
||||
|
||||
@@ -449,7 +449,7 @@ PrintParams SendMultiMachinePage::request_params(MachineObject* obj)
|
||||
bool timelapse = app_config->get("print", "timelapse") == "1" ? true : false;
|
||||
auto use_ams = false;
|
||||
|
||||
AmsRadioSelectorList::Node* node = m_radio_group.GetFirst();
|
||||
AmsRadioSelectorList::compatibility_iterator node = m_radio_group.GetFirst();
|
||||
auto groupid = 0;
|
||||
|
||||
|
||||
@@ -938,7 +938,7 @@ wxBoxSizer* SendMultiMachinePage::create_item_radiobox(wxString title, wxWindow*
|
||||
|
||||
void SendMultiMachinePage::OnSelectRadio(wxMouseEvent& event)
|
||||
{
|
||||
AmsRadioSelectorList::Node* node = m_radio_group.GetFirst();
|
||||
AmsRadioSelectorList::compatibility_iterator node = m_radio_group.GetFirst();
|
||||
auto groupid = 0;
|
||||
|
||||
//while (node) {
|
||||
@@ -975,7 +975,7 @@ void SendMultiMachinePage::OnSelectRadio(wxMouseEvent& event)
|
||||
|
||||
void SendMultiMachinePage::on_select_radio(std::string param)
|
||||
{
|
||||
AmsRadioSelectorList::Node* node = m_radio_group.GetFirst();
|
||||
AmsRadioSelectorList::compatibility_iterator node = m_radio_group.GetFirst();
|
||||
auto groupid = 0;
|
||||
|
||||
while (node) {
|
||||
@@ -995,7 +995,7 @@ void SendMultiMachinePage::on_select_radio(std::string param)
|
||||
|
||||
bool SendMultiMachinePage::get_value_radio(std::string param)
|
||||
{
|
||||
AmsRadioSelectorList::Node* node = m_radio_group.GetFirst();
|
||||
AmsRadioSelectorList::compatibility_iterator node = m_radio_group.GetFirst();
|
||||
auto groupid = 0;
|
||||
while (node) {
|
||||
AmsRadioSelector* rs = node->GetData();
|
||||
|
||||
@@ -1079,7 +1079,7 @@ void SyncAmsInfoDialog::check_empty_project()
|
||||
if (!temp_plate->get_objects_on_this_plate().empty()) {
|
||||
if (m_is_empty_project) { m_is_empty_project = false; }
|
||||
if (i < 9) {
|
||||
m_plate_number_choices_str.Add("0" + std::to_wstring(i + 1));
|
||||
m_plate_number_choices_str.Add(L"0" + std::to_wstring(i + 1));
|
||||
}
|
||||
else if (i == 9) {
|
||||
m_plate_number_choices_str.Add("10");
|
||||
|
||||
@@ -44,7 +44,7 @@ bool TabButton::Create(wxWindow *parent, wxString text, ScalableBitmap &bmp, lon
|
||||
{
|
||||
StaticBox::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style);
|
||||
newtag_img = ScalableBitmap(this, "monitor_hms_new",7);
|
||||
state_handler.attach({&text_color, &border_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&text_color, &border_color});
|
||||
state_handler.update_binds();
|
||||
//BBS set default font
|
||||
SetFont(Label::Body_14);
|
||||
|
||||
@@ -69,7 +69,13 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag,
|
||||
auto it = ams->GetTrays().find(std::to_string(i));
|
||||
Caninfo info;
|
||||
// tray is exists
|
||||
if (it != ams->GetTrays().end() && it->second->is_exists) {
|
||||
// A placeholder slot is explicitly empty (reported by the Moonraker/AMS-Lite
|
||||
// bridge): treat it as empty regardless of any stale is_exists/filament data
|
||||
// left over from a previous update, so it renders grey at its position
|
||||
// instead of showing the previously loaded filament.
|
||||
const bool tray_present = it != ams->GetTrays().end() && it->second->is_exists
|
||||
&& !it->second->is_slot_placeholder;
|
||||
if (tray_present) {
|
||||
if (it->second->is_tray_info_ready()) {
|
||||
info.can_id = it->second->id;
|
||||
info.ctype = it->second->ctype;
|
||||
|
||||
@@ -56,7 +56,7 @@ AxisCtrlButton::AxisCtrlButton(wxWindow *parent, ScalableBitmap &icon, long stly
|
||||
inner_background_color.append(BUTTON_IN_BG_COL, StateColor::Normal);
|
||||
inner_background_color.append(BUTTON_IN_BG_COL, StateColor::Enabled);
|
||||
|
||||
state_handler.attach({ &border_color, &background_color });
|
||||
state_handler.attach(std::vector<StateColor const*>{ &border_color, &background_color });
|
||||
state_handler.update_binds();
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int i
|
||||
bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id)
|
||||
{
|
||||
StaticBox::Create(parent, btn_id, wxDefaultPosition, wxDefaultSize, style);
|
||||
state_handler.attach({&text_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&text_color});
|
||||
state_handler.update_binds();
|
||||
//BBS set default font
|
||||
SetFont(Label::Body_14);
|
||||
|
||||
@@ -57,7 +57,7 @@ void DropDown::Create(wxWindow *parent, long style)
|
||||
PopupWindow::Create(parent, wxPU_CONTAINS_CONTROLS);
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
state_handler.attach({&border_color, &text_color, &selector_border_color, &selector_background_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&border_color, &text_color, &selector_border_color, &selector_background_color});
|
||||
state_handler.update_binds();
|
||||
if ((style & DD_NO_CHECK_ICON) == 0)
|
||||
check_bitmap = ScalableBitmap(this, "checked", 16);
|
||||
|
||||
@@ -66,7 +66,7 @@ bool LabeledStaticBox::Create(
|
||||
PickDC(dc);
|
||||
}));
|
||||
|
||||
state_handler.attach({&text_color, &background_color, &border_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&text_color, &background_color, &border_color});
|
||||
state_handler.update_binds();
|
||||
#ifndef __WXOSX__
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
@@ -45,7 +45,7 @@ SideButton::SideButton(wxWindow* parent, wxString text, wxString icon, long stly
|
||||
|
||||
SetBottomColour(wxColour("#3B4446"));
|
||||
|
||||
state_handler.attach({ &border_color, &text_color, &background_color });
|
||||
state_handler.attach(std::vector<StateColor const*>{ &border_color, &text_color, &background_color });
|
||||
state_handler.update_binds();
|
||||
|
||||
// icon only
|
||||
|
||||
@@ -55,7 +55,7 @@ void SpinInput::Create(wxWindow *parent,
|
||||
StaticBox::Create(parent, wxID_ANY, pos, size);
|
||||
SetFont(Label::Body_12);
|
||||
wxWindow::SetLabel(label);
|
||||
state_handler.attach({&label_color, &text_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&label_color, &text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {20, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER, wxTextValidator(wxFILTER_DIGITS));
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
|
||||
@@ -40,7 +40,7 @@ bool StaticBox::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, cons
|
||||
if (style & wxBORDER_NONE)
|
||||
border_width = 0;
|
||||
wxWindow::Create(parent, id, pos, size, style);
|
||||
state_handler.attach({&border_color, &background_color, &background_color2});
|
||||
state_handler.attach(std::vector<StateColor const*>{&border_color, &background_color, &background_color2});
|
||||
state_handler.update_binds();
|
||||
SetBackgroundColour(GetParentBackgroundColor(parent));
|
||||
return true;
|
||||
|
||||
@@ -84,7 +84,7 @@ void TempInput::Create(wxWindow *parent, wxString text, wxString label, wxString
|
||||
StaticBox::Create(parent, wxID_ANY, pos, size, style);
|
||||
wxWindow::SetLabel(label);
|
||||
style &= ~wxALIGN_CENTER_HORIZONTAL;
|
||||
state_handler.attach({&label_color, &text_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&label_color, &text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new wxTextCtrl(this, wxID_ANY, text, {5, 5}, wxDefaultSize, wxTE_PROCESS_ENTER | wxBORDER_NONE, wxTextValidator(wxFILTER_NUMERIC), wxTextCtrlNameStr);
|
||||
text_ctrl->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
||||
|
||||
@@ -57,7 +57,7 @@ void TextInput::Create(wxWindow * parent,
|
||||
wxWindow::SetLabel(label);
|
||||
assert((style & wxRIGHT) == 0);
|
||||
style &= ~wxALIGN_MASK;
|
||||
state_handler.attach({&label_color, & text_color});
|
||||
state_handler.attach(std::vector<StateColor const*>{&label_color, & text_color});
|
||||
state_handler.update_binds();
|
||||
text_ctrl = new TextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER);
|
||||
text_ctrl->SetFont(Label::Body_14);
|
||||
|
||||
@@ -331,7 +331,12 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
|
||||
bool WebView::CheckWebViewRuntime()
|
||||
{
|
||||
wxWebViewFactoryEdge factory;
|
||||
#if wxVERSION_NUMBER >= 3300
|
||||
// wxWidgets 3.3 requires a wxVersionContext argument for GetVersionInfo().
|
||||
auto wxVersion = factory.GetVersionInfo(wxVersionContext::RunTime);
|
||||
#else
|
||||
auto wxVersion = factory.GetVersionInfo();
|
||||
#endif
|
||||
return wxVersion.GetMajor() != 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
|
||||
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
|
||||
#include "slic3r/GUI/DeviceCore/DevManager.h"
|
||||
#include "../GUI/DeviceCore/DevStorage.h"
|
||||
#include "../GUI/DeviceCore/DevFirmware.h"
|
||||
@@ -88,6 +89,146 @@ std::string map_moonraker_state(std::string state)
|
||||
return "IDLE";
|
||||
}
|
||||
|
||||
std::string normalize_filament_name_for_match(const std::string& input)
|
||||
{
|
||||
std::string normalized = input;
|
||||
boost::trim(normalized);
|
||||
// Ignore profile suffixes like " @0.4 nozzle" for name matching.
|
||||
if (const auto suffix_pos = normalized.find(" @"); suffix_pos != std::string::npos) {
|
||||
normalized = normalized.substr(0, suffix_pos);
|
||||
}
|
||||
// Remove non-name symbols (e.g. trademark signs) while preserving separators
|
||||
// commonly used in filament names.
|
||||
std::string cleaned;
|
||||
cleaned.reserve(normalized.size());
|
||||
for (unsigned char c : normalized) {
|
||||
if (std::isalnum(c) || c == '-' || c == '+' || c == '/' || std::isspace(c)) {
|
||||
cleaned.push_back(static_cast<char>(std::toupper(c)));
|
||||
} else {
|
||||
cleaned.push_back(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse repeated whitespace.
|
||||
std::string collapsed;
|
||||
collapsed.reserve(cleaned.size());
|
||||
bool prev_space = true;
|
||||
for (unsigned char c : cleaned) {
|
||||
if (std::isspace(c)) {
|
||||
if (!prev_space) {
|
||||
collapsed.push_back(' ');
|
||||
}
|
||||
prev_space = true;
|
||||
} else {
|
||||
collapsed.push_back(static_cast<char>(c));
|
||||
prev_space = false;
|
||||
}
|
||||
}
|
||||
boost::trim(collapsed);
|
||||
return collapsed;
|
||||
}
|
||||
|
||||
bool filament_name_match_relaxed(const std::string& wanted, const std::string& candidate)
|
||||
{
|
||||
if (wanted == candidate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow lane names with trailing color descriptors, e.g.:
|
||||
// "ELEGOO RAPID PETG GREY" -> "ELEGOO RAPID PETG".
|
||||
if (!candidate.empty() && boost::starts_with(wanted, candidate + " ")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> vendor_match_candidates(std::string vendor)
|
||||
{
|
||||
std::vector<std::string> candidates;
|
||||
boost::trim(vendor);
|
||||
if (vendor.empty()) {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
candidates.push_back(vendor);
|
||||
|
||||
// Also try first token (e.g. "Bambu Lab" -> "Bambu") without hardcoded aliases.
|
||||
const auto first_space = vendor.find_first_of(" \t");
|
||||
if (first_space != std::string::npos) {
|
||||
std::string first = vendor.substr(0, first_space);
|
||||
boost::trim(first);
|
||||
if (!first.empty() && !boost::iequals(first, vendor)) {
|
||||
candidates.push_back(first);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
std::string filament_id_by_name(const Slic3r::PresetCollection& filaments,
|
||||
const std::string& filament_name,
|
||||
const std::vector<std::string>& vendor_filters = {})
|
||||
{
|
||||
if (filament_name.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher received empty filament name";
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::string wanted = normalize_filament_name_for_match(filament_name);
|
||||
std::vector<std::string> normalized_vendor_filters;
|
||||
normalized_vendor_filters.reserve(vendor_filters.size());
|
||||
for (const auto& vendor_filter : vendor_filters) {
|
||||
const std::string normalized_vendor = normalize_filament_name_for_match(vendor_filter);
|
||||
if (!normalized_vendor.empty()) {
|
||||
normalized_vendor_filters.push_back(normalized_vendor);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher lookup requested='" << filament_name
|
||||
<< "' normalized='" << wanted << "' vendor_filters=" << normalized_vendor_filters.size();
|
||||
for (size_t i = 0; i < filaments.size(); ++i) {
|
||||
const auto& preset = filaments.preset(i);
|
||||
if (!preset.is_visible || !preset.is_compatible || preset.filament_id.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name
|
||||
<< "' visible=" << preset.is_visible << " compatible=" << preset.is_compatible
|
||||
<< " filament_id_empty=" << preset.filament_id.empty();
|
||||
continue;
|
||||
}
|
||||
// Skip the vendor filter for user presets: a preset derived from a
|
||||
// vendor-specific parent (e.g. "Anycubic PLA Matte @...") resolves
|
||||
// filament_vendor from the PARENT ("Anycubic"), not from the user
|
||||
// preset itself ("Tinmorry"), so the filter would wrongly drop it and
|
||||
// the AMS sync falls back to the vendor preset (Issue #52). The strict
|
||||
// name match below is discriminating enough for user presets.
|
||||
if (!preset.is_user() && !normalized_vendor_filters.empty()) {
|
||||
const std::string preset_vendor = normalize_filament_name_for_match(preset.config.opt_string("filament_vendor", 0u));
|
||||
bool vendor_match = false;
|
||||
for (const auto& vendor_filter : normalized_vendor_filters) {
|
||||
if (preset_vendor == vendor_filter) {
|
||||
vendor_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vendor_match) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher skip preset='" << preset.name
|
||||
<< "' reason=vendor_filter_miss preset_vendor='" << preset_vendor << "'";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const std::string candidate = normalize_filament_name_for_match(preset.name);
|
||||
BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: filament matcher compare preset='" << preset.name
|
||||
<< "' normalized='" << candidate << "' filament_id='" << preset.filament_id << "'";
|
||||
if (filament_name_match_relaxed(wanted, candidate)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher matched requested='" << filament_name
|
||||
<< "' normalized='" << wanted << "' to preset='" << preset.name
|
||||
<< "' filament_id='" << preset.filament_id << "'";
|
||||
return preset.filament_id;
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: filament matcher found no match for requested='" << filament_name
|
||||
<< "' normalized='" << wanted << "'";
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -448,7 +589,7 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn)
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays)
|
||||
void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index)
|
||||
{
|
||||
|
||||
// Look up MachineObject via DeviceManager
|
||||
@@ -499,6 +640,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
|
||||
tray_json["tray_info_idx"] = tray->tray_info_idx;
|
||||
tray_json["tray_type"] = tray->tray_type;
|
||||
tray_json["tray_sub_brands"] = tray->tray_sub_brands.empty() ? tray->tray_type : tray->tray_sub_brands;
|
||||
tray_json["tray_color"] = normalize_color_value(tray->tray_color);
|
||||
|
||||
// Add temperature data if provided
|
||||
@@ -530,6 +672,11 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
ams_json["ams"] = ams_array;
|
||||
ams_json["ams_exist_bits"] = ams_exist_ss.str();
|
||||
ams_json["tray_exist_bits"] = tray_exist_ss.str();
|
||||
if (active_lane_index >= 0) {
|
||||
const std::string active_lane = std::to_string(active_lane_index);
|
||||
ams_json["tray_now"] = active_lane;
|
||||
ams_json["tray_tar"] = active_lane;
|
||||
}
|
||||
|
||||
// Wrap in the expected structure for ParseV1_0
|
||||
nlohmann::json print_json = nlohmann::json::object();
|
||||
@@ -537,6 +684,7 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
|
||||
// Call the parser to populate DevFilaSystem
|
||||
DevFilaSystemParser::ParseV1_0(print_json, obj, obj->GetFilaSystem(), false);
|
||||
ExtderSystemParser::ParseV1_0(print_json, obj->GetExtderSystem());
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::build_ams_payload: Parsed " << trays.size() << " trays";
|
||||
|
||||
// Set printer_type so update_sync_status() can match it against the preset's printer type.
|
||||
@@ -568,26 +716,46 @@ void MoonrakerPrinterAgent::build_ams_payload(int ams_count, int max_lane_index,
|
||||
|
||||
bool MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id)
|
||||
{
|
||||
std::vector<AmsTrayData> trays;
|
||||
int max_lane_index = 0;
|
||||
auto count_loaded = [](const std::vector<AmsTrayData>& trays) {
|
||||
int loaded = 0;
|
||||
for (const auto& tray : trays) {
|
||||
if (tray.has_filament) {
|
||||
++loaded;
|
||||
}
|
||||
}
|
||||
return loaded;
|
||||
};
|
||||
|
||||
// Try Moonraker filament data (more generic, supports any filament changer
|
||||
// software that reports lane data to Moonraker like AFC and recent Happy
|
||||
// Hare as of Feb 15, 2026)
|
||||
if (fetch_moonraker_filament_data(trays, max_lane_index)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Moonraker filament system with "
|
||||
<< (max_lane_index + 1) << " lanes";
|
||||
int ams_count = (max_lane_index + 4) / 4;
|
||||
build_ams_payload(ams_count, max_lane_index, trays);
|
||||
// Query both sources. The Moonraker lane_data namespace (AFC and recent Happy
|
||||
// Hare as of Feb 15, 2026) is more generic, but it can be stale or only
|
||||
// partially populated, while the Happy Hare mmu object carries the full gate
|
||||
// inventory (or vice versa). Pick whichever source reports more loaded slots
|
||||
// so a sparse database can't shadow a fully-populated changer.
|
||||
std::vector<AmsTrayData> moonraker_trays;
|
||||
int moonraker_max_lane = 0;
|
||||
const bool moonraker_ok = fetch_moonraker_filament_data(moonraker_trays, moonraker_max_lane);
|
||||
const int moonraker_loaded = moonraker_ok ? count_loaded(moonraker_trays) : 0;
|
||||
|
||||
std::vector<AmsTrayData> hh_trays;
|
||||
int hh_max_lane = 0;
|
||||
int hh_active_lane = -1; // KX/stable: 3-arg signature from PR #13719
|
||||
const bool hh_ok = fetch_hh_filament_info(hh_trays, hh_max_lane, hh_active_lane);
|
||||
const int hh_loaded = hh_ok ? count_loaded(hh_trays) : 0;
|
||||
|
||||
// Prefer Moonraker lane_data on ties to keep the original priority.
|
||||
if (moonraker_ok && moonraker_loaded >= hh_loaded) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Using Moonraker filament data with "
|
||||
<< moonraker_loaded << " loaded of " << (moonraker_max_lane + 1) << " lanes";
|
||||
int ams_count = (moonraker_max_lane + 4) / 4;
|
||||
build_ams_payload(ams_count, moonraker_max_lane, moonraker_trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Attempt Happy Hare first (more widely adopted, supports more filament changers)
|
||||
if (fetch_hh_filament_info(trays, max_lane_index)) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Detected Happy Hare MMU with "
|
||||
<< (max_lane_index + 1) << " gates";
|
||||
int ams_count = (max_lane_index + 4) / 4;
|
||||
build_ams_payload(ams_count, max_lane_index, trays);
|
||||
if (hh_ok) {
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info: Using Happy Hare MMU data with "
|
||||
<< hh_loaded << " loaded of " << (hh_max_lane + 1) << " gates";
|
||||
int ams_count = (hh_max_lane + 4) / 4;
|
||||
build_ams_payload(ams_count, hh_max_lane, hh_trays);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -776,6 +944,7 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
|
||||
const auto& value = json["result"]["value"];
|
||||
trays.clear();
|
||||
max_lane_index = 0;
|
||||
bool has_populated_lane = false;
|
||||
|
||||
for (const auto& [lane_key, lane_obj] : value.items()) {
|
||||
if (!lane_obj.is_object()) {
|
||||
@@ -801,15 +970,60 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
|
||||
tray.slot_index = lane_index;
|
||||
tray.tray_color = safe_json_string(lane_obj, "color");
|
||||
tray.tray_type = safe_json_string(lane_obj, "material");
|
||||
const std::string lane_name = safe_json_string(lane_obj, "name");
|
||||
const std::string lane_vendor = safe_json_string(lane_obj, "vendor_name");
|
||||
tray.tray_sub_brands = lane_name;
|
||||
tray.bed_temp = safe_json_int(lane_obj, "bed_temp");
|
||||
tray.nozzle_temp = safe_json_int(lane_obj, "nozzle_temp");
|
||||
tray.has_filament = !tray.tray_type.empty();
|
||||
has_populated_lane = has_populated_lane || tray.has_filament;
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
tray.tray_info_idx = bundle
|
||||
? bundle->filaments.filament_id_by_type(tray.tray_type)
|
||||
: map_filament_type_to_generic_id(tray.tray_type);
|
||||
if (bundle) {
|
||||
const auto vendor_candidates = vendor_match_candidates(lane_vendor);
|
||||
auto match_with_vendor_prefix = [&](const std::string& suffix) -> std::string {
|
||||
if (suffix.empty()) {
|
||||
return "";
|
||||
}
|
||||
for (const auto& vendor_candidate : vendor_candidates) {
|
||||
const std::string requested = vendor_candidate + " " + suffix;
|
||||
std::string match_id = filament_id_by_name(bundle->filaments, requested, vendor_candidates);
|
||||
if (!match_id.empty()) {
|
||||
return match_id;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
max_lane_index = std::max(max_lane_index, lane_index);
|
||||
// Prefer the most specific lane identity first, then broader vendor/material mapping.
|
||||
tray.tray_info_idx = match_with_vendor_prefix(lane_name);
|
||||
if (tray.tray_info_idx.empty()) {
|
||||
tray.tray_info_idx = match_with_vendor_prefix(tray.tray_type);
|
||||
}
|
||||
if (tray.tray_info_idx.empty() && !lane_name.empty()) {
|
||||
tray.tray_info_idx = filament_id_by_name(bundle->filaments, lane_name, vendor_candidates);
|
||||
}
|
||||
if (tray.tray_info_idx.empty()) {
|
||||
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
|
||||
<< "' index=" << lane_index << " material='" << tray.tray_type
|
||||
<< "' vendor='" << lane_vendor << "' vendor_candidates=" << vendor_candidates.size()
|
||||
<< "' name='" << lane_name
|
||||
<< "' mapped_by='preset_bundle' tray_info_idx='" << tray.tray_info_idx << "'";
|
||||
} else {
|
||||
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
|
||||
BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_moonraker_filament_data: lane='" << lane_key
|
||||
<< "' index=" << lane_index << " material='" << tray.tray_type
|
||||
<< "' mapped_by='generic_fallback' tray_info_idx='" << tray.tray_info_idx << "'";
|
||||
}
|
||||
|
||||
// Only advance max_lane_index for lanes that actually hold filament so trailing
|
||||
// empty lanes are trimmed. Interior empty lanes (e.g. lane 0 empty, lane 1 loaded,
|
||||
// lane 2 empty, lane 3 loaded) stay below the highest filled index and are still
|
||||
// emitted as empty placeholder slots by build_ams_payload.
|
||||
if (tray.has_filament) {
|
||||
max_lane_index = std::max(max_lane_index, lane_index);
|
||||
}
|
||||
trays.push_back(tray);
|
||||
}
|
||||
|
||||
@@ -818,11 +1032,15 @@ bool MoonrakerPrinterAgent::fetch_moonraker_filament_data(std::vector<AmsTrayDat
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!has_populated_lane) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fetch filament info from Happy Hare MMU
|
||||
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index)
|
||||
bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index)
|
||||
{
|
||||
// Query Happy Hare MMU status
|
||||
std::string url = join_url(device_info.base_url, "/printer/objects/query?mmu");
|
||||
@@ -894,8 +1112,18 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
|
||||
// Get arrays
|
||||
const auto& gate_status = mmu.contains("gate_status") ? mmu["gate_status"] : nlohmann::json::array();
|
||||
const auto& gate_material = mmu.contains("gate_material") ? mmu["gate_material"] : nlohmann::json::array();
|
||||
const auto& gate_filament_name = mmu.contains("gate_filament_name") ? mmu["gate_filament_name"] : nlohmann::json::array();
|
||||
const auto& gate_color = mmu.contains("gate_color") ? mmu["gate_color"] : nlohmann::json::array();
|
||||
const auto& gate_temperature = mmu.contains("gate_temperature") ? mmu["gate_temperature"] : nlohmann::json::array();
|
||||
const int active_gate = safe_json_int(mmu, "gate");
|
||||
active_lane_index = active_gate;
|
||||
std::string active_filament_name;
|
||||
if (mmu.contains("active_filament") && mmu["active_filament"].is_object()) {
|
||||
active_filament_name = safe_json_string(mmu["active_filament"], "filament_name");
|
||||
if (active_filament_name.empty()) {
|
||||
active_filament_name = safe_json_string(mmu["active_filament"], "material");
|
||||
}
|
||||
}
|
||||
|
||||
if (!gate_status.is_array() || !gate_material.is_array() ||
|
||||
!gate_color.is_array() || !gate_temperature.is_array()) {
|
||||
@@ -916,8 +1144,13 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
|
||||
|
||||
// Extract gate data
|
||||
std::string material = safe_array_string(gate_material, gate_idx);
|
||||
std::string filament_name = safe_array_string(gate_filament_name, gate_idx);
|
||||
std::string color = safe_array_string(gate_color, gate_idx);
|
||||
int nozzle_temp = safe_array_int(gate_temperature, gate_idx);
|
||||
// For the active gate, prefer active_filament from MMU state.
|
||||
if (gate_idx == active_gate && !active_filament_name.empty()) {
|
||||
filament_name = active_filament_name;
|
||||
}
|
||||
|
||||
// Skip if no material type (empty gate)
|
||||
if (material.empty()) {
|
||||
@@ -927,15 +1160,21 @@ bool MoonrakerPrinterAgent::fetch_hh_filament_info(std::vector<AmsTrayData>& tra
|
||||
AmsTrayData tray;
|
||||
tray.slot_index = gate_idx;
|
||||
tray.tray_type = material;
|
||||
tray.tray_sub_brands = filament_name;
|
||||
tray.tray_color = color;
|
||||
tray.nozzle_temp = nozzle_temp;
|
||||
tray.bed_temp = 0; // HH doesn't provide bed temp in gate arrays
|
||||
tray.has_filament = true;
|
||||
|
||||
auto* bundle = GUI::wxGetApp().preset_bundle;
|
||||
tray.tray_info_idx = bundle
|
||||
? bundle->filaments.filament_id_by_type(tray.tray_type)
|
||||
: map_filament_type_to_generic_id(tray.tray_type);
|
||||
if (bundle) {
|
||||
tray.tray_info_idx = filament_id_by_name(bundle->filaments, filament_name);
|
||||
if (tray.tray_info_idx.empty()) {
|
||||
tray.tray_info_idx = bundle->filaments.filament_id_by_type(tray.tray_type);
|
||||
}
|
||||
} else {
|
||||
tray.tray_info_idx = map_filament_type_to_generic_id(tray.tray_type);
|
||||
}
|
||||
|
||||
max_lane_index = std::max(max_lane_index, gate_idx);
|
||||
trays.push_back(tray);
|
||||
|
||||
@@ -92,6 +92,7 @@ protected:
|
||||
int slot_index = 0; // 0-based slot index
|
||||
bool has_filament = false;
|
||||
std::string tray_type; // Material type (e.g., "PLA", "ASA")
|
||||
std::string tray_sub_brands; // Human-readable filament name
|
||||
std::string tray_color; // Raw color (#RRGGBB, 0xRRGGBB, or RRGGBBAA)
|
||||
std::string tray_info_idx; // Setting ID (optional)
|
||||
int bed_temp = 0; // Optional
|
||||
@@ -99,7 +100,7 @@ protected:
|
||||
};
|
||||
|
||||
// Build ams JSON and call parser
|
||||
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays);
|
||||
void build_ams_payload(int ams_count, int max_lane_index, const std::vector<AmsTrayData>& trays, int active_lane_index = -1);
|
||||
|
||||
// Methods that derived classes may need to override or access
|
||||
virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl);
|
||||
@@ -161,7 +162,7 @@ private:
|
||||
uint64_t generation);
|
||||
|
||||
// System-specific filament fetch methods
|
||||
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||
bool fetch_hh_filament_info(std::vector<AmsTrayData>& trays, int& max_lane_index, int& active_lane_index);
|
||||
bool fetch_moonraker_filament_data(std::vector<AmsTrayData>& trays, int& max_lane_index);
|
||||
|
||||
// JSON helper methods
|
||||
|
||||
117
tools/verify_build.sh
Executable file
117
tools/verify_build.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prüft ob ein OrcaSlicer-KX Build-Artefakt die erwarteten Strings enthält.
|
||||
# Verwendung:
|
||||
# ./tools/verify_build.sh linux <path/to/OrcaSlicer.AppImage>
|
||||
# ./tools/verify_build.sh windows <path/to/OrcaSlicer-package.zip>
|
||||
#
|
||||
# Gibt Exit-Code 0 bei Erfolg, 1 bei Fehler zurück.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLATFORM="${1:-}"
|
||||
ARTIFACT="${2:-}"
|
||||
|
||||
if [[ -z "$PLATFORM" || -z "$ARTIFACT" ]]; then
|
||||
echo "Verwendung: $0 <linux|windows> <artefakt-pfad>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$ARTIFACT" ]]; then
|
||||
echo "FEHLER: Artefakt nicht gefunden: $ARTIFACT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Versionstring aus version.inc lesen
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
VERSION=$(grep 'set(SoftFever_VERSION' "$REPO_ROOT/version.inc" | head -1 | grep -oP 'SoftFever_VERSION\s+"\K[^"]+')
|
||||
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "FEHLER: Konnte VERSION nicht aus version.inc lesen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== OrcaSlicer-KX Build-Verifikation ==="
|
||||
echo "Plattform : $PLATFORM"
|
||||
echo "Artefakt : $ARTIFACT"
|
||||
echo "Erwartet : $VERSION"
|
||||
echo "========================================"
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
trap "rm -rf $TMPDIR" EXIT
|
||||
|
||||
CHECKS=(
|
||||
"$VERSION"
|
||||
"KX-Bridge"
|
||||
"tray_info_idx"
|
||||
)
|
||||
|
||||
BINARY=""
|
||||
|
||||
if [[ "$PLATFORM" == "linux" ]]; then
|
||||
# Bevorzugt die unkomprimierte Binary aus dem build/-Baum prüfen (kein FUSE,
|
||||
# keine AppImage-Extraktion nötig). Fällt nur zurück auf --appimage-extract,
|
||||
# wenn keine lose Binary gefunden wird.
|
||||
# Echte ELF-Binary suchen, nicht die 4K-Shell-Wrapper im build/-Root.
|
||||
BUILD_ROOT="$(dirname "$ARTIFACT")"
|
||||
BINARY=""
|
||||
while IFS= read -r cand; do
|
||||
[[ -z "$cand" ]] && continue
|
||||
if file -b "$cand" 2>/dev/null | grep -q "ELF"; then
|
||||
BINARY="$cand"
|
||||
break
|
||||
fi
|
||||
done < <(find "$BUILD_ROOT" -name "orca-slicer" -type f -not -path "*/squashfs-root/*" 2>/dev/null)
|
||||
|
||||
if [[ -z "$BINARY" ]]; then
|
||||
echo "Keine lose Binary gefunden — extrahiere AppImage..."
|
||||
chmod +x "$ARTIFACT"
|
||||
cd "$TMPDIR"
|
||||
if ! "$ARTIFACT" --appimage-extract bin/orca-slicer >/dev/null 2>&1; then
|
||||
"$ARTIFACT" --appimage-extract >/dev/null 2>&1 || true
|
||||
fi
|
||||
BINARY=$(find "$TMPDIR/squashfs-root" -name "orca-slicer" -type f 2>/dev/null | head -1)
|
||||
else
|
||||
echo "Prüfe lose Binary aus build/: $BINARY"
|
||||
fi
|
||||
|
||||
if [[ -z "$BINARY" || ! -f "$BINARY" ]]; then
|
||||
echo "FEHLER: orca-slicer Binary weder im build/-Baum noch im AppImage gefunden"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
elif [[ "$PLATFORM" == "windows" ]]; then
|
||||
echo "Extrahiere Windows ZIP..."
|
||||
unzip -q "$ARTIFACT" "OrcaSlicer.dll" -d "$TMPDIR" 2>/dev/null || true
|
||||
BINARY="$TMPDIR/OrcaSlicer.dll"
|
||||
if [[ ! -f "$BINARY" ]]; then
|
||||
echo "FEHLER: OrcaSlicer.dll nicht im ZIP gefunden"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
else
|
||||
echo "FEHLER: Unbekannte Plattform '$PLATFORM' (erwartet: linux oder windows)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Binary : $BINARY ($(du -sh "$BINARY" | cut -f1))"
|
||||
echo ""
|
||||
|
||||
FAILED=0
|
||||
for needle in "${CHECKS[@]}"; do
|
||||
if grep -qaF "$needle" "$BINARY" 2>/dev/null; then
|
||||
echo " [OK] $needle"
|
||||
else
|
||||
echo " [FAIL] $needle — NICHT GEFUNDEN"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [[ $FAILED -eq 0 ]]; then
|
||||
echo "=== VERIFIKATION ERFOLGREICH ==="
|
||||
exit 0
|
||||
else
|
||||
echo "=== VERIFIKATION FEHLGESCHLAGEN — Upload abgebrochen ==="
|
||||
exit 1
|
||||
fi
|
||||
@@ -7,7 +7,7 @@ set(SLIC3R_APP_KEY "OrcaSlicer")
|
||||
if(NOT DEFINED BBL_INTERNAL_TESTING)
|
||||
set(BBL_INTERNAL_TESTING "0")
|
||||
endif()
|
||||
set(SoftFever_VERSION "2.3.2-dev")
|
||||
set(SoftFever_VERSION "2.3.2-kx4")
|
||||
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)"
|
||||
SoftFever_VERSION_MATCH ${SoftFever_VERSION})
|
||||
set(ORCA_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
|
||||
Reference in New Issue
Block a user