merge: integrate OrcaSlicer-ImageMap onto kx-v2.4.2 base
Rebuilds experimental/imagemap on the correct base. The previous attempt
merged onto upstream 8500fcdcca, which silently dropped all 13 KX commits
(branding, Gitea updater, Kobra X profile, AMS fixes) and reverted the
Moonraker AMS feature set.
Conflict resolution:
- KX side: branding assets, workflows, Kobra X profile, Gitea updater,
GUI_App declarations, PrintObject filament-id structure, GLCanvas3D
reload fixes, PresetBundle extruder validation, ZAA/Polyline3 in GCode.
- ImageMap side: texture/colour machinery (GCode texture sampling,
ToolOrdering zone resolution, MeshBoolean provenance, CGAL repair
colour remap, top-surface image fill params).
- Rewrote ImageMap's wall_filament/sparse_infill_filament/
solid_infill_filament accesses onto the KX *_filament_id options
instead of adding duplicate config fields.
9
.devcontainer/devcontainer-lock.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/desktop-lite:1": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "ghcr.io/devcontainers/features/desktop-lite@sha256:d8649bc8ca8e50a52fbee29d281739ea05dc1022905b185434d6bbe9afc8f221",
|
||||
"integrity": "sha256:d8649bc8ca8e50a52fbee29d281739ea05dc1022905b185434d6bbe9afc8f221"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
@@ -49,3 +49,5 @@ internal_docs/
|
||||
# Python bytecode
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.3mf
|
||||
*.gcode
|
||||
|
||||
@@ -85,6 +85,7 @@ else ()
|
||||
endif ()
|
||||
|
||||
find_package(Git)
|
||||
set(GIT_COMMIT_HASH "0000000")
|
||||
if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "")
|
||||
message(STATUS "Specified git commit hash: $ENV{git_commit_hash}")
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
@@ -99,7 +100,6 @@ if(DEFINED ENV{git_commit_hash} AND NOT "$ENV{git_commit_hash}" STREQUAL "")
|
||||
# No .git directory (e.g., Flatpak sandbox) — truncate directly
|
||||
string(SUBSTRING "$ENV{git_commit_hash}" 0 7 GIT_COMMIT_HASH)
|
||||
endif()
|
||||
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
# Check current Git commit hash
|
||||
execute_process(
|
||||
@@ -108,7 +108,6 @@ elseif(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{SLIC3R_STATIC})
|
||||
@@ -434,8 +433,8 @@ if (NOT MSVC AND ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMP
|
||||
|
||||
if((${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang") AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15)
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag(-Wno-error=enum-constexpr-conversion HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
|
||||
if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV)
|
||||
check_cxx_compiler_flag("-Werror=unknown-warning-option -Wno-error=enum-constexpr-conversion" HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV_STRICT)
|
||||
if(HAS_WNO_ERROR_ENUM_CONSTEXPR_CONV_STRICT)
|
||||
add_compile_options(-Wno-error=enum-constexpr-conversion)
|
||||
endif()
|
||||
endif()
|
||||
@@ -510,10 +509,10 @@ include_directories(SYSTEM ${LIBDIR_BIN}/dev-utils/platform)
|
||||
include_directories(SYSTEM ${LIBDIR}/libigl)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS)
|
||||
add_definitions(-D_USE_MATH_DEFINES -D_WIN32 -D_CRT_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_WARNINGS -DNOMINMAX)
|
||||
if(MSVC)
|
||||
# BOOST_ALL_NO_LIB: Avoid the automatic linking of Boost libraries on Windows. Rather rely on explicit linking.
|
||||
add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8 )
|
||||
add_definitions(-DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8 -DBOOST_REGEX_NO_WIN32_LOCALE)
|
||||
# Force the source code encoding to UTF-8. See OrcaSlicer GH pull request #5583
|
||||
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
@@ -578,7 +577,7 @@ endif()
|
||||
# boost::process was introduced first in version 1.64.0,
|
||||
# boost::beast::detail::base64 was introduced first in version 1.66.0
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
cmake_policy(SET CMP0167 OLD)
|
||||
endif()
|
||||
set(Boost_NO_SYSTEM_PATHS TRUE)
|
||||
find_package(Boost 1.83.0 REQUIRED COMPONENTS system filesystem thread log log_setup locale regex chrono atomic date_time iostreams program_options nowide)
|
||||
@@ -956,7 +955,7 @@ set (CPACK_PACKAGE_INSTALL_REGISTRY_KEY "OrcaSlicer")
|
||||
set (CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
|
||||
set (CPACK_NSIS_EXECUTABLES_DIRECTORY ".")
|
||||
# set (CPACK_NSIS_MODIFY_PATH "ON")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer")
|
||||
set(CPACK_PACKAGE_EXECUTABLES "orca-slicer;OrcaSlicer-ImageMap")
|
||||
set(CPACK_CREATE_DESKTOP_LINKS "orca-slicer")
|
||||
set (CPACK_RESOURCE_FILE_LICENSE ${CMAKE_SOURCE_DIR}/LICENSE.txt) # must also include in install command
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ function build_slicer() {
|
||||
resources_path=$(readlink ./OrcaSlicer.app/Contents/Resources)
|
||||
rm ./OrcaSlicer.app/Contents/Resources
|
||||
cp -R "$resources_path" ./OrcaSlicer.app/Contents/Resources
|
||||
plutil -replace CFBundleIconFile -string "images/OrcaSlicer.icns" ./OrcaSlicer.app/Contents/Info.plist
|
||||
# delete .DS_Store file
|
||||
find ./OrcaSlicer.app/ -name '.DS_Store' -delete
|
||||
|
||||
|
||||
34
cmake/GenerateSoftFeverVersion.cmake
Normal file
@@ -0,0 +1,34 @@
|
||||
if(NOT DEFINED INPUT)
|
||||
message(FATAL_ERROR "INPUT is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED OUTPUT)
|
||||
message(FATAL_ERROR "OUTPUT is required")
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED GIT_COMMIT_HASH)
|
||||
set(GIT_COMMIT_HASH "0000000")
|
||||
endif()
|
||||
|
||||
file(STRINGS "${INPUT}" SOFTFEVER_VERSION LIMIT_COUNT 1)
|
||||
string(STRIP "${SOFTFEVER_VERSION}" SOFTFEVER_VERSION)
|
||||
|
||||
set(CONTENT [=[
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
const char* softfever_version()
|
||||
{
|
||||
return "@SOFTFEVER_VERSION@";
|
||||
}
|
||||
|
||||
const char* git_commit_hash()
|
||||
{
|
||||
return "@GIT_COMMIT_HASH@";
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
]=])
|
||||
string(CONFIGURE "${CONTENT}" CONTENT @ONLY)
|
||||
file(WRITE "${OUTPUT}" "${CONTENT}")
|
||||
@@ -30,6 +30,10 @@ add_subdirectory(md4c)
|
||||
add_subdirectory(mdns)
|
||||
add_subdirectory(miniz)
|
||||
add_subdirectory(minilzo)
|
||||
add_subdirectory(pigment-painter)
|
||||
add_subdirectory(prusa-fdm-mixer)
|
||||
add_subdirectory(colorsolver)
|
||||
add_subdirectory(qhull)
|
||||
add_subdirectory(qoi)
|
||||
add_subdirectory(semver) # Semver static library
|
||||
add_subdirectory(tinygltf)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(Clipper2 VERSION 1.5.2 LANGUAGES C CXX)
|
||||
project(Clipper2 VERSION 2.0.1 LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
@@ -19,6 +19,7 @@ set(CLIPPER2_INC
|
||||
Clipper2Lib/include/clipper2/clipper.minkowski.h
|
||||
Clipper2Lib/include/clipper2/clipper.offset.h
|
||||
Clipper2Lib/include/clipper2/clipper.rectclip.h
|
||||
Clipper2Lib/include/clipper2/clipper.triangulation.h
|
||||
Clipper2Lib/include/clipper2/clipper2_z.hpp
|
||||
)
|
||||
|
||||
@@ -26,6 +27,7 @@ set(CLIPPER2_SRC
|
||||
Clipper2Lib/src/clipper.engine.cpp
|
||||
Clipper2Lib/src/clipper.offset.cpp
|
||||
Clipper2Lib/src/clipper.rectclip.cpp
|
||||
Clipper2Lib/src/clipper.triangulation.cpp
|
||||
Clipper2Lib/src/clipper2_z.cpp
|
||||
)
|
||||
|
||||
@@ -51,4 +53,3 @@ set_target_properties(Clipper2 PROPERTIES FOLDER Libraries
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
PUBLIC_HEADER "${CLIPPER2_INC}"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 12 May 2024 *
|
||||
* Date : 12 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Core Clipper Library structures and functions *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -141,7 +141,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
}
|
||||
|
||||
explicit Point() : x(0), y(0), z(0) {};
|
||||
explicit Point() : x(0), y(0), z(0) {}
|
||||
|
||||
template <typename T2>
|
||||
Point(const T2 x_, const T2 y_, const z_type z_ = 0)
|
||||
@@ -193,7 +193,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
}
|
||||
|
||||
explicit Point() : x(0), y(0) {};
|
||||
explicit Point() : x(0), y(0) {}
|
||||
|
||||
template <typename T2>
|
||||
Point(const T2 x_, const T2 y_) { Init(x_, y_); }
|
||||
@@ -251,6 +251,20 @@ namespace Clipper2Lib {
|
||||
template <typename T>
|
||||
using Paths = std::vector<Path<T>>;
|
||||
|
||||
template <typename T, typename T2=T>
|
||||
Path<T>& operator<<(Path<T>& poly, const Point<T2>& p)
|
||||
{
|
||||
poly.emplace_back(p);
|
||||
return poly;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Paths<T>& operator<<(Paths<T>& polys, const Path<T>& p)
|
||||
{
|
||||
polys.emplace_back(p);
|
||||
return polys;
|
||||
}
|
||||
|
||||
using Path64 = Path<int64_t>;
|
||||
using PathD = Path<double>;
|
||||
using Paths64 = std::vector< Path64>;
|
||||
@@ -356,13 +370,13 @@ namespace Clipper2Lib {
|
||||
bottom *= scale;
|
||||
}
|
||||
|
||||
bool IsEmpty() const { return bottom <= top || right <= left; };
|
||||
bool IsEmpty() const { return bottom <= top || right <= left; }
|
||||
|
||||
bool Intersects(const Rect<T>& rec) const
|
||||
{
|
||||
return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) &&
|
||||
((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom));
|
||||
};
|
||||
}
|
||||
|
||||
bool operator==(const Rect<T>& other) const {
|
||||
return left == other.left && right == other.right &&
|
||||
@@ -688,29 +702,28 @@ namespace Clipper2Lib {
|
||||
return (x > 0) - (x < 0);
|
||||
}
|
||||
|
||||
struct MultiplyUInt64Result
|
||||
struct UInt128Struct
|
||||
{
|
||||
const uint64_t result = 0;
|
||||
const uint64_t carry = 0;
|
||||
const uint64_t lo = 0;
|
||||
const uint64_t hi = 0;
|
||||
|
||||
bool operator==(const MultiplyUInt64Result& other) const
|
||||
bool operator==(const UInt128Struct& other) const
|
||||
{
|
||||
return result == other.result && carry == other.carry;
|
||||
};
|
||||
return lo == other.lo && hi == other.hi;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
inline MultiplyUInt64Result Multiply(uint64_t a, uint64_t b) // #834, #835
|
||||
inline UInt128Struct MultiplyUInt64(uint64_t a, uint64_t b) // #834, #835
|
||||
{
|
||||
// note to self - lamba expressions follow
|
||||
const auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; };
|
||||
const auto hi = [](uint64_t x) { return x >> 32; };
|
||||
|
||||
const uint64_t x1 = lo(a) * lo(b);
|
||||
const uint64_t x2 = hi(a) * lo(b) + hi(x1);
|
||||
const uint64_t x3 = lo(a) * hi(b) + lo(x2);
|
||||
const uint64_t result = lo(x3) << 32 | lo(x1);
|
||||
const uint64_t carry = hi(a) * hi(b) + hi(x2) + hi(x3);
|
||||
|
||||
return { result, carry };
|
||||
return { uint64_t(lo(x3) << 32 | lo(x1)), uint64_t(hi(a) * hi(b) + hi(x2) + hi(x3)) };
|
||||
}
|
||||
|
||||
// returns true if (and only if) a * b == c * d
|
||||
@@ -727,14 +740,50 @@ namespace Clipper2Lib {
|
||||
const auto abs_c = static_cast<uint64_t>(std::abs(c));
|
||||
const auto abs_d = static_cast<uint64_t>(std::abs(d));
|
||||
|
||||
const auto abs_ab = Multiply(abs_a, abs_b);
|
||||
const auto abs_cd = Multiply(abs_c, abs_d);
|
||||
const auto ab = MultiplyUInt64(abs_a, abs_b);
|
||||
const auto cd = MultiplyUInt64(abs_c, abs_d);
|
||||
|
||||
// nb: it's important to differentiate 0 values here from other values
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
return abs_ab == abs_cd && sign_ab == sign_cd;
|
||||
return ab == cd && sign_ab == sign_cd;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline int CrossProductSign(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3)
|
||||
{
|
||||
const auto a = pt2.x - pt1.x;
|
||||
const auto b = pt3.y - pt2.y;
|
||||
const auto c = pt2.y - pt1.y;
|
||||
const auto d = pt3.x - pt2.x;
|
||||
|
||||
#if (defined(__clang__) || defined(__GNUC__)) && UINTPTR_MAX >= UINT64_MAX
|
||||
const auto ab = static_cast<__int128_t>(a) * static_cast<__int128_t>(b);
|
||||
const auto cd = static_cast<__int128_t>(c) * static_cast<__int128_t>(d);
|
||||
if (ab > cd) return 1;
|
||||
else if (ab < cd) return -1;
|
||||
else return 0;
|
||||
#else
|
||||
const auto ab = MultiplyUInt64(std::abs(a), std::abs(b));
|
||||
const auto cd = MultiplyUInt64(std::abs(c), std::abs(d));
|
||||
|
||||
const auto sign_ab = TriSign(a) * TriSign(b);
|
||||
const auto sign_cd = TriSign(c) * TriSign(d);
|
||||
|
||||
if (sign_ab == sign_cd)
|
||||
{
|
||||
int result;
|
||||
if (ab.hi == cd.hi)
|
||||
{
|
||||
if (ab.lo == cd.lo) return 0;
|
||||
result = (ab.lo > cd.lo) ? 1 : -1;
|
||||
}
|
||||
else result = (ab.hi > cd.hi) ? 1 : -1;
|
||||
return (sign_ab > 0) ? result : -result;
|
||||
}
|
||||
return (sign_ab > sign_cd) ? 1 : -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -838,14 +887,18 @@ namespace Clipper2Lib {
|
||||
return Area<T>(poly) >= 0;
|
||||
}
|
||||
|
||||
#if CLIPPER2_HI_PRECISION
|
||||
// GetLineIntersectPt - a 'true' result is non-parallel. The 'ip' will also
|
||||
// be constrained to seg1. However, it's possible that 'ip' won't be inside
|
||||
// seg2, even when 'ip' hasn't been constrained (ie 'ip' is inside seg1).
|
||||
|
||||
#if defined(CLIPPER2_HI_PRECISION) && CLIPPER2_HI_PRECISION
|
||||
// caution: this will compromise performance
|
||||
// https://github.com/AngusJohnson/Clipper2/issues/317#issuecomment-1314023253
|
||||
// See also CPP/BenchMark/GetIntersectPtBenchmark.cpp
|
||||
#define CC_MIN(x,y) ((x)>(y)?(y):(x))
|
||||
#define CC_MAX(x,y) ((x)<(y)?(y):(x))
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
double ln1dy = static_cast<double>(ln1b.y - ln1a.y);
|
||||
@@ -891,11 +944,14 @@ namespace Clipper2Lib {
|
||||
ip.x = originx + static_cast<T>(hitx);
|
||||
ip.y = originy + static_cast<T>(hity);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
template<typename T>
|
||||
inline bool GetSegmentIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
inline bool GetLineIntersectPt(const Point<T>& ln1a, const Point<T>& ln1b,
|
||||
const Point<T>& ln2a, const Point<T>& ln2b, Point<T>& ip)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
|
||||
@@ -913,7 +969,10 @@ namespace Clipper2Lib {
|
||||
{
|
||||
ip.x = static_cast<T>(ln1a.x + t * dx1);
|
||||
ip.y = static_cast<T>(ln1a.y + t * dy1);
|
||||
}
|
||||
#ifdef USINGZ
|
||||
ip.z = 0;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -949,21 +1008,44 @@ namespace Clipper2Lib {
|
||||
inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b,
|
||||
const Point64& seg2a, const Point64& seg2b, bool inclusive = false)
|
||||
{
|
||||
double dy1 = static_cast<double>(seg1b.y - seg1a.y);
|
||||
double dx1 = static_cast<double>(seg1b.x - seg1a.x);
|
||||
double dy2 = static_cast<double>(seg2b.y - seg2a.y);
|
||||
double dx2 = static_cast<double>(seg2b.x - seg2a.x);
|
||||
double cp = dy1 * dx2 - dy2 * dx1;
|
||||
if (cp == 0) return false; // ie parallel segments
|
||||
|
||||
if (inclusive)
|
||||
{
|
||||
double res1 = CrossProduct(seg1a, seg2a, seg2b);
|
||||
double res2 = CrossProduct(seg1b, seg2a, seg2b);
|
||||
if (res1 * res2 > 0) return false;
|
||||
double res3 = CrossProduct(seg2a, seg1a, seg1b);
|
||||
double res4 = CrossProduct(seg2b, seg1a, seg1b);
|
||||
if (res3 * res4 > 0) return false;
|
||||
return (res1 || res2 || res3 || res4); // ensures not collinear
|
||||
//result **includes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return true;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t > cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t < cp) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return true;
|
||||
if (t > 0) return (cp > 0 && t <= cp);
|
||||
else return (cp < 0 && t >= cp); // true when t less neg. than cp
|
||||
}
|
||||
else {
|
||||
return (GetSign(CrossProduct(seg1a, seg2a, seg2b)) *
|
||||
GetSign(CrossProduct(seg1b, seg2a, seg2b)) < 0) &&
|
||||
(GetSign(CrossProduct(seg2a, seg1a, seg1b)) *
|
||||
GetSign(CrossProduct(seg2b, seg1a, seg1b)) < 0);
|
||||
else
|
||||
{
|
||||
//result **excludes** segments that touch at an end point
|
||||
double t = ((seg1a.x - seg2a.x) * dy2 - (seg1a.y - seg2a.y) * dx2);
|
||||
if (t == 0) return false;
|
||||
if (t > 0)
|
||||
{
|
||||
if (cp < 0 || t >= cp) return false;
|
||||
}
|
||||
else if (cp > 0 || t <= cp ) return false; // false when t more neg. than cp
|
||||
|
||||
t = ((seg1a.x - seg2a.x) * dy1 - (seg1a.y - seg2a.y) * dx1);
|
||||
if (t == 0) return false;
|
||||
if (t > 0) return (cp > 0 && t < cp);
|
||||
else return (cp < 0 && t > cp); // true when t less neg. than cp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,7 +1133,7 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
@@ -1065,7 +1147,7 @@ namespace Clipper2Lib {
|
||||
if (curr == cend) curr = cbegin;
|
||||
if (curr == cbegin) prev = cend - 1;
|
||||
else prev = curr - 1;
|
||||
double d = CrossProduct(*prev, *curr, pt);
|
||||
int d = CrossProductSign(*prev, *curr, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Clipper2Lib {
|
||||
if (splits) delete splits;
|
||||
// nb: don't delete the split pointers
|
||||
// as these are owned by ClipperBase's outrec_list_
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
@@ -160,7 +160,7 @@ namespace Clipper2Lib {
|
||||
struct HorzJoin {
|
||||
OutPt* op1 = nullptr;
|
||||
OutPt* op2 = nullptr;
|
||||
HorzJoin() {};
|
||||
HorzJoin() {}
|
||||
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
|
||||
};
|
||||
|
||||
@@ -280,11 +280,11 @@ namespace Clipper2Lib {
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ClipperBase();
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
|
||||
bool PreserveCollinear() const { return preserve_collinear_;};
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; };
|
||||
bool ReverseSolution() const { return reverse_solution_; };
|
||||
int ErrorCode() const { return error_code_; }
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; }
|
||||
bool PreserveCollinear() const { return preserve_collinear_;}
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; }
|
||||
bool ReverseSolution() const { return reverse_solution_; }
|
||||
void Clear();
|
||||
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
|
||||
#ifdef USINGZ
|
||||
@@ -304,7 +304,7 @@ namespace Clipper2Lib {
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() {};
|
||||
virtual ~PolyPath() {}
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
PolyPath(const PolyPath&) = delete;
|
||||
PolyPath& operator=(const PolyPath&) = delete;
|
||||
@@ -352,7 +352,7 @@ namespace Clipper2Lib {
|
||||
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
|
||||
|
||||
~PolyPath64() {
|
||||
~PolyPath64() override {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Clipper2Lib {
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const Path64& Polygon() const { return polygon_; };
|
||||
const Path64& Polygon() const { return polygon_; }
|
||||
|
||||
double Area() const
|
||||
{
|
||||
@@ -418,7 +418,7 @@ namespace Clipper2Lib {
|
||||
polygon_ = path;
|
||||
}
|
||||
|
||||
~PolyPathD() {
|
||||
~PolyPathD() override {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ namespace Clipper2Lib {
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const PathD& Polygon() const { return polygon_; };
|
||||
const PathD& Polygon() const { return polygon_; }
|
||||
|
||||
double Area() const
|
||||
{
|
||||
@@ -548,7 +548,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; }
|
||||
|
||||
void ZCB(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)
|
||||
@@ -564,7 +564,7 @@ namespace Clipper2Lib {
|
||||
PointD e2t = PointD(e2top) * invScale_;
|
||||
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
|
||||
pt.z = tmp.z; // only update 'z'
|
||||
};
|
||||
}
|
||||
|
||||
void CheckCallback()
|
||||
{
|
||||
|
||||
@@ -116,6 +116,7 @@ the four vertices that define the two segments that are intersecting.
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -816,6 +817,24 @@ EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath6
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 Triangulate64(const CPaths64 paths, bool use_delaunay)
|
||||
{
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsFromPathsT(sol);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD TriangulateD(const CPathsD paths, int decimal_precison, bool use_delaunay)
|
||||
{
|
||||
if (decimal_precison < -8 || decimal_precison > 8) return nullptr;
|
||||
const double scale = std::pow(10, decimal_precison);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
Paths64 sol;
|
||||
if (Triangulate(pp, sol, use_delaunay) != TriangulateResult::success) return nullptr;
|
||||
return CreateCPathsDFromPaths64(sol, 1 / scale);
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
|
||||
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 27 April 2024 *
|
||||
* Date : 5 March 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : This module provides a simple interface to the Clipper Library *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -13,8 +13,9 @@
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
#include "clipper2/clipper.minkowski.h"
|
||||
#include "clipper2/clipper.triangulation.h"
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -137,7 +138,7 @@
|
||||
JoinType jt, EndType et, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0)
|
||||
{
|
||||
if (!delta) return paths;
|
||||
if (delta==0.0) return paths;
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
clip_offset.AddPaths(paths, jt, et);
|
||||
Paths64 solution;
|
||||
@@ -151,10 +152,10 @@
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (!delta) return paths;
|
||||
if (delta==0.0) return paths;
|
||||
if (error_code) return PathsD();
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance * scale);
|
||||
clip_offset.AddPaths(ScalePaths<int64_t,double>(paths, scale, error_code), jt, et);
|
||||
if (error_code) return PathsD();
|
||||
Paths64 solution;
|
||||
@@ -355,6 +356,29 @@
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
} // end details namespace
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
|
||||
@@ -615,29 +639,6 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> SimplifyPath(const Path<T> &path,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
@@ -669,13 +670,13 @@
|
||||
start = curr;
|
||||
do
|
||||
{
|
||||
curr = GetNext(curr, high, flags);
|
||||
curr = details::GetNext(curr, high, flags);
|
||||
} while (curr != start && distSqr[curr] > epsSqr);
|
||||
if (curr == start) break;
|
||||
}
|
||||
|
||||
prior = GetPrior(curr, high, flags);
|
||||
next = GetNext(curr, high, flags);
|
||||
prior = details::GetPrior(curr, high, flags);
|
||||
next = details::GetNext(curr, high, flags);
|
||||
if (next == prior) break;
|
||||
|
||||
// flag for removal the smaller of adjacent 'distances'
|
||||
@@ -684,14 +685,14 @@
|
||||
prior2 = prior;
|
||||
prior = curr;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
}
|
||||
else
|
||||
prior2 = GetPrior(prior, high, flags);
|
||||
prior2 = details::GetPrior(prior, high, flags);
|
||||
|
||||
flags[curr] = true;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
next = details::GetNext(next, high, flags);
|
||||
|
||||
if (isClosedPath || ((curr != high) && (curr != 0)))
|
||||
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
|
||||
@@ -716,6 +717,35 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool Path2ContainsPath1(const Path<T>& path1, const Path<T>& path2)
|
||||
{
|
||||
// precondition: paths must not intersect, except for
|
||||
// transient (and presumed 'micro') path intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
for (const Point<T>& pt : path1)
|
||||
{
|
||||
switch (PointInPolygon(pt, path2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pip != PointInPolygonResult::IsInside) return false;
|
||||
// result is likely true but check midpoint
|
||||
Point<T> mp1 = GetBounds(path1).MidPoint();
|
||||
return PointInPolygon(mp1, path2) == PointInPolygonResult::IsInside;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void RDP(const Path<T> path, std::size_t begin,
|
||||
std::size_t end, double epsSqrd, std::vector<bool>& flags)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Clipper2Lib {
|
||||
quad.emplace_back(tmp[i][h]);
|
||||
quad.emplace_back(tmp[i][j]);
|
||||
quad.emplace_back(tmp[g][j]);
|
||||
};
|
||||
}
|
||||
if (!IsPositive(quad))
|
||||
std::reverse(quad.begin(), quad.end());
|
||||
result.emplace_back(std::move(quad));
|
||||
|
||||
@@ -39,7 +39,7 @@ private:
|
||||
class Group {
|
||||
public:
|
||||
Paths64 paths_in;
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
std::optional<size_t> lowest_path_idx{};
|
||||
bool is_reversed = false;
|
||||
JoinType join_type;
|
||||
EndType end_type;
|
||||
@@ -92,14 +92,14 @@ public:
|
||||
bool reverse_solution = false) :
|
||||
miter_limit_(miter_limit), arc_tolerance_(arc_tolerance),
|
||||
preserve_collinear_(preserve_collinear),
|
||||
reverse_solution_(reverse_solution) { };
|
||||
reverse_solution_(reverse_solution) { }
|
||||
|
||||
~ClipperOffset() { Clear(); };
|
||||
~ClipperOffset() { Clear(); }
|
||||
|
||||
int ErrorCode() const { return error_code_; };
|
||||
int ErrorCode() const { return error_code_; }
|
||||
void AddPath(const Path64& path, JoinType jt_, EndType et_);
|
||||
void AddPaths(const Paths64& paths, JoinType jt_, EndType et_);
|
||||
void Clear() { groups_.clear(); norms.clear(); };
|
||||
void Clear() { groups_.clear(); norms.clear(); }
|
||||
|
||||
void Execute(double delta, Paths64& sols_64);
|
||||
void Execute(double delta, PolyTree64& polytree);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Clipper2Lib {
|
||||
void ExecuteInternal(const Path64& path);
|
||||
Path64 GetPath(OutPt2*& op);
|
||||
public:
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {};
|
||||
explicit RectClipLines64(const Rect64& rect) : RectClip64(rect) {}
|
||||
Paths64 Execute(const Paths64& paths);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 6 December 2025 *
|
||||
* Release : BETA RELEASE *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Delaunay Triangulation *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_TRIANGULATION_H
|
||||
#define CLIPPER_TRIANGULATION_H
|
||||
|
||||
#include <stack>
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
#ifdef USINGZ
|
||||
namespace Clipper2Lib_Z {
|
||||
#else
|
||||
namespace Clipper2Lib {
|
||||
#endif
|
||||
|
||||
enum class TriangulateResult { success, fail, no_polygons, paths_intersect };
|
||||
|
||||
// Triangulate - this function will not accept intesecting paths
|
||||
TriangulateResult Triangulate(const Paths64& pp, Paths64& solution, bool useDelaunay = true);
|
||||
TriangulateResult Triangulate(const PathsD& pp, int decPlaces, PathsD& solution, bool useDelaunay = true);
|
||||
|
||||
} // Clipper2Lib namespace
|
||||
#endif // CLIPPER_TRIANGULATION_H
|
||||
@@ -1,6 +1,6 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.5.2";
|
||||
constexpr auto CLIPPER2_VERSION = "2.0.1";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Date : 21 February 2026 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2026 *
|
||||
* Purpose : This is the main polygon clipping module *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -482,8 +482,7 @@ namespace Clipper2Lib {
|
||||
inline void SetOwner(OutRec* outrec, OutRec* new_owner)
|
||||
{
|
||||
//precondition1: new_owner is never null
|
||||
while (new_owner->owner && !new_owner->owner->pts)
|
||||
new_owner->owner = new_owner->owner->owner;
|
||||
new_owner->owner = GetRealOutRec(new_owner->owner);
|
||||
OutRec* tmp = new_owner;
|
||||
while (tmp && tmp != outrec) tmp = tmp->owner;
|
||||
if (tmp) new_owner->owner = outrec->owner;
|
||||
@@ -536,9 +535,9 @@ namespace Clipper2Lib {
|
||||
val = 1 - val; // toggle val
|
||||
else
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
is_above = !is_above;
|
||||
op2 = op2->next;
|
||||
@@ -546,9 +545,9 @@ namespace Clipper2Lib {
|
||||
|
||||
if (is_above != starting_above)
|
||||
{
|
||||
double d = CrossProduct(op2->prev->pt, op2->pt, pt);
|
||||
if (d == 0) return PointInPolygonResult::IsOn;
|
||||
if ((d < 0) == is_above) val = 1 - val;
|
||||
int i = CrossProductSign(op2->prev->pt, op2->pt, pt);
|
||||
if (i == 0) return PointInPolygonResult::IsOn;
|
||||
if ((i < 0) == is_above) val = 1 - val;
|
||||
}
|
||||
|
||||
if (val == 0) return PointInPolygonResult::IsOutside;
|
||||
@@ -578,30 +577,31 @@ namespace Clipper2Lib {
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool Path1InsidePath2(OutPt* op1, OutPt* op2)
|
||||
inline bool Path2ContainsPath1(OutPt* op1, OutPt* op2)
|
||||
{
|
||||
// we need to make some accommodation for rounding errors
|
||||
// so we won't jump if the first vertex is found outside
|
||||
PointInPolygonResult result;
|
||||
int outside_cnt = 0;
|
||||
// this function accommodates rounding errors that
|
||||
// can cause path micro intersections
|
||||
PointInPolygonResult pip = PointInPolygonResult::IsOn;
|
||||
OutPt* op = op1;
|
||||
do
|
||||
{
|
||||
result = PointInOpPolygon(op->pt, op2);
|
||||
if (result == PointInPolygonResult::IsOutside) ++outside_cnt;
|
||||
else if (result == PointInPolygonResult::IsInside) --outside_cnt;
|
||||
do {
|
||||
switch (PointInOpPolygon(op->pt, op2))
|
||||
{
|
||||
case PointInPolygonResult::IsOutside:
|
||||
if (pip == PointInPolygonResult::IsOutside) return false;
|
||||
pip = PointInPolygonResult::IsOutside;
|
||||
break;
|
||||
case PointInPolygonResult::IsInside:
|
||||
if (pip == PointInPolygonResult::IsInside) return true;
|
||||
pip = PointInPolygonResult::IsInside;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
op = op->next;
|
||||
} while (op != op1 && std::abs(outside_cnt) < 2);
|
||||
if (std::abs(outside_cnt) > 1) return (outside_cnt < 0);
|
||||
// since path1's location is still equivocal, check its midpoint
|
||||
Point64 mp = GetBounds(GetCleanPath(op1)).MidPoint();
|
||||
Path64 path2 = GetCleanPath(op2);
|
||||
return PointInPolygon(mp, path2) != PointInPolygonResult::IsOutside;
|
||||
} while (op != op1);
|
||||
// result unclear, so try again using cleaned paths
|
||||
return Path2ContainsPath1(GetCleanPath(op1), GetCleanPath(op2)); // (#973)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void AddLocMin(LocalMinimaList& list,
|
||||
Vertex& vert, PathType polytype, bool is_open)
|
||||
{
|
||||
@@ -621,7 +621,7 @@ namespace Clipper2Lib {
|
||||
{return a + path.size(); });
|
||||
if (total_vertex_count == 0) return;
|
||||
|
||||
Vertex* vertices = new Vertex[total_vertex_count], * v = vertices;
|
||||
Vertex* allVertices = new Vertex[total_vertex_count], * v = allVertices;
|
||||
for (const Path64& path : paths)
|
||||
{
|
||||
//for each path create a circular double linked list of vertices
|
||||
@@ -713,7 +713,7 @@ namespace Clipper2Lib {
|
||||
}
|
||||
} // end processing current path
|
||||
|
||||
vertexLists.emplace_back(vertices);
|
||||
vertexLists.emplace_back(allVertices);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -1126,21 +1126,19 @@ namespace Clipper2Lib {
|
||||
return newcomer.curr_x > resident.curr_x;
|
||||
|
||||
//get the turning direction a1.top, a2.bot, a2.top
|
||||
double d = CrossProduct(resident.top, newcomer.bot, newcomer.top);
|
||||
if (d != 0) return d < 0;
|
||||
int i = CrossProductSign(resident.top, newcomer.bot, newcomer.top);
|
||||
if (i != 0) return i < 0;
|
||||
|
||||
//edges must be collinear to get here
|
||||
//for starting open paths, place them according to
|
||||
//the direction they're about to turn
|
||||
if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
resident.top, NextVertex(resident)->pt) <= 0;
|
||||
return (CrossProductSign(newcomer.bot, resident.top, NextVertex(resident)->pt) <= 0);
|
||||
}
|
||||
else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y))
|
||||
{
|
||||
return CrossProduct(newcomer.bot,
|
||||
newcomer.top, NextVertex(newcomer)->pt) >= 0;
|
||||
return (CrossProductSign(newcomer.bot, newcomer.top, NextVertex(newcomer)->pt) >= 0);
|
||||
}
|
||||
|
||||
int64_t y = newcomer.bot.y;
|
||||
@@ -1155,7 +1153,7 @@ namespace Clipper2Lib {
|
||||
resident.bot, resident.top)) return true;
|
||||
else
|
||||
//compare turning direction of the alternate bound
|
||||
return (CrossProduct(PrevPrevVertex(resident)->pt,
|
||||
return (CrossProductSign(PrevPrevVertex(resident)->pt,
|
||||
newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft;
|
||||
}
|
||||
|
||||
@@ -1565,7 +1563,7 @@ namespace Clipper2Lib {
|
||||
FixSelfIntersects(outrec);
|
||||
}
|
||||
|
||||
void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp)
|
||||
void ClipperBase::DoSplitOp (OutRec* outrec, OutPt* splitOp)
|
||||
{
|
||||
// splitOp.prev -> splitOp &&
|
||||
// splitOp.next -> splitOp.next.next are intersecting
|
||||
@@ -1574,7 +1572,7 @@ namespace Clipper2Lib {
|
||||
outrec->pts = prevOp;
|
||||
|
||||
Point64 ip;
|
||||
GetSegmentIntersectPt(prevOp->pt, splitOp->pt,
|
||||
GetLineIntersectPt(prevOp->pt, splitOp->pt,
|
||||
splitOp->next->pt, nextNextOp->pt, ip);
|
||||
|
||||
#ifdef USINGZ
|
||||
@@ -1630,7 +1628,7 @@ namespace Clipper2Lib {
|
||||
|
||||
if (using_polytree_)
|
||||
{
|
||||
if (Path1InsidePath2(prevOp, newOp))
|
||||
if (Path2ContainsPath1(prevOp, newOp))
|
||||
{
|
||||
newOr->splits = new OutRecList();
|
||||
newOr->splits->emplace_back(outrec);
|
||||
@@ -1652,10 +1650,10 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::FixSelfIntersects(OutRec* outrec)
|
||||
{
|
||||
OutPt* op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
return; // because triangles can't self-intersect
|
||||
for (; ; )
|
||||
{
|
||||
// triangles can't self-intersect
|
||||
if (op2->prev == op2->next->next) break;
|
||||
if (SegmentsIntersect(op2->prev->pt,
|
||||
op2->pt, op2->next->pt, op2->next->next->pt))
|
||||
{
|
||||
@@ -1664,6 +1662,8 @@ namespace Clipper2Lib {
|
||||
DoSplitOp(outrec, op2);
|
||||
if (!outrec->pts) break;
|
||||
op2 = outrec->pts;
|
||||
if (op2->prev == op2->next->next)
|
||||
break; // again, because triangles can't self-intersect
|
||||
continue;
|
||||
}
|
||||
else
|
||||
@@ -2112,10 +2112,9 @@ namespace Clipper2Lib {
|
||||
e->prev_in_sel = e->prev_in_ael;
|
||||
e->next_in_sel = e->next_in_ael;
|
||||
e->jump = e->next_in_sel;
|
||||
if (e->join_with == JoinWith::Left)
|
||||
e->curr_x = e->prev_in_ael->curr_x; // also avoids complications
|
||||
else
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
// it is safe to ignore 'joined' edges here because
|
||||
// if necessary they will be split in IntersectEdges()
|
||||
e->curr_x = TopX(*e, top_y);
|
||||
e = e->next_in_ael;
|
||||
}
|
||||
}
|
||||
@@ -2262,15 +2261,14 @@ namespace Clipper2Lib {
|
||||
|
||||
void MoveSplits(OutRec* fromOr, OutRec* toOr)
|
||||
{
|
||||
if (!fromOr->splits) return;
|
||||
if (!toOr->splits) toOr->splits = new OutRecList();
|
||||
OutRecList::iterator orIter = fromOr->splits->begin();
|
||||
for (; orIter != fromOr->splits->end(); ++orIter)
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
if (toOr != *orIter) // #987
|
||||
toOr->splits->emplace_back(*orIter);
|
||||
fromOr->splits->clear();
|
||||
}
|
||||
|
||||
|
||||
void ClipperBase::ProcessHorzJoins()
|
||||
{
|
||||
for (const HorzJoin& j : horz_join_list_)
|
||||
@@ -2300,7 +2298,7 @@ namespace Clipper2Lib {
|
||||
|
||||
if (using_polytree_) //#498, #520, #584, D#576, #618
|
||||
{
|
||||
if (Path1InsidePath2(or1->pts, or2->pts))
|
||||
if (Path2ContainsPath1(or1->pts, or2->pts))
|
||||
{
|
||||
//swap or1's & or2's pts
|
||||
OutPt* tmp = or1->pts;
|
||||
@@ -2311,7 +2309,7 @@ namespace Clipper2Lib {
|
||||
//or2 is now inside or1
|
||||
or2->owner = or1;
|
||||
}
|
||||
else if (Path1InsidePath2(or2->pts, or1->pts))
|
||||
else if (Path2ContainsPath1(or2->pts, or1->pts))
|
||||
{
|
||||
or2->owner = or1;
|
||||
}
|
||||
@@ -2324,13 +2322,14 @@ namespace Clipper2Lib {
|
||||
else
|
||||
or2->owner = or1;
|
||||
}
|
||||
else
|
||||
else // joining, not splitting
|
||||
{
|
||||
or2->pts = nullptr;
|
||||
if (using_polytree_)
|
||||
{
|
||||
SetOwner(or2, or1);
|
||||
MoveSplits(or2, or1); //#618
|
||||
if (or2->splits)
|
||||
MoveSplits(or2, or1); //#618
|
||||
}
|
||||
else
|
||||
or2->owner = or1;
|
||||
@@ -2350,7 +2349,7 @@ namespace Clipper2Lib {
|
||||
void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y)
|
||||
{
|
||||
Point64 ip;
|
||||
if (!GetSegmentIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
if (!GetLineIntersectPt(e1.bot, e1.top, e2.bot, e2.top, ip))
|
||||
ip = Point64(e1.curr_x, top_y); //parallel edges
|
||||
|
||||
//rounding errors can occasionally place the calculated intersection
|
||||
@@ -2934,22 +2933,28 @@ namespace Clipper2Lib {
|
||||
|
||||
bool ClipperBase::CheckSplitOwner(OutRec* outrec, OutRecList* splits)
|
||||
{
|
||||
for (auto split : *splits)
|
||||
// nb: use indexing (not an iterator) in case 'splits' is modified inside this loop (#1029)
|
||||
for (size_t idx = 0; idx < splits->size(); ++idx)
|
||||
{
|
||||
OutRec* split = (*splits)[idx];
|
||||
if (!split->pts && split->splits &&
|
||||
CheckSplitOwner(outrec, split->splits)) return true; //#942
|
||||
split = GetRealOutRec(split);
|
||||
if(!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
if (!split || split == outrec || split->recursive_split == outrec) continue;
|
||||
split->recursive_split = outrec; // prevent infinite loops
|
||||
|
||||
if (split->splits && CheckSplitOwner(outrec, split->splits))
|
||||
return true;
|
||||
else if (CheckBounds(split) &&
|
||||
IsValidOwner(outrec, split) &&
|
||||
split->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, split->pts))
|
||||
{
|
||||
outrec->owner = split; //found in split
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CheckBounds(split) || !split->bounds.Contains(outrec->bounds) ||
|
||||
!Path2ContainsPath1(outrec->pts, split->pts)) continue;
|
||||
|
||||
if (!IsValidOwner(outrec, split)) // split is owned by outrec! (#957)
|
||||
split->owner = outrec->owner;
|
||||
|
||||
outrec->owner = split;
|
||||
return true;
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2960,13 +2965,12 @@ namespace Clipper2Lib {
|
||||
// post-condition: if a valid path, outrec will have a polypath
|
||||
|
||||
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
|
||||
|
||||
while (outrec->owner)
|
||||
{
|
||||
if (outrec->owner->splits && CheckSplitOwner(outrec, outrec->owner->splits)) break;
|
||||
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
|
||||
outrec->owner->bounds.Contains(outrec->bounds) &&
|
||||
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break;
|
||||
Path2ContainsPath1(outrec->pts, outrec->owner->pts)) break;
|
||||
outrec->owner = outrec->owner->owner;
|
||||
}
|
||||
|
||||
@@ -3029,6 +3033,7 @@ namespace Clipper2Lib {
|
||||
{
|
||||
OutRec* outrec = outrec_list_[i];
|
||||
if (!outrec || !outrec->pts) continue;
|
||||
|
||||
if (outrec->is_open)
|
||||
{
|
||||
Path64 path;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 22 January 2025 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : Path Offset (Inflate/Shrink) *
|
||||
@@ -37,22 +37,28 @@ const double arc_const = 0.002; // <-- 1/500
|
||||
// Miscellaneous methods
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
std::optional<size_t> GetLowestClosedPathIdx(const Paths64& paths)
|
||||
void GetLowestClosedPathInfo(const Paths64& paths, std::optional<size_t>& idx, bool& is_neg_area)
|
||||
{
|
||||
std::optional<size_t> result;
|
||||
idx.reset();
|
||||
Point64 botPt = Point64(INT64_MAX, INT64_MIN);
|
||||
for (size_t i = 0; i < paths.size(); ++i)
|
||||
{
|
||||
double a = MAX_DBL;
|
||||
for (const Point64& pt : paths[i])
|
||||
{
|
||||
if ((pt.y < botPt.y) ||
|
||||
((pt.y == botPt.y) && (pt.x >= botPt.x))) continue;
|
||||
result = i;
|
||||
if (a == MAX_DBL)
|
||||
{
|
||||
a = Area(paths[i]);
|
||||
if (a == 0) break; // invalid closed path, so break from inner loop
|
||||
is_neg_area = a < 0;
|
||||
}
|
||||
idx = i;
|
||||
botPt.x = pt.x;
|
||||
botPt.y = pt.y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline double Hypot(double x, double y)
|
||||
@@ -145,15 +151,16 @@ ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType
|
||||
|
||||
if (end_type == EndType::Polygon)
|
||||
{
|
||||
lowest_path_idx = GetLowestClosedPathIdx(paths_in);
|
||||
bool is_neg_area;
|
||||
GetLowestClosedPathInfo(paths_in, lowest_path_idx, is_neg_area);
|
||||
// the lowermost path must be an outer path, so if its orientation is negative,
|
||||
// then flag the whole group is 'reversed' (will negate delta etc.)
|
||||
// as this is much more efficient than reversing every path.
|
||||
is_reversed = (lowest_path_idx.has_value()) && Area(paths_in[lowest_path_idx.value()]) < 0;
|
||||
is_reversed = lowest_path_idx.has_value() && is_neg_area;
|
||||
}
|
||||
else
|
||||
{
|
||||
lowest_path_idx = std::nullopt;
|
||||
lowest_path_idx.reset();
|
||||
is_reversed = false;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +243,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
path_out.emplace_back(pt);
|
||||
@@ -245,7 +252,7 @@ void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
|
||||
{
|
||||
PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_);
|
||||
PointD pt = ptQ;
|
||||
GetSegmentIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
GetLineIntersectPt(pt1, pt2, pt3, pt4, pt);
|
||||
path_out.emplace_back(pt);
|
||||
//get the second intersect point through reflecion
|
||||
path_out.emplace_back(ReflectPoint(pt, ptQ));
|
||||
@@ -601,10 +608,10 @@ void ClipperOffset::ExecuteInternal(double delta)
|
||||
|
||||
if (!solution->size()) return;
|
||||
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
bool paths_reversed = CheckReverseOrientation();
|
||||
//clean up self-intersections ...
|
||||
Clipper64 c;
|
||||
c.PreserveCollinear(false);
|
||||
c.PreserveCollinear(preserve_collinear_);
|
||||
//the solution should retain the orientation of the input
|
||||
c.ReverseSolution(reverse_solution_ != paths_reversed);
|
||||
#ifdef USINGZ
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 5 July 2024 *
|
||||
* Date : 11 October 2025 *
|
||||
* Website : https://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Copyright : Angus Johnson 2010-2025 *
|
||||
* Purpose : FAST rectangular clipping *
|
||||
* License : https://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
@@ -77,8 +77,8 @@ namespace Clipper2Lib {
|
||||
bool GetSegmentIntersection(const Point64& p1,
|
||||
const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
|
||||
{
|
||||
double res1 = CrossProduct(p1, p3, p4);
|
||||
double res2 = CrossProduct(p2, p3, p4);
|
||||
int res1 = CrossProductSign(p1, p3, p4);
|
||||
int res2 = CrossProductSign(p2, p3, p4);
|
||||
if (res1 == 0)
|
||||
{
|
||||
ip = p1;
|
||||
@@ -97,8 +97,8 @@ namespace Clipper2Lib {
|
||||
}
|
||||
if ((res1 > 0) == (res2 > 0)) return false;
|
||||
|
||||
double res3 = CrossProduct(p3, p1, p2);
|
||||
double res4 = CrossProduct(p4, p1, p2);
|
||||
int res3 = CrossProductSign(p3, p1, p2);
|
||||
int res4 = CrossProductSign(p4, p1, p2);
|
||||
if (res3 == 0)
|
||||
{
|
||||
ip = p3;
|
||||
@@ -116,7 +116,7 @@ namespace Clipper2Lib {
|
||||
if ((res3 > 0) == (res4 > 0)) return false;
|
||||
|
||||
// segments must intersect to get here
|
||||
return GetSegmentIntersectPt(p1, p2, p3, p4, ip);
|
||||
return GetLineIntersectPt(p1, p2, p3, p4, ip);
|
||||
}
|
||||
|
||||
inline bool GetIntersection(const Path64& rectPath,
|
||||
@@ -227,7 +227,7 @@ namespace Clipper2Lib {
|
||||
const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp)
|
||||
{
|
||||
if (AreOpposites(prev, curr))
|
||||
return CrossProduct(prev_pt, rect_mp, curr_pt) < 0;
|
||||
return CrossProductSign(prev_pt, rect_mp, curr_pt) < 0;
|
||||
else
|
||||
return HeadingClockwise(prev, curr);
|
||||
}
|
||||
|
||||
1224
deps_src/clipper2/Clipper2Lib/src/clipper.triangulation.cpp
Normal file
@@ -6,3 +6,4 @@
|
||||
#include "clipper.engine.cpp"
|
||||
#include "clipper.offset.cpp"
|
||||
#include "clipper.rectclip.cpp"
|
||||
#include "clipper.triangulation.cpp"
|
||||
|
||||
11
deps_src/colorsolver/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(colorsolver)
|
||||
|
||||
add_library(colorsolver STATIC
|
||||
ColorSolver.cpp
|
||||
ColorSolver.hpp
|
||||
)
|
||||
|
||||
target_include_directories(colorsolver PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(colorsolver PUBLIC cxx_std_17)
|
||||
target_link_libraries(colorsolver PUBLIC pigment_painter prusa_fdm_mixer)
|
||||
2024
deps_src/colorsolver/ColorSolver.cpp
Normal file
243
deps_src/colorsolver/ColorSolver.hpp
Normal file
@@ -0,0 +1,243 @@
|
||||
// ColorSolver
|
||||
// Copyright (C) 2026 sentientstardust
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef slic3r_ColorSolver_hpp_
|
||||
#define slic3r_ColorSolver_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ColorSolverMixModel : int
|
||||
{
|
||||
PigmentPainter = 0,
|
||||
PrusaFdmMixer = 1
|
||||
};
|
||||
|
||||
enum class ColorSolverLookupMode : int
|
||||
{
|
||||
ClosestMix = 0,
|
||||
BlendClosestTwo = 1
|
||||
};
|
||||
|
||||
enum class ColorSolverMode : int
|
||||
{
|
||||
RGB = 0,
|
||||
Oklab = 1,
|
||||
OklabSoftCap4Dark4 = 2
|
||||
};
|
||||
|
||||
enum class ColorSolverStackComponentRole : uint8_t
|
||||
{
|
||||
Generic = 0,
|
||||
Cyan = 1,
|
||||
Magenta = 2,
|
||||
Yellow = 3,
|
||||
Black = 4,
|
||||
White = 5
|
||||
};
|
||||
|
||||
enum class ColorSolverCalibratedStackModelKind : uint8_t
|
||||
{
|
||||
None = 0,
|
||||
CurrentLinearAffine = 1,
|
||||
AlphaEffective = 2,
|
||||
DepthKernelLinear = 3,
|
||||
NearestMeasuredSample = 4
|
||||
};
|
||||
|
||||
struct ColorSolverCalibratedStackModel {
|
||||
ColorSolverCalibratedStackModelKind kind { ColorSolverCalibratedStackModelKind::None };
|
||||
size_t component_count { 0 };
|
||||
int measured_stack_depth { 0 };
|
||||
std::vector<float> measured_layer_heights_mm;
|
||||
std::vector<float> alphas;
|
||||
std::vector<float> taus;
|
||||
std::vector<float> coefficients_linear_rgb;
|
||||
std::vector<float> measured_sample_rgbs;
|
||||
std::vector<uint16_t> measured_sample_stacks;
|
||||
mutable std::string cached_key;
|
||||
|
||||
bool valid() const;
|
||||
std::string cache_key() const;
|
||||
};
|
||||
|
||||
struct ColorSolverCandidateSet {
|
||||
struct KdNode {
|
||||
uint32_t candidate_idx { 0 };
|
||||
int left { -1 };
|
||||
int right { -1 };
|
||||
uint8_t axis { 0 };
|
||||
};
|
||||
|
||||
size_t component_count { 0 };
|
||||
std::vector<float> rgbs;
|
||||
std::vector<float> perceptual_coords;
|
||||
std::vector<float> weights;
|
||||
std::vector<KdNode> kd_nodes;
|
||||
std::vector<KdNode> perceptual_kd_nodes;
|
||||
int kd_root { -1 };
|
||||
int perceptual_kd_root { -1 };
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return component_count == 0 || rgbs.empty() || rgbs.size() % 3 != 0 ||
|
||||
weights.size() != (rgbs.size() / 3) * component_count;
|
||||
}
|
||||
};
|
||||
|
||||
using ColorSolverCandidateCache = std::map<std::string, ColorSolverCandidateSet>;
|
||||
|
||||
struct ColorSolverOrderedStackCandidateSet {
|
||||
using KdNode = ColorSolverCandidateSet::KdNode;
|
||||
|
||||
size_t component_count { 0 };
|
||||
int stack_depth { 0 };
|
||||
int simulated_stack_depth { 0 };
|
||||
std::vector<float> rgbs;
|
||||
std::vector<float> perceptual_coords;
|
||||
std::vector<uint16_t> stacks;
|
||||
std::vector<KdNode> kd_nodes;
|
||||
std::vector<KdNode> perceptual_kd_nodes;
|
||||
int kd_root { -1 };
|
||||
int perceptual_kd_root { -1 };
|
||||
|
||||
bool empty() const
|
||||
{
|
||||
return component_count == 0 || stack_depth <= 0 || rgbs.empty() || rgbs.size() % 3 != 0 ||
|
||||
stacks.size() != (rgbs.size() / 3) * size_t(stack_depth);
|
||||
}
|
||||
};
|
||||
|
||||
struct ColorSolverOrderedStackResult {
|
||||
std::vector<uint16_t> surface_to_deep;
|
||||
std::array<float, 3> rgb { { 0.f, 0.f, 0.f } };
|
||||
bool has_rgb { false };
|
||||
};
|
||||
|
||||
using ColorSolverOrderedStackCandidateCache = std::map<std::string, ColorSolverOrderedStackCandidateSet>;
|
||||
|
||||
ColorSolverMixModel color_solver_mix_model_from_index(int model);
|
||||
ColorSolverLookupMode color_solver_lookup_mode_from_index(int mode);
|
||||
ColorSolverMode color_solver_mode_from_index(int mode);
|
||||
|
||||
int color_solver_total_units_for_component_count(size_t component_count);
|
||||
size_t color_solver_candidate_count(size_t component_count, int total_units);
|
||||
|
||||
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<int> &weights,
|
||||
ColorSolverMixModel mix_model);
|
||||
std::array<float, 3> mix_color_solver_components(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &weights,
|
||||
ColorSolverMixModel mix_model);
|
||||
std::array<float, 3> mix_color_solver_ordered_stack(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<uint16_t> &surface_to_deep,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
std::array<float, 3> color_solver_oklab_from_srgb(const std::array<float, 3> &rgb);
|
||||
std::array<float, 3> color_solver_srgb_from_oklab(const std::array<float, 3> &oklab);
|
||||
|
||||
std::string color_solver_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
ColorSolverCandidateSet build_color_solver_candidates(const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
void build_color_solver_candidate_kd_trees(ColorSolverCandidateSet &candidates);
|
||||
const ColorSolverCandidateSet &color_solver_candidates(ColorSolverCandidateCache &cache,
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
ColorSolverMixModel mix_model,
|
||||
int total_units = 0);
|
||||
std::vector<float> solve_color_solver_weights_for_target(const ColorSolverCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverLookupMode lookup_mode,
|
||||
ColorSolverMode solver_mode);
|
||||
std::string color_solver_ordered_stack_candidate_cache_key(const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
ColorSolverOrderedStackCandidateSet build_color_solver_ordered_stack_candidates(
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
const ColorSolverOrderedStackCandidateSet &color_solver_ordered_stack_candidates(
|
||||
ColorSolverOrderedStackCandidateCache &cache,
|
||||
const std::vector<std::array<float, 3>> &component_colors,
|
||||
const std::vector<float> &layer_opacities,
|
||||
const std::array<float, 3> &background_rgb,
|
||||
ColorSolverMixModel mix_model,
|
||||
int stack_depth,
|
||||
int simulated_stack_depth = 0,
|
||||
size_t candidate_limit = 0,
|
||||
size_t stack_item_limit = 0,
|
||||
float surface_scatter = 0.f,
|
||||
bool beer_lambert_rgb_correction = false,
|
||||
bool td_effective_alpha_correction = false,
|
||||
const std::vector<ColorSolverStackComponentRole> &component_roles = {},
|
||||
bool beam_search_stack_expansion = false,
|
||||
const std::vector<float> &layer_opacities_by_depth = {},
|
||||
const ColorSolverCalibratedStackModel *calibrated_stack_model = nullptr,
|
||||
bool adaptive_spectral_correction = false);
|
||||
ColorSolverOrderedStackResult solve_color_solver_ordered_stack_result_for_target(
|
||||
const ColorSolverOrderedStackCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverMode solver_mode);
|
||||
std::vector<uint16_t> solve_color_solver_ordered_stack_for_target(
|
||||
const ColorSolverOrderedStackCandidateSet &candidates,
|
||||
const std::array<float, 3> &target_rgb,
|
||||
ColorSolverMode solver_mode);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
14
deps_src/pigment-painter/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(pigment_painter)
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
|
||||
add_library(pigment_painter STATIC
|
||||
lut_wide.png.c
|
||||
pigment_painter_mixer.cpp
|
||||
pigment_painter_mixer.hpp
|
||||
)
|
||||
|
||||
target_include_directories(pigment_painter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(pigment_painter PUBLIC cxx_std_17)
|
||||
target_link_libraries(pigment_painter PRIVATE PNG::PNG)
|
||||
674
deps_src/pigment-painter/COPYING
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
480199
deps_src/pigment-painter/lut_wide.png.c
Normal file
334
deps_src/pigment-painter/pigment_painter_mixer.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
#include "pigment_painter_mixer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <png.h>
|
||||
|
||||
extern "C" const char lut_wide_png_data[10295238];
|
||||
|
||||
namespace pigment_painter {
|
||||
namespace {
|
||||
|
||||
constexpr int k_lut_dimen = 256;
|
||||
constexpr size_t k_lut_png_size = sizeof(lut_wide_png_data);
|
||||
|
||||
struct PngMemoryReader
|
||||
{
|
||||
const std::uint8_t *data = nullptr;
|
||||
size_t size = 0;
|
||||
size_t offset = 0;
|
||||
};
|
||||
|
||||
struct LutImage
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int row_size = 0;
|
||||
std::vector<std::uint8_t> rgba;
|
||||
bool loaded = false;
|
||||
};
|
||||
|
||||
float clamp01(float value)
|
||||
{
|
||||
return std::max(0.f, std::min(1.f, value));
|
||||
}
|
||||
|
||||
float srgb_to_linear(float value)
|
||||
{
|
||||
const float x = clamp01(value);
|
||||
return x <= 0.04045f ? x / 12.92f : std::pow((x + 0.055f) / 1.055f, 2.4f);
|
||||
}
|
||||
|
||||
float linear_to_srgb(float value)
|
||||
{
|
||||
const float x = clamp01(value);
|
||||
return x <= 0.0031308f ? x * 12.92f : 1.055f * std::pow(x, 1.f / 2.4f) - 0.055f;
|
||||
}
|
||||
|
||||
void png_memory_read(png_structp png_ptr, png_bytep out, png_size_t bytes)
|
||||
{
|
||||
PngMemoryReader *reader = static_cast<PngMemoryReader *>(png_get_io_ptr(png_ptr));
|
||||
if (reader == nullptr || reader->offset + bytes > reader->size)
|
||||
png_error(png_ptr, "pigment painter lut read failed");
|
||||
|
||||
std::memcpy(out, reader->data + reader->offset, bytes);
|
||||
reader->offset += bytes;
|
||||
}
|
||||
|
||||
bool decode_lut_png(LutImage &image)
|
||||
{
|
||||
PngMemoryReader reader {
|
||||
reinterpret_cast<const std::uint8_t *>(lut_wide_png_data),
|
||||
k_lut_png_size,
|
||||
0
|
||||
};
|
||||
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (png == nullptr)
|
||||
return false;
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (info == nullptr) {
|
||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png))) {
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
png_set_read_fn(png, &reader, png_memory_read);
|
||||
png_read_info(png, info);
|
||||
|
||||
png_uint_32 width = png_get_image_width(png, info);
|
||||
png_uint_32 height = png_get_image_height(png, info);
|
||||
int bit_depth = png_get_bit_depth(png, info);
|
||||
int color_type = png_get_color_type(png, info);
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16(png);
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png);
|
||||
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png);
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png);
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_filler(png, 0xff, PNG_FILLER_AFTER);
|
||||
|
||||
png_read_update_info(png, info);
|
||||
|
||||
if (png_get_channels(png, info) != 4 || width % k_lut_dimen != 0 || height % k_lut_dimen != 0) {
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
image.width = int(width);
|
||||
image.height = int(height);
|
||||
image.row_size = image.width / k_lut_dimen;
|
||||
image.rgba.resize(size_t(width) * size_t(height) * 4);
|
||||
|
||||
std::vector<png_bytep> rows(height);
|
||||
for (png_uint_32 row = 0; row < height; ++row)
|
||||
rows[row] = image.rgba.data() + size_t(row) * size_t(width) * 4;
|
||||
|
||||
png_read_image(png, rows.data());
|
||||
png_read_end(png, nullptr);
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
|
||||
image.loaded = image.width == k_lut_dimen * image.row_size &&
|
||||
image.height >= k_lut_dimen * ((k_lut_dimen * 2 + image.row_size - 1) / image.row_size);
|
||||
return image.loaded;
|
||||
}
|
||||
|
||||
const LutImage *lut_image()
|
||||
{
|
||||
static LutImage image;
|
||||
static std::once_flag once;
|
||||
std::call_once(once, []() { decode_lut_png(image); });
|
||||
return image.loaded ? &image : nullptr;
|
||||
}
|
||||
|
||||
std::array<float, 3> sample_lut_texel(const LutImage &image, int x, int y, int z)
|
||||
{
|
||||
const int col = z % image.row_size;
|
||||
const int row = z / image.row_size;
|
||||
const int ix = x + col * k_lut_dimen;
|
||||
const int iy = y + row * k_lut_dimen;
|
||||
const size_t idx = (size_t(iy) * size_t(image.width) + size_t(ix)) * 4;
|
||||
|
||||
return {
|
||||
float(image.rgba[idx]) / 255.f,
|
||||
float(image.rgba[idx + 2]) / 255.f,
|
||||
float(image.rgba[idx + 1]) / 255.f
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 3> lerp_color(const std::array<float, 3> &a, const std::array<float, 3> &b, float t)
|
||||
{
|
||||
return {
|
||||
a[0] + (b[0] - a[0]) * t,
|
||||
a[1] + (b[1] - a[1]) * t,
|
||||
a[2] + (b[2] - a[2]) * t
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 3> sample_lut(const LutImage &image, const std::array<float, 3> &color, int z_offset)
|
||||
{
|
||||
const float xf = clamp01(color[0]) * float(k_lut_dimen - 1);
|
||||
const float yf = clamp01(color[1]) * float(k_lut_dimen - 1);
|
||||
const float zf = clamp01(color[2]) * float(k_lut_dimen - 1);
|
||||
|
||||
const int x0 = int(std::floor(xf));
|
||||
const int y0 = int(std::floor(yf));
|
||||
const int z0 = int(std::floor(zf));
|
||||
const int x1 = std::min(x0 + 1, k_lut_dimen - 1);
|
||||
const int y1 = std::min(y0 + 1, k_lut_dimen - 1);
|
||||
const int z1 = std::min(z0 + 1, k_lut_dimen - 1);
|
||||
|
||||
const float tx = xf - float(x0);
|
||||
const float ty = yf - float(y0);
|
||||
const float tz = zf - float(z0);
|
||||
|
||||
const std::array<float, 3> c000 = sample_lut_texel(image, x0, y0, z0 + z_offset);
|
||||
const std::array<float, 3> c100 = sample_lut_texel(image, x1, y0, z0 + z_offset);
|
||||
const std::array<float, 3> c010 = sample_lut_texel(image, x0, y1, z0 + z_offset);
|
||||
const std::array<float, 3> c110 = sample_lut_texel(image, x1, y1, z0 + z_offset);
|
||||
const std::array<float, 3> c001 = sample_lut_texel(image, x0, y0, z1 + z_offset);
|
||||
const std::array<float, 3> c101 = sample_lut_texel(image, x1, y0, z1 + z_offset);
|
||||
const std::array<float, 3> c011 = sample_lut_texel(image, x0, y1, z1 + z_offset);
|
||||
const std::array<float, 3> c111 = sample_lut_texel(image, x1, y1, z1 + z_offset);
|
||||
|
||||
const std::array<float, 3> c00 = lerp_color(c000, c100, tx);
|
||||
const std::array<float, 3> c10 = lerp_color(c010, c110, tx);
|
||||
const std::array<float, 3> c01 = lerp_color(c001, c101, tx);
|
||||
const std::array<float, 3> c11 = lerp_color(c011, c111, tx);
|
||||
const std::array<float, 3> c0 = lerp_color(c00, c10, ty);
|
||||
const std::array<float, 3> c1 = lerp_color(c01, c11, ty);
|
||||
return lerp_color(c0, c1, tz);
|
||||
}
|
||||
|
||||
std::array<float, 4> color_to_pigment(const LutImage &image, const std::array<float, 3> &color)
|
||||
{
|
||||
const std::array<float, 3> sampled = sample_lut(image, color, 0);
|
||||
std::array<float, 4> pigment {
|
||||
sampled[0],
|
||||
sampled[1],
|
||||
sampled[2],
|
||||
1.f - sampled[0] - sampled[1] - sampled[2]
|
||||
};
|
||||
|
||||
const float total = pigment[0] + pigment[1] + pigment[2] + pigment[3];
|
||||
if (total > 0.0001f) {
|
||||
const float inv_total = 1.f / total;
|
||||
for (float &value : pigment)
|
||||
value *= inv_total;
|
||||
}
|
||||
|
||||
return pigment;
|
||||
}
|
||||
|
||||
std::array<float, 3> pigment_to_color(const LutImage &image, const std::array<float, 4> &pigment)
|
||||
{
|
||||
return sample_lut(image, { pigment[0], pigment[1], pigment[2] }, k_lut_dimen);
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_with_lut(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights,
|
||||
const LutImage &image)
|
||||
{
|
||||
std::array<float, 4> mixed_pigment { 0.f, 0.f, 0.f, 0.f };
|
||||
std::array<float, 3> error { 0.f, 0.f, 0.f };
|
||||
float total_weight = 0.f;
|
||||
|
||||
for (const float weight : weights) {
|
||||
if (std::isfinite(weight) && weight > 0.f)
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if (total_weight <= 0.f)
|
||||
return colors.front();
|
||||
|
||||
const float inv_total_weight = 1.f / total_weight;
|
||||
for (size_t idx = 0; idx < colors.size(); ++idx) {
|
||||
const float raw_weight = weights[idx];
|
||||
if (!std::isfinite(raw_weight) || raw_weight <= 0.f)
|
||||
continue;
|
||||
|
||||
const float weight = raw_weight * inv_total_weight;
|
||||
const std::array<float, 4> pigment = color_to_pigment(image, colors[idx]);
|
||||
const std::array<float, 3> reconstructed = pigment_to_color(image, pigment);
|
||||
|
||||
for (size_t channel = 0; channel < 4; ++channel)
|
||||
mixed_pigment[channel] += pigment[channel] * weight;
|
||||
for (size_t channel = 0; channel < 3; ++channel)
|
||||
error[channel] += (clamp01(colors[idx][channel]) - reconstructed[channel]) * weight;
|
||||
}
|
||||
|
||||
const float pigment_total = mixed_pigment[0] + mixed_pigment[1] + mixed_pigment[2] + mixed_pigment[3];
|
||||
if (pigment_total > 0.0001f) {
|
||||
const float inv_pigment_total = 1.f / pigment_total;
|
||||
for (float &value : mixed_pigment)
|
||||
value *= inv_pigment_total;
|
||||
}
|
||||
|
||||
std::array<float, 3> mixed_color = pigment_to_color(image, mixed_pigment);
|
||||
for (size_t channel = 0; channel < 3; ++channel)
|
||||
mixed_color[channel] = clamp01(mixed_color[channel] + error[channel]);
|
||||
|
||||
return mixed_color;
|
||||
}
|
||||
|
||||
float reflectance_to_ks(float reflectance)
|
||||
{
|
||||
constexpr float k_min_reflectance = 0.02f;
|
||||
const float r = std::clamp(reflectance, k_min_reflectance, 1.f);
|
||||
return ((1.f - r) * (1.f - r)) / (2.f * r);
|
||||
}
|
||||
|
||||
float ks_to_reflectance(float ks)
|
||||
{
|
||||
const float ratio = std::max(0.f, ks);
|
||||
return clamp01(1.f + ratio - std::sqrt(std::max(0.f, ratio * ratio + 2.f * ratio)));
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_linear_reflectance(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights)
|
||||
{
|
||||
if (colors.empty() || colors.size() != weights.size())
|
||||
return { 0.f, 0.f, 0.f };
|
||||
|
||||
if (const LutImage *image = lut_image(); image != nullptr)
|
||||
return mix_with_lut(colors, weights, *image);
|
||||
|
||||
std::array<float, 3> accumulated_ks { 0.f, 0.f, 0.f };
|
||||
float total_weight = 0.f;
|
||||
for (size_t idx = 0; idx < colors.size(); ++idx) {
|
||||
const float weight = std::max(0.f, weights[idx]);
|
||||
if (!std::isfinite(weight) || weight <= 0.f)
|
||||
continue;
|
||||
|
||||
accumulated_ks[0] += reflectance_to_ks(srgb_to_linear(colors[idx][0])) * weight;
|
||||
accumulated_ks[1] += reflectance_to_ks(srgb_to_linear(colors[idx][1])) * weight;
|
||||
accumulated_ks[2] += reflectance_to_ks(srgb_to_linear(colors[idx][2])) * weight;
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if (total_weight <= 0.f)
|
||||
return colors.front();
|
||||
|
||||
const float inv_total = 1.f / total_weight;
|
||||
return {
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[0] * inv_total)),
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[1] * inv_total)),
|
||||
linear_to_srgb(ks_to_reflectance(accumulated_ks[2] * inv_total))
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights)
|
||||
{
|
||||
return mix_linear_reflectance(colors, weights);
|
||||
}
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<int> &weights)
|
||||
{
|
||||
std::vector<float> float_weights;
|
||||
float_weights.reserve(weights.size());
|
||||
for (const int weight : weights)
|
||||
float_weights.emplace_back(float(std::max(0, weight)));
|
||||
return mix_linear_reflectance(colors, float_weights);
|
||||
}
|
||||
|
||||
} // namespace pigment_painter
|
||||
17
deps_src/pigment-painter/pigment_painter_mixer.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef PIGMENT_PAINTER_MIXER_HPP
|
||||
#define PIGMENT_PAINTER_MIXER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
namespace pigment_painter {
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<float> &weights);
|
||||
|
||||
std::array<float, 3> mix_srgb(const std::vector<std::array<float, 3>> &colors,
|
||||
const std::vector<int> &weights);
|
||||
|
||||
} // namespace pigment_painter
|
||||
|
||||
#endif
|
||||
10
deps_src/prusa-fdm-mixer/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(prusa_fdm_mixer)
|
||||
|
||||
add_library(prusa_fdm_mixer STATIC
|
||||
prusa_fdm_mixer.cpp
|
||||
prusa_fdm_mixer.hpp
|
||||
)
|
||||
|
||||
target_include_directories(prusa_fdm_mixer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_features(prusa_fdm_mixer PUBLIC cxx_std_17)
|
||||
21
deps_src/prusa-fdm-mixer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Ondrej Bartas (Prusa Research s.r.o.) and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
351
deps_src/prusa-fdm-mixer/prusa_fdm_mixer.cpp
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* prusa_fdm_mixer.cpp — Implementation of the prusa-fdm-mixer color mixing model.
|
||||
*
|
||||
* Copyright (c) Prusa Research s.r.o.
|
||||
* MIT License — see LICENSE.
|
||||
*/
|
||||
|
||||
#include "prusa_fdm_mixer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace prusa_fdm_mixer {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double PI = 3.14159265358979323846;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* sRGB <-> linear RGB
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
inline double srgb_to_linear(double c) {
|
||||
c = c / 255.0;
|
||||
if (c <= 0.04045) return c / 12.92;
|
||||
return std::pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
inline double linear_to_srgb(double c) {
|
||||
c = std::clamp(c, 0.0, 1.0);
|
||||
double v;
|
||||
if (c <= 0.0031308) v = 12.92 * c;
|
||||
else v = 1.055 * std::pow(c, 1.0 / 2.4) - 0.055;
|
||||
return v * 255.0;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* sRGB <-> XYZ (D65)
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
struct XYZ { double x, y, z; };
|
||||
|
||||
inline XYZ rgb_to_xyz(const RGB& rgb) {
|
||||
const double r = srgb_to_linear(rgb.r);
|
||||
const double g = srgb_to_linear(rgb.g);
|
||||
const double b = srgb_to_linear(rgb.b);
|
||||
return XYZ{
|
||||
(r * 0.4124564 + g * 0.3575761 + b * 0.1804375) * 100.0,
|
||||
(r * 0.2126729 + g * 0.7151522 + b * 0.0721750) * 100.0,
|
||||
(r * 0.0193339 + g * 0.1191920 + b * 0.9503041) * 100.0,
|
||||
};
|
||||
}
|
||||
|
||||
inline RGB xyz_to_rgb(const XYZ& xyz) {
|
||||
const double x = xyz.x / 100.0;
|
||||
const double y = xyz.y / 100.0;
|
||||
const double z = xyz.z / 100.0;
|
||||
const double r = linear_to_srgb(x * 3.2404542 + y * -1.5371385 + z * -0.4985314);
|
||||
const double g = linear_to_srgb(x * -0.9692660 + y * 1.8760108 + z * 0.0415560);
|
||||
const double b = linear_to_srgb(x * 0.0556434 + y * -0.2040259 + z * 1.0572252);
|
||||
auto clamp_round = [](double v) -> std::uint8_t {
|
||||
const long iv = std::lround(v);
|
||||
return static_cast<std::uint8_t>(std::clamp<long>(iv, 0, 255));
|
||||
};
|
||||
return RGB{ clamp_round(r), clamp_round(g), clamp_round(b) };
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* XYZ <-> CIELAB (D65)
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
constexpr double XN = 95.047;
|
||||
constexpr double YN = 100.0;
|
||||
constexpr double ZN = 108.883;
|
||||
|
||||
inline double lab_f(double t) {
|
||||
return (t > 0.008856) ? std::cbrt(t) : (7.787 * t + 16.0 / 116.0);
|
||||
}
|
||||
|
||||
inline double lab_f_inv(double t) {
|
||||
return (t > 0.206893) ? (t * t * t) : ((t - 16.0 / 116.0) / 7.787);
|
||||
}
|
||||
|
||||
inline LAB xyz_to_lab(const XYZ& xyz) {
|
||||
const double fx = lab_f(xyz.x / XN);
|
||||
const double fy = lab_f(xyz.y / YN);
|
||||
const double fz = lab_f(xyz.z / ZN);
|
||||
return LAB{ 116.0 * fy - 16.0, 500.0 * (fx - fy), 200.0 * (fy - fz) };
|
||||
}
|
||||
|
||||
inline XYZ lab_to_xyz(const LAB& lab) {
|
||||
const double fy = (lab.L + 16.0) / 116.0;
|
||||
const double fx = lab.a / 500.0 + fy;
|
||||
const double fz = fy - lab.b / 200.0;
|
||||
return XYZ{ XN * lab_f_inv(fx), YN * lab_f_inv(fy), ZN * lab_f_inv(fz) };
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* ============================================================================
|
||||
* Public color-space helpers
|
||||
* ============================================================================ */
|
||||
|
||||
RGB hex_to_rgb(const std::string& hex) {
|
||||
std::string s = hex;
|
||||
if (!s.empty() && s[0] == '#') s.erase(0, 1);
|
||||
if (s.size() != 6) {
|
||||
throw std::invalid_argument("prusa_fdm_mixer::hex_to_rgb: expected 6 hex digits, got: " + hex);
|
||||
}
|
||||
auto parse_byte = [&](size_t off) -> std::uint8_t {
|
||||
unsigned int v = 0;
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
char c = s[off + i];
|
||||
unsigned int d;
|
||||
if (c >= '0' && c <= '9') d = static_cast<unsigned int>(c - '0');
|
||||
else if (c >= 'a' && c <= 'f') d = 10u + static_cast<unsigned int>(c - 'a');
|
||||
else if (c >= 'A' && c <= 'F') d = 10u + static_cast<unsigned int>(c - 'A');
|
||||
else throw std::invalid_argument("prusa_fdm_mixer::hex_to_rgb: invalid hex char in: " + hex);
|
||||
v = (v << 4) | d;
|
||||
}
|
||||
return static_cast<std::uint8_t>(v);
|
||||
};
|
||||
return RGB{ parse_byte(0), parse_byte(2), parse_byte(4) };
|
||||
}
|
||||
|
||||
std::string rgb_to_hex(const RGB& rgb) {
|
||||
char buf[8];
|
||||
std::snprintf(buf, sizeof(buf), "#%02x%02x%02x",
|
||||
static_cast<unsigned int>(rgb.r),
|
||||
static_cast<unsigned int>(rgb.g),
|
||||
static_cast<unsigned int>(rgb.b));
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
LAB rgb_to_lab(const RGB& rgb) {
|
||||
return xyz_to_lab(rgb_to_xyz(rgb));
|
||||
}
|
||||
|
||||
RGB lab_to_rgb(const LAB& lab) {
|
||||
return xyz_to_rgb(lab_to_xyz(lab));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* prusa-fdm-mixer model
|
||||
* ============================================================================ */
|
||||
|
||||
namespace {
|
||||
|
||||
// Empirically fit constants — see companion repo for derivation.
|
||||
constexpr double YN_EXPONENT = 3.0;
|
||||
constexpr double L_SLOPE = -0.0477;
|
||||
constexpr double L_INTERCEPT = -2.112;
|
||||
constexpr double L_KINK_THRESHOLD = 15.0;
|
||||
constexpr double L_KINK_SLOPE = -0.060;
|
||||
constexpr double C_SLOPE = 0.2780;
|
||||
constexpr double C_INTERCEPT = -15.580;
|
||||
constexpr double PEAK_STRENGTH = 1.375;
|
||||
constexpr double HUE_PEAK_DEG = 10.38;
|
||||
constexpr double HUE_CENTER_DEG = 210.0;
|
||||
constexpr double HUE_HALF_WIDTH_DEG = 30.0;
|
||||
|
||||
LAB predict_lab(const std::vector<Part>& parts) {
|
||||
if (parts.empty()) {
|
||||
throw std::invalid_argument("prusa_fdm_mixer::mix: empty parts list");
|
||||
}
|
||||
|
||||
// ---- Gradient safety: if any single part has ratio >= ~1, return it directly. ----
|
||||
// Without this guard, the constant lightness correction (-2.112 * cs) would
|
||||
// perturb the predicted color even at the endpoints of a gradient.
|
||||
for (const Part& p : parts) {
|
||||
if (p.ratio >= 0.9999) {
|
||||
return rgb_to_lab(hex_to_rgb(p.hex));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Step 1: Yule-Nielsen base prediction ----
|
||||
double r_acc = 0.0, g_acc = 0.0, b_acc = 0.0;
|
||||
std::vector<double> component_Ls;
|
||||
component_Ls.reserve(parts.size());
|
||||
|
||||
const double inv_n = 1.0 / YN_EXPONENT;
|
||||
for (const Part& p : parts) {
|
||||
const RGB rgb = hex_to_rgb(p.hex);
|
||||
const double r_lin = srgb_to_linear(rgb.r);
|
||||
const double g_lin = srgb_to_linear(rgb.g);
|
||||
const double b_lin = srgb_to_linear(rgb.b);
|
||||
r_acc += std::pow(r_lin, inv_n) * p.ratio;
|
||||
g_acc += std::pow(g_lin, inv_n) * p.ratio;
|
||||
b_acc += std::pow(b_lin, inv_n) * p.ratio;
|
||||
component_Ls.push_back(rgb_to_lab(rgb).L);
|
||||
}
|
||||
|
||||
auto pow_clamped = [](double v, double exp) {
|
||||
return std::pow(std::max(0.0, v), exp);
|
||||
};
|
||||
const double yn_r = linear_to_srgb(pow_clamped(r_acc, YN_EXPONENT));
|
||||
const double yn_g = linear_to_srgb(pow_clamped(g_acc, YN_EXPONENT));
|
||||
const double yn_b = linear_to_srgb(pow_clamped(b_acc, YN_EXPONENT));
|
||||
|
||||
auto clamp_round = [](double v) -> std::uint8_t {
|
||||
const long iv = std::lround(v);
|
||||
return static_cast<std::uint8_t>(std::clamp<long>(iv, 0, 255));
|
||||
};
|
||||
const RGB yn_rgb{ clamp_round(yn_r), clamp_round(yn_g), clamp_round(yn_b) };
|
||||
const LAB base = rgb_to_lab(yn_rgb);
|
||||
|
||||
// ---- Bell-curve weight ----
|
||||
// For N components: w = N^N * prod(ratios)
|
||||
// = 1 when ratios are equal (peak correction zone)
|
||||
// = 0 at any endpoint (single-component, no correction needed)
|
||||
double w = 1.0;
|
||||
for (const Part& p : parts) w *= p.ratio;
|
||||
w *= std::pow(static_cast<double>(parts.size()),
|
||||
static_cast<double>(parts.size()));
|
||||
w = std::clamp(w, 0.0, 1.0);
|
||||
const double cs = w * PEAK_STRENGTH;
|
||||
|
||||
// ---- Step 2: piecewise lightness correction ----
|
||||
const double L_min = *std::min_element(component_Ls.begin(), component_Ls.end());
|
||||
const double L_max = *std::max_element(component_Ls.begin(), component_Ls.end());
|
||||
const double L_gap = L_max - L_min;
|
||||
|
||||
double L_corr = L_SLOPE * L_gap + L_INTERCEPT;
|
||||
if (L_gap > L_KINK_THRESHOLD) {
|
||||
L_corr += L_KINK_SLOPE * (L_gap - L_KINK_THRESHOLD);
|
||||
}
|
||||
L_corr *= cs;
|
||||
const double L_new = base.L + L_corr;
|
||||
|
||||
// ---- Step 3: chroma correction ----
|
||||
double a_out = base.a;
|
||||
double b_out = base.b;
|
||||
const double pred_C = std::hypot(base.a, base.b);
|
||||
if (pred_C >= 0.01) {
|
||||
const double target_dC = (C_SLOPE * L_new + C_INTERCEPT) * cs;
|
||||
const double new_C = std::max(0.0, pred_C + target_dC);
|
||||
const double scale = new_C / pred_C;
|
||||
a_out = base.a * scale;
|
||||
b_out = base.b * scale;
|
||||
}
|
||||
|
||||
// ---- Step 4: cyan-band hue rotation ----
|
||||
const double new_C_final = std::hypot(a_out, b_out);
|
||||
if (new_C_final >= 1.0) {
|
||||
double pred_h = std::atan2(b_out, a_out) * 180.0 / PI;
|
||||
if (pred_h < 0.0) pred_h += 360.0;
|
||||
|
||||
double h_corr = 0.0;
|
||||
if (pred_h >= HUE_CENTER_DEG - HUE_HALF_WIDTH_DEG &&
|
||||
pred_h < HUE_CENTER_DEG + HUE_HALF_WIDTH_DEG) {
|
||||
const double dist = std::abs(pred_h - HUE_CENTER_DEG);
|
||||
const double falloff = std::max(0.0, 1.0 - dist / HUE_HALF_WIDTH_DEG);
|
||||
h_corr = HUE_PEAK_DEG * falloff * w;
|
||||
}
|
||||
if (h_corr != 0.0) {
|
||||
double new_h = std::fmod(pred_h + h_corr, 360.0);
|
||||
if (new_h < 0.0) new_h += 360.0;
|
||||
const double new_h_rad = new_h * PI / 180.0;
|
||||
a_out = new_C_final * std::cos(new_h_rad);
|
||||
b_out = new_C_final * std::sin(new_h_rad);
|
||||
}
|
||||
}
|
||||
|
||||
return LAB{ L_new, a_out, b_out };
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
RGB mix_rgb(const std::vector<Part>& parts) {
|
||||
return lab_to_rgb(predict_lab(parts));
|
||||
}
|
||||
|
||||
std::string mix(const std::vector<Part>& parts) {
|
||||
return rgb_to_hex(mix_rgb(parts));
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* ΔE2000
|
||||
* ============================================================================ */
|
||||
|
||||
double delta_e_2000(const LAB& lab1, const LAB& lab2) {
|
||||
const double L1 = lab1.L, a1 = lab1.a, b1 = lab1.b;
|
||||
const double L2 = lab2.L, a2 = lab2.a, b2 = lab2.b;
|
||||
|
||||
const double avg_L = (L1 + L2) / 2.0;
|
||||
const double C1 = std::hypot(a1, b1);
|
||||
const double C2 = std::hypot(a2, b2);
|
||||
const double avg_C = (C1 + C2) / 2.0;
|
||||
const double avg_C7 = std::pow(avg_C, 7.0);
|
||||
const double pow25_7 = std::pow(25.0, 7.0);
|
||||
const double G = 0.5 * (1.0 - std::sqrt(avg_C7 / (avg_C7 + pow25_7)));
|
||||
|
||||
const double a1p = a1 * (1.0 + G);
|
||||
const double a2p = a2 * (1.0 + G);
|
||||
const double C1p = std::hypot(a1p, b1);
|
||||
const double C2p = std::hypot(a2p, b2);
|
||||
const double avg_Cp = (C1p + C2p) / 2.0;
|
||||
|
||||
auto deg = [](double x) {
|
||||
double d = x * 180.0 / PI;
|
||||
if (d < 0.0) d += 360.0;
|
||||
return d;
|
||||
};
|
||||
const double h1p = deg(std::atan2(b1, a1p));
|
||||
const double h2p = deg(std::atan2(b2, a2p));
|
||||
|
||||
double dHp_diff = h2p - h1p;
|
||||
double dhp = 0.0;
|
||||
if (C1p * C2p != 0.0) {
|
||||
if (std::abs(dHp_diff) <= 180.0) dhp = dHp_diff;
|
||||
else if (dHp_diff > 180.0) dhp = dHp_diff - 360.0;
|
||||
else dhp = dHp_diff + 360.0;
|
||||
}
|
||||
|
||||
double avg_Hp;
|
||||
if (C1p * C2p == 0.0) avg_Hp = h1p + h2p;
|
||||
else if (std::abs(h1p - h2p) <= 180.0) avg_Hp = (h1p + h2p) / 2.0;
|
||||
else if (h1p + h2p < 360.0) avg_Hp = (h1p + h2p + 360.0) / 2.0;
|
||||
else avg_Hp = (h1p + h2p - 360.0) / 2.0;
|
||||
|
||||
const double T = 1.0
|
||||
- 0.17 * std::cos((avg_Hp - 30.0) * PI / 180.0)
|
||||
+ 0.24 * std::cos((2.0 * avg_Hp) * PI / 180.0)
|
||||
+ 0.32 * std::cos((3.0 * avg_Hp + 6.0) * PI / 180.0)
|
||||
- 0.20 * std::cos((4.0 * avg_Hp - 63.0) * PI / 180.0);
|
||||
|
||||
const double dLp = L2 - L1;
|
||||
const double dCp = C2p - C1p;
|
||||
const double dHpFinal = 2.0 * std::sqrt(C1p * C2p) * std::sin(dhp * PI / 360.0);
|
||||
|
||||
const double SL = 1.0 + (0.015 * std::pow(avg_L - 50.0, 2.0))
|
||||
/ std::sqrt(20.0 + std::pow(avg_L - 50.0, 2.0));
|
||||
const double SC = 1.0 + 0.045 * avg_Cp;
|
||||
const double SH = 1.0 + 0.015 * avg_Cp * T;
|
||||
|
||||
const double dTheta = 30.0 * std::exp(-std::pow((avg_Hp - 275.0) / 25.0, 2.0));
|
||||
const double avg_Cp7 = std::pow(avg_Cp, 7.0);
|
||||
const double RC = 2.0 * std::sqrt(avg_Cp7 / (avg_Cp7 + pow25_7));
|
||||
const double RT = -RC * std::sin(2.0 * dTheta * PI / 180.0);
|
||||
|
||||
return std::sqrt(
|
||||
std::pow(dLp / SL, 2.0) +
|
||||
std::pow(dCp / SC, 2.0) +
|
||||
std::pow(dHpFinal / SH, 2.0) +
|
||||
RT * (dCp / SC) * (dHpFinal / SH)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace prusa_fdm_mixer
|
||||
99
deps_src/prusa-fdm-mixer/prusa_fdm_mixer.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* prusa_fdm_mixer.hpp — Predicts the apparent color of FDM filament mixes.
|
||||
*
|
||||
* The prusa-fdm-mixer model, calibrated against measured prints. Produces
|
||||
* predictions with a median ΔE2000 of ~5.7 against real samples
|
||||
* (Linear RGB ~14.5 for comparison).
|
||||
*
|
||||
* Drop-in usage in PrusaSlicer / OrcaSlicer / any C++17 project:
|
||||
*
|
||||
* #include "prusa_fdm_mixer.hpp"
|
||||
*
|
||||
* std::vector<prusa_fdm_mixer::Part> parts = {
|
||||
* { "#007a9d", 0.75 }, // 75% azure blue
|
||||
* { "#f6b921", 0.25 }, // 25% yellow
|
||||
* };
|
||||
* std::string mixed_hex = prusa_fdm_mixer::mix(parts);
|
||||
* // mixed_hex == "#XXXXXX" — predicted apparent color
|
||||
*
|
||||
* Properties:
|
||||
* - Gradient-safe: a part with ratio 1.0 returns that part's hex exactly.
|
||||
* - Smooth: small ratio changes produce small color changes.
|
||||
* - Works for 2 or 3+ components; ratios should sum to 1.
|
||||
*
|
||||
* No external dependencies beyond the C++ standard library.
|
||||
*
|
||||
* Copyright (c) Prusa Research s.r.o.
|
||||
* MIT License — see LICENSE.
|
||||
*/
|
||||
|
||||
#ifndef PRUSA_FDM_MIXER_HPP
|
||||
#define PRUSA_FDM_MIXER_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace prusa_fdm_mixer {
|
||||
|
||||
/** A single filament component in a mix. */
|
||||
struct Part {
|
||||
/** 6-digit hex color including leading '#', e.g. "#aabbcc". */
|
||||
std::string hex;
|
||||
/** Mixing ratio in [0, 1]. Ratios across all parts should sum to 1. */
|
||||
double ratio;
|
||||
};
|
||||
|
||||
/** RGB triple, channel values in [0, 255]. */
|
||||
struct RGB {
|
||||
std::uint8_t r;
|
||||
std::uint8_t g;
|
||||
std::uint8_t b;
|
||||
};
|
||||
|
||||
/** CIELAB triple — L in [0, 100], a/b unbounded but typically [-128, 127]. */
|
||||
struct LAB {
|
||||
double L;
|
||||
double a;
|
||||
double b;
|
||||
};
|
||||
|
||||
/* ============================================================================
|
||||
* Public API
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Predict the apparent color when filaments are mixed in the given ratios.
|
||||
* @param parts Two or more parts; ratios should sum to ~1.
|
||||
* @return Predicted hex color, e.g. "#aabbcc".
|
||||
*/
|
||||
std::string mix(const std::vector<Part>& parts);
|
||||
|
||||
/**
|
||||
* Same as mix() but returns an RGB struct.
|
||||
*/
|
||||
RGB mix_rgb(const std::vector<Part>& parts);
|
||||
|
||||
/* ============================================================================
|
||||
* Color-space conversions (exposed for callers who want them)
|
||||
* ============================================================================ */
|
||||
|
||||
/** Parse a "#rrggbb" hex string. Throws std::invalid_argument on bad input. */
|
||||
RGB hex_to_rgb(const std::string& hex);
|
||||
|
||||
/** Format an RGB as "#rrggbb" lowercase. */
|
||||
std::string rgb_to_hex(const RGB& rgb);
|
||||
|
||||
/** Convert sRGB (0-255 channels) to CIELAB (D65 white point). */
|
||||
LAB rgb_to_lab(const RGB& rgb);
|
||||
|
||||
/** Convert CIELAB (D65 white point) back to sRGB (0-255 channels, clamped). */
|
||||
RGB lab_to_rgb(const LAB& lab);
|
||||
|
||||
/** ΔE2000 perceptual color difference between two LAB colors. */
|
||||
double delta_e_2000(const LAB& lab1, const LAB& lab2);
|
||||
|
||||
} // namespace prusa_fdm_mixer
|
||||
|
||||
#endif // PRUSA_FDM_MIXER_HPP
|
||||
8
deps_src/tinygltf/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
add_library(tinygltf_v3 STATIC tiny_gltf_v3.c)
|
||||
|
||||
target_include_directories(tinygltf_v3 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set_target_properties(tinygltf_v3 PROPERTIES
|
||||
C_STANDARD 11
|
||||
C_STANDARD_REQUIRED ON
|
||||
)
|
||||
21
deps_src/tinygltf/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Syoyo Fujita, Aurélien Chatelain and many contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3278
deps_src/tinygltf/tiny_gltf_v3.c
Normal file
4477
deps_src/tinygltf/tiny_gltf_v3.h
Normal file
1024
deps_src/tinygltf/tinygltf_json_c.h
Normal file
@@ -5638,7 +5638,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -6126,8 +6126,8 @@ msgstr "Desa el projecte com a"
|
||||
msgid "Save current project as"
|
||||
msgstr "Desar el projecte actual com"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar un model"
|
||||
|
||||
@@ -6048,8 +6048,8 @@ msgstr "Uložit projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Uložit aktuální projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importovat 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Načíst model"
|
||||
|
||||
@@ -6219,8 +6219,8 @@ msgstr "Projekt speichern als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Aktuelles Projekt speichern als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importiere 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Lade ein Modell"
|
||||
|
||||
@@ -5695,7 +5695,7 @@ msgstr ""
|
||||
msgid "Save current project as"
|
||||
msgstr ""
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load a model"
|
||||
|
||||
@@ -6206,8 +6206,8 @@ msgstr "Guardar proyecto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Guardar el proyecto actual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Cargar un modelo"
|
||||
|
||||
@@ -6227,8 +6227,8 @@ msgstr "Enregistrer le projet sous"
|
||||
msgid "Save current project as"
|
||||
msgstr "Enregistrer le projet actuel sous"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importer des fichiers 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Charger un modèle"
|
||||
|
||||
@@ -6124,8 +6124,8 @@ msgstr "Projekt mentése másként"
|
||||
msgid "Save current project as"
|
||||
msgstr "Jelenlegi projekt mentése másként"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importálás 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Modell betöltése"
|
||||
|
||||
@@ -6179,8 +6179,8 @@ msgstr "Salva progetto con nome"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salva progetto corrente con nome"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importa 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carica modello"
|
||||
|
||||
@@ -5981,8 +5981,8 @@ msgstr "プロジェクトを名前を付けて保存"
|
||||
msgid "Save current project as"
|
||||
msgstr "プロジェクトを名前を付けて保存"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMFをインポート"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMFをインポート"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "モデルを読み込む"
|
||||
|
||||
@@ -5959,8 +5959,8 @@ msgstr "프로젝트 다른 이름으로 저장"
|
||||
msgid "Save current project as"
|
||||
msgstr "현재 프로젝트 다른 이름으로 저장"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF 가져오기"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF 가져오기"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "모델 불러오기"
|
||||
|
||||
@@ -6085,8 +6085,8 @@ msgstr "Išsaugoti projektą kaip"
|
||||
msgid "Save current project as"
|
||||
msgstr "Išsaugoti dabartinį projektą kaip"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuoti 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Įkelti modelį"
|
||||
|
||||
@@ -6072,8 +6072,8 @@ msgstr "Bewaar project als"
|
||||
msgid "Save current project as"
|
||||
msgstr "Bewaar huidig project als"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF importeren"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF importeren"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Laad een model"
|
||||
|
||||
@@ -6043,8 +6043,8 @@ msgstr "Zapisz projekt jako"
|
||||
msgid "Save current project as"
|
||||
msgstr "Zapisz bieżący projekt jako"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importuj 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Wczytaj model"
|
||||
|
||||
@@ -6143,8 +6143,8 @@ msgstr "Salvar projeto como"
|
||||
msgid "Save current project as"
|
||||
msgstr "Salvar o projeto atual como"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importar 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Carregar um modelo"
|
||||
|
||||
@@ -6265,8 +6265,8 @@ msgstr "Сохранить проект как"
|
||||
msgid "Save current project as"
|
||||
msgstr "Сохранить текущий проект как"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Импорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Загрузка модели"
|
||||
|
||||
@@ -6041,8 +6041,8 @@ msgstr "Spara Projekt som"
|
||||
msgid "Save current project as"
|
||||
msgstr "Spara nuvarande projekt som"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Importera 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Ladda modell"
|
||||
|
||||
@@ -6062,8 +6062,8 @@ msgstr "Projeyi farklı kaydet"
|
||||
msgid "Save current project as"
|
||||
msgstr "Mevcut projeyi farklı kaydet"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/AMF'yi içe aktar"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF'yi içe aktar"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Model yükle"
|
||||
|
||||
@@ -5950,8 +5950,8 @@ msgstr "Зберегти проєкт як"
|
||||
msgid "Save current project as"
|
||||
msgstr "Зберегти поточний проєкт як"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Імпорт 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Завантажте модель"
|
||||
|
||||
@@ -6003,8 +6003,8 @@ msgstr "Lưu dự án thành"
|
||||
msgid "Save current project as"
|
||||
msgstr "Lưu dự án hiện tại thành"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "Nhập 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "Tải model"
|
||||
|
||||
@@ -5848,8 +5848,8 @@ msgstr "项目另存为"
|
||||
msgid "Save current project as"
|
||||
msgstr "项目另存为"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "导入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "加载模型"
|
||||
|
||||
@@ -5916,8 +5916,8 @@ msgstr "另存專案為"
|
||||
msgid "Save current project as"
|
||||
msgstr "將目前專案另存為"
|
||||
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/AMF"
|
||||
msgid "Import 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
msgstr "匯入 3MF/STL/STEP/SVG/OBJ/GLTF/GLB/AMF"
|
||||
|
||||
msgid "Load a model"
|
||||
msgstr "載入模型"
|
||||
|
||||
BIN
resources/images/OrcaSlicer_gradient.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/OrcaSlicer_gradient_circle.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/OrcaSlicer_gradient_narrow.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
resources/images/OrcaSlicer_gray.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
61
resources/images/image_projection.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="image_projection.svg"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="8.1972734"
|
||||
inkscape:cx="25.374291"
|
||||
inkscape:cy="23.971385"
|
||||
inkscape:window-width="1135"
|
||||
inkscape:window-height="616"
|
||||
inkscape:window-x="555"
|
||||
inkscape:window-y="322"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg17" />
|
||||
<defs
|
||||
id="defs17" />
|
||||
<rect
|
||||
style="fill:#77d1ff;fill-opacity:1;stroke:#2b3436;stroke-opacity:1"
|
||||
id="rect20"
|
||||
width="19.864098"
|
||||
height="14.551606"
|
||||
x="3.9266238"
|
||||
y="5.0815129" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#2b3436;stroke-opacity:1"
|
||||
d="m 26.446966,11.89536 v 17.136303 l 8.376353,4.936095 0.05434,-17.914796 z"
|
||||
id="path20"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
d="m 5.9484579,16.541602 4.1557721,-6.192915 3.748343,6.274401 3.422401,-3.748343 3.177943,3.829829 z"
|
||||
id="path22" />
|
||||
<path
|
||||
style="fill:#17eac5;fill-opacity:1;stroke:#2b3436;stroke-opacity:1"
|
||||
d="M 25.535042,12.035433 8.0178771,22.738372 8.0539714,38.543239 25.595623,28.782985 Z"
|
||||
id="path21"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<circle
|
||||
style="fill:#fcff00;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="path23"
|
||||
cx="16.94903"
|
||||
cy="9.0449152"
|
||||
r="3.259429" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
61
resources/images/image_projection_dark.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="image_projection_dark.svg"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="8.1972734"
|
||||
inkscape:cx="25.374291"
|
||||
inkscape:cy="23.971385"
|
||||
inkscape:window-width="1135"
|
||||
inkscape:window-height="616"
|
||||
inkscape:window-x="555"
|
||||
inkscape:window-y="322"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg17" />
|
||||
<defs
|
||||
id="defs17" />
|
||||
<rect
|
||||
style="fill:#77d1ff;fill-opacity:1;stroke:#b6b6b6;stroke-opacity:1"
|
||||
id="rect20"
|
||||
width="19.864098"
|
||||
height="14.551606"
|
||||
x="3.9266238"
|
||||
y="5.0815129" />
|
||||
<path
|
||||
style="fill:none;fill-opacity:1;stroke:#b6b6b6;stroke-opacity:1"
|
||||
d="m 26.446966,11.89536 v 17.136303 l 8.376353,4.936095 0.05434,-17.914796 z"
|
||||
id="path20"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
d="m 5.9484579,16.541602 4.1557721,-6.192915 3.748343,6.274401 3.422401,-3.748343 3.177943,3.829829 z"
|
||||
id="path22" />
|
||||
<path
|
||||
style="fill:#17eac5;fill-opacity:1;stroke:#b6b6b6;stroke-opacity:1"
|
||||
d="M 25.535042,12.035433 8.0178771,22.738372 8.0539714,38.543239 25.595623,28.782985 Z"
|
||||
id="path21"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<circle
|
||||
style="fill:#fcff00;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="path23"
|
||||
cx="16.94903"
|
||||
cy="9.0449152"
|
||||
r="3.259429" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
89
resources/images/true_color_painting.svg
Normal file
@@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="true_color_painting.svg"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="11.115146"
|
||||
inkscape:cx="25.460755"
|
||||
inkscape:cy="19.657862"
|
||||
inkscape:window-width="1219"
|
||||
inkscape:window-height="718"
|
||||
inkscape:window-x="447"
|
||||
inkscape:window-y="116"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg17"
|
||||
showguides="true">
|
||||
<sodipodi:guide
|
||||
position="55.149975,1.2240794"
|
||||
orientation="0,-1"
|
||||
id="guide1"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<defs
|
||||
id="defs17" />
|
||||
<path
|
||||
d="M27.878,34.926h-4.4c-.509,0-.845,0-.955-.394a.593.593,0,0,1,.36-.67,2.3,2.3,0,0,0,1.2-2.189,3.253,3.253,0,1,1,3.8,3.206Zm-3.538-1h2.988a2.253,2.253,0,1,0-2.253-2.253A3.445,3.445,0,0,1,24.34,33.926Z"
|
||||
style="fill:#009688"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 30.5,30.289 -0.846,-0.535 4.364,-6.9 a 0.222,0.222 0 0 0 -0.36,-0.258 l -5.4,6.644 -0.776,-0.631 5.4,-6.644 a 1.222,1.222 0 0 1 1.982,1.423 z"
|
||||
style="fill:#009688"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M33.5,35h-1a.5.5,0,0,1,0-1h1a.5.5,0,0,0,.5-.5v-5a.5.5,0,0,1,1,0v5A1.5,1.5,0,0,1,33.5,35Z"
|
||||
style="fill:#2b3436"
|
||||
id="path3" />
|
||||
<path
|
||||
d="M19.5,35H5.5A1.5,1.5,0,0,1,4,33.5V5.5A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v13a.5.5,0,0,1-1,0V5.5a.5.5,0,0,0-.5-.5H5.5a.5.5,0,0,0-.5.5v28a.5.5,0,0,0,.5.5h14a.5.5,0,0,1,0,1Z"
|
||||
style="fill:#2b3436"
|
||||
id="path4" />
|
||||
<path
|
||||
style="fill:none;stroke:#2b3436;stroke-opacity:1"
|
||||
d="m 8.4562867,23.400587 c 0,0 2.2252913,-19.4227297 14.8714013,-14.7256034 12.757329,4.7384364 6.050618,16.8396744 2.91596,13.1218244 -3.134658,-3.717851 -5.467426,-0.874789 -5.467426,-0.874789 0,0 -2.529885,2.814818 0.364495,5.613225 2.151955,2.0806 -1.057336,4.314407 -2.734014,4.606003 -1.676677,0.291596 -8.7840319,1.444616 -9.5859212,-0.08626 -0.8018892,-1.53088 -0.3644951,-7.654397 -0.3644951,-7.654397 z"
|
||||
id="path17" />
|
||||
<ellipse
|
||||
style="fill:#3297ff;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="path18"
|
||||
cx="20.280308"
|
||||
cy="12.027146"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#f7ff0d;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse18"
|
||||
cx="15.427248"
|
||||
cy="15.754859"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#ff0d0d;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse19"
|
||||
cx="12.402879"
|
||||
cy="21.240925"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#0dff24;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse20"
|
||||
cx="12.051207"
|
||||
cy="27.571003"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
82
resources/images/true_color_painting_dark.svg
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="true_color_painting_dark.svg"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="10.121314"
|
||||
inkscape:cx="22.872525"
|
||||
inkscape:cy="18.278259"
|
||||
inkscape:window-width="1192"
|
||||
inkscape:window-height="701"
|
||||
inkscape:window-x="170"
|
||||
inkscape:window-y="295"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg17" />
|
||||
<defs
|
||||
id="defs17" />
|
||||
<path
|
||||
d="M27.878,34.926h-4.4c-.509,0-.845,0-.955-.394a.593.593,0,0,1,.36-.67,2.3,2.3,0,0,0,1.2-2.189,3.253,3.253,0,1,1,3.8,3.206Zm-3.538-1h2.988a2.253,2.253,0,1,0-2.253-2.253A3.445,3.445,0,0,1,24.34,33.926Z"
|
||||
style="fill:#009688"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 30.5,30.289 -0.846,-0.535 4.364,-6.9 a 0.222,0.222 0 0 0 -0.36,-0.258 l -5.4,6.644 -0.776,-0.631 5.4,-6.644 a 1.222,1.222 0 0 1 1.982,1.423 z"
|
||||
style="fill:#009688"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M33.5,35h-1a.5.5,0,0,1,0-1h1a.5.5,0,0,0,.5-.5v-5a.5.5,0,0,1,1,0v5A1.5,1.5,0,0,1,33.5,35Z"
|
||||
style="fill:#b6b6b6;fill-opacity:1"
|
||||
id="path3" />
|
||||
<path
|
||||
d="M19.5,35H5.5A1.5,1.5,0,0,1,4,33.5V5.5A1.5,1.5,0,0,1,5.5,4h28A1.5,1.5,0,0,1,35,5.5v13a.5.5,0,0,1-1,0V5.5a.5.5,0,0,0-.5-.5H5.5a.5.5,0,0,0-.5.5v28a.5.5,0,0,0,.5.5h14a.5.5,0,0,1,0,1Z"
|
||||
style="fill:#b6b6b6;fill-opacity:1"
|
||||
id="path4" />
|
||||
<path
|
||||
style="fill:none;stroke:#b6b6b6;stroke-opacity:0.90588236"
|
||||
d="m 8.4562867,23.400587 c 0,0 2.2252913,-19.4227297 14.8714013,-14.7256034 12.757329,4.7384364 6.050618,16.8396744 2.91596,13.1218244 -3.134658,-3.717851 -5.467426,-0.874789 -5.467426,-0.874789 0,0 -2.529885,2.814818 0.364495,5.613225 2.151955,2.0806 -1.057336,4.314407 -2.734014,4.606003 -1.676677,0.291596 -8.7840319,1.444616 -9.5859212,-0.08626 -0.8018892,-1.53088 -0.3644951,-7.654397 -0.3644951,-7.654397 z"
|
||||
id="path17" />
|
||||
<ellipse
|
||||
style="fill:#3297ff;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="path18"
|
||||
cx="20.280308"
|
||||
cy="12.027146"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#f7ff0d;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse18"
|
||||
cx="15.427248"
|
||||
cy="15.754859"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#ff0d0d;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse19"
|
||||
cx="12.402879"
|
||||
cy="21.240925"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
<ellipse
|
||||
style="fill:#0dff24;fill-opacity:1;stroke:none;stroke-width:1.31579;stroke-opacity:1"
|
||||
id="ellipse20"
|
||||
cx="12.051207"
|
||||
cy="27.571003"
|
||||
rx="1.7120823"
|
||||
ry="1.7583548" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -7,5 +7,14 @@
|
||||
"setting_id": "YiQZfhL6hH0giSHL",
|
||||
"filament_id": "OGFG99",
|
||||
"instantiation": "true",
|
||||
"enable_pressure_advance": [
|
||||
"1"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02"
|
||||
],
|
||||
"adaptive_pressure_advance": [
|
||||
"0"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
|
||||
@@ -6,5 +6,14 @@
|
||||
"from": "system",
|
||||
"setting_id": "RcBNzytWgwRrwXXz",
|
||||
"instantiation": "true",
|
||||
"enable_pressure_advance": [
|
||||
"1"
|
||||
],
|
||||
"pressure_advance": [
|
||||
"0.02"
|
||||
],
|
||||
"adaptive_pressure_advance": [
|
||||
"0"
|
||||
],
|
||||
"compatible_printers": []
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"raft_first_layer_expansion": "3",
|
||||
"raft_contact_distance": "0.2",
|
||||
"support_type": "tree(auto)",
|
||||
"support_style": "organic",
|
||||
"support_style": "default",
|
||||
"support_base_pattern_spacing": "2",
|
||||
"support_interface_top_layers": "5",
|
||||
"support_interface_bottom_layers": "0",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"inherits": "fdm_U1",
|
||||
"printer_model": "Snapmaker U1",
|
||||
"printer_variant": "0.4",
|
||||
"default_print_profile": "0.08 Extra Fine @Snapmaker U1 (0.4 nozzle)",
|
||||
"auxiliary_fan": "1",
|
||||
"change_filament_gcode": ";===== date: 20260607 =====================\n; Change Tool[previous_extruder] -> Tool[next_extruder] (layer [layer_num])\n; max_layer_z [max_layer_z]\n; max_print_height [max_print_height]\n; print_sequence [print_sequence]\n\n{\nlocal move_z = 1.5;\nlocal max_speed_toolchange = 350.0;\nlocal wait_for_extruder_temp = true;\nposition[2] = position[2] + 2.0;\nlocal speed_toolchange = max_speed_toolchange;\n\nif travel_speed < max_speed_toolchange then\n speed_toolchange = travel_speed;\nendif\n\nif print_sequence == \"by object\" then\n\n if max_layer_z < ((max_print_height - z_offset) - 2) then\n move_z = z_offset + min(((max_layer_z - z_offset) + 2), max_print_height);\n endif\n\nendif\n\n\"G91\nG1 Z\" + move_z + \" F600\nG90\n\";\n\"G1 F\" + (speed_toolchange * 60) + \"\n\";\nif wait_for_extruder_temp and not((layer_num < 0) and (next_extruder == initial_tool)) then\n \"\n\";\n \"; \" + layer_num + \"\n\";\n if layer_num == 0 then\n \"M109 S\" + first_layer_temperature[next_extruder] + \" T\" + next_extruder + \"\n\";\n else\n \"M109 S\" + temperature[next_extruder] + \" T\" + next_extruder + \"\n\";\n endif\nendif\n\"M400\" + \"\n\";\n\"T\" + next_extruder + \"\n\";\nif filament_type[next_extruder] == \"PVA\" then\n\"SET_VELOCITY_LIMIT ACCEL=3000\n\";\nelse\nendif\nif previous_extruder != next_extruder and initial_extruder != next_extruder then\n\"SM_PRINT_PREEXTRUDE_FILAMENT INDEX=\" + next_extruder + \"\n\";\nendif\n\"G90\n\";\n}\n",
|
||||
"extruder_colour": [
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"top_shell_thickness": "0.5",
|
||||
"initial_layer_speed": "100",
|
||||
"initial_layer_infill_speed": "180",
|
||||
"outer_wall_speed": "180",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "200",
|
||||
"sparse_infill_speed": "200",
|
||||
"internal_solid_infill_speed": "200",
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"initial_layer_print_height": "0.30",
|
||||
"top_shell_layers": "3",
|
||||
"bottom_shell_layers": "3",
|
||||
"outer_wall_speed": "220",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "330",
|
||||
"sparse_infill_speed": "300",
|
||||
"internal_solid_infill_speed": "280"
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"initial_layer_print_height": "0.35",
|
||||
"top_shell_layers": "3",
|
||||
"bottom_shell_layers": "3",
|
||||
"outer_wall_speed": "220",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "330",
|
||||
"sparse_infill_speed": "320",
|
||||
"internal_solid_infill_speed": "300",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"bridge_no_support": "0",
|
||||
"elefant_foot_compensation": "0.1",
|
||||
"outer_wall_line_width": "0.42",
|
||||
"outer_wall_speed": "120",
|
||||
"outer_wall_speed": "100",
|
||||
"line_width": "0.45",
|
||||
"infill_direction": "45",
|
||||
"sparse_infill_density": "15%",
|
||||
@@ -57,6 +57,8 @@
|
||||
"support_base_pattern": "default",
|
||||
"support_base_pattern_spacing": "2",
|
||||
"support_speed": "40",
|
||||
"support_type": "tree(auto)",
|
||||
"support_style": "default",
|
||||
"support_threshold_angle": "40",
|
||||
"support_object_xy_distance": "0.5",
|
||||
"detect_thin_wall": "0",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"ironing_flow": "8%",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "350",
|
||||
"sparse_infill_speed": "450",
|
||||
"internal_solid_infill_speed": "350",
|
||||
@@ -20,7 +20,7 @@
|
||||
"overhang_1_4_speed": "60",
|
||||
"overhang_2_4_speed": "30",
|
||||
"overhang_3_4_speed": "10",
|
||||
"support_threshold_angle": "15",
|
||||
"support_threshold_angle": "25",
|
||||
"support_top_z_distance": "0.08",
|
||||
"support_bottom_z_distance": "0.08"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"bridge_flow": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "350",
|
||||
"sparse_infill_speed": "430",
|
||||
"internal_solid_infill_speed": "350",
|
||||
@@ -20,7 +20,7 @@
|
||||
"overhang_1_4_speed": "60",
|
||||
"overhang_2_4_speed": "30",
|
||||
"overhang_3_4_speed": "10",
|
||||
"support_threshold_angle": "20",
|
||||
"support_threshold_angle": "25",
|
||||
"support_top_z_distance": "0.12",
|
||||
"support_bottom_z_distance": "0.12"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"bridge_flow": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "300",
|
||||
"sparse_infill_speed": "330",
|
||||
"internal_solid_infill_speed": "300",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"bridge_flow": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "300",
|
||||
"sparse_infill_speed": "270",
|
||||
"internal_solid_infill_speed": "250",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bridge_flow": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "230",
|
||||
"sparse_infill_speed": "230",
|
||||
"internal_solid_infill_speed": "230",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"bridge_flow": "1",
|
||||
"initial_layer_speed": "50",
|
||||
"initial_layer_infill_speed": "105",
|
||||
"outer_wall_speed": "200",
|
||||
"outer_wall_speed": "100",
|
||||
"inner_wall_speed": "200",
|
||||
"sparse_infill_speed": "200",
|
||||
"internal_solid_infill_speed": "200",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"elefant_foot_compensation": "0",
|
||||
"enable_arc_fitting": "1",
|
||||
"exclude_object": "1",
|
||||
"outer_wall_acceleration": "5000",
|
||||
"outer_wall_acceleration": "2000",
|
||||
"wall_infill_order": "inner wall/outer wall/infill",
|
||||
"line_width": "0.42",
|
||||
"internal_bridge_support_thickness": "0.8",
|
||||
@@ -46,7 +46,7 @@
|
||||
"internal_solid_infill_speed": "150",
|
||||
"initial_layer_infill_speed": "60",
|
||||
"resolution": "0.012",
|
||||
"support_type": "normal(auto)",
|
||||
"support_type": "tree(auto)",
|
||||
"support_style": "default",
|
||||
"support_top_z_distance": "0.2",
|
||||
"support_bottom_z_distance": "0.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Template",
|
||||
"version": "01.07.00.02",
|
||||
"version": "01.07.00.03",
|
||||
"force_update": "0",
|
||||
"description": "Template configurations",
|
||||
"machine_model_list": [],
|
||||
|
||||
@@ -84,8 +84,8 @@
|
||||
"support_threshold_angle": "30",
|
||||
"support_object_xy_distance": "0.35",
|
||||
"skirt_loops": "0",
|
||||
"support_type": "normal(auto)",
|
||||
"support_style": "default",
|
||||
"support_type": "tree(auto)",
|
||||
"support_style": "tree_hybrid",
|
||||
"support_bottom_z_distance": "0.2",
|
||||
"support_interface_bottom_layers": "2",
|
||||
"support_expansion": "0",
|
||||
|
||||
8
resources/shaders/110/flat_vertex_color.fs
Normal file
@@ -0,0 +1,8 @@
|
||||
#version 110
|
||||
|
||||
varying vec4 vertex_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = vertex_color;
|
||||
}
|
||||
15
resources/shaders/110/flat_vertex_color.vs
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 110
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec4 v_color;
|
||||
|
||||
varying vec4 vertex_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertex_color = v_color;
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -31,12 +31,24 @@ uniform mat3 view_normal_matrix;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float smooth_world_normal_z;
|
||||
|
||||
struct SlopeDetection
|
||||
{
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
int preview_mode;
|
||||
float top_z;
|
||||
float bottom_z;
|
||||
vec4 highlight_color;
|
||||
bool override_all;
|
||||
vec4 override_mask0;
|
||||
vec4 override_mask1;
|
||||
vec4 override_mask2;
|
||||
vec4 override_mask3;
|
||||
int current_state;
|
||||
int base_state;
|
||||
};
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
@@ -62,6 +74,77 @@ vec3 getWireframeColor(vec3 fill) {
|
||||
}
|
||||
uniform bool show_wireframe;
|
||||
|
||||
float slopeOverrideMaskSlot(int slot) {
|
||||
if (slot == 0)
|
||||
return slope.override_mask0.x;
|
||||
if (slot == 1)
|
||||
return slope.override_mask0.y;
|
||||
if (slot == 2)
|
||||
return slope.override_mask0.z;
|
||||
if (slot == 3)
|
||||
return slope.override_mask0.w;
|
||||
if (slot == 4)
|
||||
return slope.override_mask1.x;
|
||||
if (slot == 5)
|
||||
return slope.override_mask1.y;
|
||||
if (slot == 6)
|
||||
return slope.override_mask1.z;
|
||||
if (slot == 7)
|
||||
return slope.override_mask1.w;
|
||||
if (slot == 8)
|
||||
return slope.override_mask2.x;
|
||||
if (slot == 9)
|
||||
return slope.override_mask2.y;
|
||||
if (slot == 10)
|
||||
return slope.override_mask2.z;
|
||||
if (slot == 11)
|
||||
return slope.override_mask2.w;
|
||||
if (slot == 12)
|
||||
return slope.override_mask3.x;
|
||||
if (slot == 13)
|
||||
return slope.override_mask3.y;
|
||||
if (slot == 14)
|
||||
return slope.override_mask3.z;
|
||||
if (slot == 15)
|
||||
return slope.override_mask3.w;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool slopeOverrideMatches(int state) {
|
||||
if (slope.override_all)
|
||||
return true;
|
||||
if (state < 0 || state >= 256)
|
||||
return false;
|
||||
int slot = state / 16;
|
||||
int bit = state - slot * 16;
|
||||
float mask = slopeOverrideMaskSlot(slot);
|
||||
float divisor = pow(2.0, float(bit));
|
||||
return floor(mod(floor(mask / divisor), 2.0)) > 0.5;
|
||||
}
|
||||
|
||||
bool slopePreviewMatches(float world_normal_z) {
|
||||
if (slope.preview_mode == 1)
|
||||
return world_normal_z >= slope.top_z - EPSILON;
|
||||
if (slope.preview_mode == 2)
|
||||
return world_normal_z <= slope.bottom_z + EPSILON;
|
||||
if (slope.preview_mode == 3)
|
||||
return world_normal_z <= slope.top_z + EPSILON && world_normal_z >= slope.bottom_z - EPSILON;
|
||||
return false;
|
||||
}
|
||||
|
||||
float slopePreviewChecker(vec3 position, vec3 normal) {
|
||||
vec2 uv;
|
||||
vec3 abs_normal = abs(normal);
|
||||
if (abs_normal.z >= abs_normal.x && abs_normal.z >= abs_normal.y)
|
||||
uv = position.xy;
|
||||
else if (abs_normal.x >= abs_normal.y)
|
||||
uv = position.yz;
|
||||
else
|
||||
uv = position.xz;
|
||||
vec2 cells = floor(uv / 3.0);
|
||||
return mod(cells.x + cells.y, 2.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
@@ -80,15 +163,39 @@ void main()
|
||||
vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal);
|
||||
|
||||
if (slope.actived) {
|
||||
if(world_pos.z<0.1&&world_pos.z>-0.1)
|
||||
{
|
||||
color = LightBlue;
|
||||
alpha = 1.0;
|
||||
}
|
||||
else if( transformed_normal.z < slope.normal_z - EPSILON)
|
||||
{
|
||||
color = color * 0.5 + LightRed * 0.5;
|
||||
alpha = 1.0;
|
||||
if (slope.preview_mode == 0) {
|
||||
if(world_pos.z<0.1&&world_pos.z>-0.1)
|
||||
{
|
||||
color = LightBlue;
|
||||
alpha = 1.0;
|
||||
}
|
||||
else if( transformed_normal.z < slope.normal_z - EPSILON)
|
||||
{
|
||||
color = color * 0.5 + LightRed * 0.5;
|
||||
alpha = 1.0;
|
||||
}
|
||||
} else if (slope.preview_mode == 4) {
|
||||
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
|
||||
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
|
||||
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
|
||||
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
|
||||
color = mix(preview_color_a, preview_color_b, preview_check);
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
} else if (slope.preview_mode == 5) {
|
||||
color = slope.highlight_color.rgb;
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
} else {
|
||||
int effective_state = slope.current_state == 0 ? slope.base_state : slope.current_state;
|
||||
if (slopeOverrideMatches(effective_state) && slopePreviewMatches(smooth_world_normal_z)) {
|
||||
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
|
||||
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
|
||||
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
|
||||
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
|
||||
color = mix(preview_color_a, preview_color_b, preview_check);
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
}
|
||||
}
|
||||
}
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
|
||||
@@ -12,11 +12,13 @@ uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_slope_normal;
|
||||
attribute vec3 v_barycentric;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float smooth_world_normal_z;
|
||||
varying vec3 barycentric_coordinates;
|
||||
|
||||
struct SlopeDetection
|
||||
@@ -24,6 +26,17 @@ struct SlopeDetection
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
int preview_mode;
|
||||
float top_z;
|
||||
float bottom_z;
|
||||
vec4 highlight_color;
|
||||
bool override_all;
|
||||
vec4 override_mask0;
|
||||
vec4 override_mask1;
|
||||
vec4 override_mask2;
|
||||
vec4 override_mask3;
|
||||
int current_state;
|
||||
int base_state;
|
||||
};
|
||||
uniform SlopeDetection slope;
|
||||
void main()
|
||||
@@ -31,6 +44,7 @@ void main()
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
// Point in homogenous coordinates.
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
smooth_world_normal_z = normalize(slope.volume_world_normal_matrix * v_slope_normal).z;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
|
||||
329
resources/shaders/110/painted_surface_gradient_preview.fs
Normal file
@@ -0,0 +1,329 @@
|
||||
#version 110
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float INVALID_TEXTURE_CHECKER_SCALE = 0.2;
|
||||
const int MAX_GRADIENT_COMPONENTS = 10;
|
||||
const int MAX_LINEAR_GRADIENT_LUT_COLORS = 33;
|
||||
const float EPSILON = 0.000001;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
int type;
|
||||
vec4 xy_data;
|
||||
vec2 z_data;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform float texture_preview_mix;
|
||||
uniform bool invalid_texture_mapping;
|
||||
uniform PrintVolumeDetection print_volume;
|
||||
|
||||
uniform int gradient_component_count;
|
||||
uniform vec3 gradient_base_color;
|
||||
uniform vec3 gradient_component_colors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_distances_mm[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_angles_deg[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_strength_factors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_minimum_offset_factors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_max_component_distance_mm;
|
||||
uniform float gradient_max_width_delta_limit_mm;
|
||||
uniform int gradient_angle_mode;
|
||||
uniform bool gradient_rotation_enabled;
|
||||
uniform float gradient_rotations;
|
||||
uniform float gradient_repeats;
|
||||
uniform bool gradient_reverse_repeats;
|
||||
uniform bool gradient_clockwise;
|
||||
uniform int gradient_fade_mode;
|
||||
uniform vec3 gradient_center;
|
||||
uniform float gradient_z_min;
|
||||
uniform float gradient_z_max;
|
||||
uniform bool gradient_linear_mode;
|
||||
uniform bool gradient_linear_radial_mode;
|
||||
uniform vec3 gradient_linear_start;
|
||||
uniform vec3 gradient_linear_end;
|
||||
uniform float gradient_linear_radius_mm;
|
||||
uniform int gradient_linear_lut_count;
|
||||
uniform vec3 gradient_linear_lut_colors[MAX_LINEAR_GRADIENT_LUT_COLORS];
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
|
||||
float normalize_angle(float angle)
|
||||
{
|
||||
float out_angle = mod(angle, 360.0);
|
||||
if (out_angle < 0.0)
|
||||
out_angle += 360.0;
|
||||
return out_angle;
|
||||
}
|
||||
|
||||
float angular_distance_deg(float a, float b)
|
||||
{
|
||||
float d = abs(normalize_angle(a) - normalize_angle(b));
|
||||
return min(d, 360.0 - d);
|
||||
}
|
||||
|
||||
float angular_distance_cw(float from_deg, float to_deg)
|
||||
{
|
||||
float d = normalize_angle(to_deg) - normalize_angle(from_deg);
|
||||
if (d < 0.0)
|
||||
d += 360.0;
|
||||
return d;
|
||||
}
|
||||
|
||||
float repeated_rotation_progress(float progress01, float repeats, bool reverse_repeats)
|
||||
{
|
||||
float p = clamp(progress01, 0.0, 1.0);
|
||||
float r = max(1.0, repeats);
|
||||
if (r <= 1.0 + EPSILON)
|
||||
return p;
|
||||
|
||||
float repeated_pos = p * r;
|
||||
float segment_idx = floor(repeated_pos);
|
||||
float local = repeated_pos - segment_idx;
|
||||
|
||||
if (p >= 1.0 - EPSILON) {
|
||||
segment_idx = max(0.0, ceil(r) - 1.0);
|
||||
local = 1.0;
|
||||
}
|
||||
|
||||
if (reverse_repeats && mod(segment_idx, 2.0) >= 1.0)
|
||||
local = 1.0 - local;
|
||||
return clamp(local, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float offset_fade_factor(int fade_mode, float progress01)
|
||||
{
|
||||
float p = clamp(progress01, 0.0, 1.0);
|
||||
if (fade_mode == 1)
|
||||
return p;
|
||||
if (fade_mode == 2)
|
||||
return 1.0 - p;
|
||||
if (fade_mode == 3)
|
||||
return 1.0 - abs(2.0 * p - 1.0);
|
||||
if (fade_mode == 4)
|
||||
return abs(2.0 * p - 1.0);
|
||||
if (fade_mode == 5)
|
||||
return 1.0 - 2.0 * p;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float component_angular_influence(int component_idx, float theta_deg)
|
||||
{
|
||||
int count = min(gradient_component_count, MAX_GRADIENT_COMPONENTS);
|
||||
if (count <= 0)
|
||||
return 0.0;
|
||||
if (count == 1)
|
||||
return 1.0;
|
||||
|
||||
float self_angle = normalize_angle(gradient_angles_deg[component_idx]);
|
||||
float prev_angle = self_angle;
|
||||
float next_angle = self_angle;
|
||||
float prev_to_self_deg = 360.0;
|
||||
float self_to_next_deg = 360.0;
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count || i == component_idx)
|
||||
continue;
|
||||
float other_angle = normalize_angle(gradient_angles_deg[i]);
|
||||
float prev_distance = angular_distance_cw(other_angle, self_angle);
|
||||
float next_distance = angular_distance_cw(self_angle, other_angle);
|
||||
if (prev_distance < prev_to_self_deg) {
|
||||
prev_to_self_deg = prev_distance;
|
||||
prev_angle = other_angle;
|
||||
}
|
||||
if (next_distance < self_to_next_deg) {
|
||||
self_to_next_deg = next_distance;
|
||||
next_angle = other_angle;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev_to_self_deg <= 0.001 || self_to_next_deg <= 0.001) {
|
||||
float total_weight = 0.0;
|
||||
float active_weight = 0.0;
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float weight = max(0.0, 1.0 - angular_distance_deg(theta_deg, gradient_angles_deg[i]) / 180.0);
|
||||
total_weight += weight;
|
||||
if (i == component_idx)
|
||||
active_weight += weight;
|
||||
}
|
||||
if (total_weight <= EPSILON)
|
||||
return 0.0;
|
||||
return clamp(active_weight / total_weight, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float theta_norm = normalize_angle(theta_deg);
|
||||
float prev_to_theta_deg = angular_distance_cw(prev_angle, theta_norm);
|
||||
if (prev_to_theta_deg <= prev_to_self_deg + 0.0001)
|
||||
return clamp(prev_to_theta_deg / prev_to_self_deg, 0.0, 1.0);
|
||||
|
||||
float self_to_theta_deg = angular_distance_cw(self_angle, theta_norm);
|
||||
if (self_to_theta_deg <= self_to_next_deg + 0.0001)
|
||||
return clamp(1.0 - self_to_theta_deg / self_to_next_deg, 0.0, 1.0);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float variable_width_delta(float inset_strength, float max_width_delta_limit_mm, float minimum_offset_factor, float strength_factor)
|
||||
{
|
||||
if (max_width_delta_limit_mm <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
float desired_width_factor = 1.0 - clamp(inset_strength, 0.0, 1.0);
|
||||
float min_width_factor = clamp(minimum_offset_factor, 0.0, 1.0);
|
||||
float adjusted_width_factor = min_width_factor + desired_width_factor * clamp(strength_factor, 0.0, 1.0) * (1.0 - min_width_factor);
|
||||
return clamp(max_width_delta_limit_mm * (1.0 - adjusted_width_factor), 0.0, max_width_delta_limit_mm);
|
||||
}
|
||||
|
||||
vec3 linear_gradient_lut_color(float t)
|
||||
{
|
||||
int count = min(gradient_linear_lut_count, MAX_LINEAR_GRADIENT_LUT_COLORS);
|
||||
if (count <= 0)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
if (count == 1)
|
||||
return clamp(gradient_linear_lut_colors[0], 0.0, 1.0);
|
||||
|
||||
float scaled = clamp(t, 0.0, 1.0) * float(count - 1);
|
||||
int lower = int(floor(scaled));
|
||||
int upper = min(lower + 1, count - 1);
|
||||
float f = scaled - float(lower);
|
||||
return clamp(mix(gradient_linear_lut_colors[lower], gradient_linear_lut_colors[upper], f), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 linear_gradient_color(int count)
|
||||
{
|
||||
float t = 0.0;
|
||||
if (gradient_linear_radial_mode) {
|
||||
t = clamp(length(world_pos.xyz - gradient_linear_start) / max(gradient_linear_radius_mm, EPSILON), 0.0, 1.0);
|
||||
} else {
|
||||
vec3 direction_vec = gradient_linear_end - gradient_linear_start;
|
||||
float denom = dot(direction_vec, direction_vec);
|
||||
t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
if (count <= 0)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
return linear_gradient_lut_color(t);
|
||||
}
|
||||
|
||||
vec3 surface_gradient_color()
|
||||
{
|
||||
int count = min(gradient_component_count, MAX_GRADIENT_COMPONENTS);
|
||||
if (count <= 0)
|
||||
return uniform_color.rgb;
|
||||
if (gradient_linear_mode)
|
||||
return linear_gradient_color(count);
|
||||
|
||||
float z_span = gradient_z_max - gradient_z_min;
|
||||
float z_progress = z_span > EPSILON ? clamp((world_pos.z - gradient_z_min) / z_span, 0.0, 1.0) : 0.0;
|
||||
|
||||
float rotation_deg = 0.0;
|
||||
if (gradient_rotation_enabled) {
|
||||
float repeated = repeated_rotation_progress(z_progress, max(1.0, gradient_repeats), gradient_reverse_repeats);
|
||||
float direction = gradient_clockwise ? -1.0 : 1.0;
|
||||
rotation_deg = direction * 360.0 * gradient_rotations * repeated;
|
||||
}
|
||||
|
||||
vec2 direction_vec = vec2(0.0);
|
||||
if (gradient_angle_mode == 1)
|
||||
direction_vec = world_normal.xy;
|
||||
if (dot(direction_vec, direction_vec) <= EPSILON) {
|
||||
vec3 radial = world_pos.xyz - gradient_center;
|
||||
direction_vec = radial.xy;
|
||||
}
|
||||
if (dot(direction_vec, direction_vec) <= EPSILON)
|
||||
direction_vec = vec2(1.0, 0.0);
|
||||
|
||||
float theta_deg = normalize_angle(degrees(atan(direction_vec.y, direction_vec.x)) - rotation_deg);
|
||||
float signed_fade_factor = offset_fade_factor(gradient_fade_mode, z_progress);
|
||||
float fade_factor = abs(signed_fade_factor);
|
||||
float sample_theta_deg = signed_fade_factor < 0.0 ? normalize_angle(theta_deg + 180.0) : theta_deg;
|
||||
float influences[MAX_GRADIENT_COMPONENTS];
|
||||
float visibility_weights[MAX_GRADIENT_COMPONENTS];
|
||||
float min_visibility = 1.0;
|
||||
float max_visibility = 0.0;
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
influences[i] = 0.0;
|
||||
visibility_weights[i] = 0.0;
|
||||
if (i < count)
|
||||
influences[i] = component_angular_influence(i, sample_theta_deg);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float raw_inset_mm = 0.0;
|
||||
for (int j = 0; j < MAX_GRADIENT_COMPONENTS; ++j) {
|
||||
if (j >= count || i == j)
|
||||
continue;
|
||||
raw_inset_mm += gradient_distances_mm[j] * influences[j];
|
||||
}
|
||||
float inset_strength = clamp(raw_inset_mm / max(gradient_max_component_distance_mm, EPSILON), 0.0, 1.0);
|
||||
float width_delta_mm = variable_width_delta(inset_strength * fade_factor,
|
||||
gradient_max_width_delta_limit_mm,
|
||||
gradient_minimum_offset_factors[i],
|
||||
gradient_strength_factors[i]);
|
||||
visibility_weights[i] = 1.0 - clamp(width_delta_mm / max(gradient_max_width_delta_limit_mm, EPSILON), 0.0, 1.0);
|
||||
min_visibility = min(min_visibility, visibility_weights[i]);
|
||||
max_visibility = max(max_visibility, visibility_weights[i]);
|
||||
}
|
||||
|
||||
vec3 target_color = vec3(0.0);
|
||||
float total_excess_weight = 0.0;
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float weight = max(0.0, visibility_weights[i] - min_visibility);
|
||||
target_color += gradient_component_colors[i] * weight;
|
||||
total_excess_weight += weight;
|
||||
}
|
||||
if (total_excess_weight <= EPSILON)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
float contrast = clamp(max_visibility - min_visibility, 0.0, 1.0);
|
||||
target_color = clamp(target_color / total_excess_weight, 0.0, 1.0);
|
||||
return mix(clamp(gradient_base_color, 0.0, 1.0), target_color, contrast);
|
||||
}
|
||||
|
||||
float invalid_texture_mapping_checker()
|
||||
{
|
||||
vec3 normal_axes = abs(world_normal);
|
||||
vec2 checker_pos = world_pos.xy;
|
||||
if (normal_axes.x > normal_axes.y && normal_axes.x > normal_axes.z)
|
||||
checker_pos = world_pos.yz;
|
||||
else if (normal_axes.y > normal_axes.z)
|
||||
checker_pos = world_pos.xz;
|
||||
return mod(floor(checker_pos.x * INVALID_TEXTURE_CHECKER_SCALE) + floor(checker_pos.y * INVALID_TEXTURE_CHECKER_SCALE), 2.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec4 color = uniform_color;
|
||||
float mix_factor = clamp(texture_preview_mix, 0.0, 1.0);
|
||||
color.rgb = mix(color.rgb, surface_gradient_color(), mix_factor);
|
||||
if (invalid_texture_mapping) {
|
||||
float checker = invalid_texture_mapping_checker();
|
||||
vec3 checker_color = mix(vec3(0.0), vec3(1.0), checker);
|
||||
color.rgb = mix(color.rgb, checker_color, 0.62);
|
||||
}
|
||||
|
||||
vec3 pv_check_min = ZERO;
|
||||
vec3 pv_check_max = ZERO;
|
||||
if (print_volume.type == 0) {
|
||||
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
|
||||
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
|
||||
}
|
||||
else if (print_volume.type == 1) {
|
||||
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
|
||||
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
|
||||
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
|
||||
}
|
||||
|
||||
color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
|
||||
}
|
||||
46
resources/shaders/110/painted_surface_gradient_preview.vs
Normal file
@@ -0,0 +1,46 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||
world_normal = normalize(vec3(volume_world_matrix * vec4(v_normal, 0.0)));
|
||||
gl_Position = projection_matrix * position;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
137
resources/shaders/110/painted_texture_preview.fs
Normal file
@@ -0,0 +1,137 @@
|
||||
#version 110
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float UV_EDGE_EPSILON = 0.000001;
|
||||
const float INVALID_TEXTURE_CHECKER_SCALE = 0.2;
|
||||
const float CONTONING_FLAT_SURFACE_NORMAL_Z = 0.999;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
int type;
|
||||
vec4 xy_data;
|
||||
vec2 z_data;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform sampler2D uniform_texture;
|
||||
uniform sampler2D contoning_flat_surface_texture;
|
||||
uniform sampler2D contoning_flat_surface_bottom_texture;
|
||||
uniform float texture_preview_mix;
|
||||
uniform bool invalid_texture_mapping;
|
||||
uniform bool contoning_flat_surface_texture_enabled;
|
||||
uniform bool contoning_flat_surface_bottom_texture_enabled;
|
||||
uniform bool raw_atlas_surface_filter_enabled;
|
||||
uniform bool raw_atlas_side_texture_enabled;
|
||||
uniform bool raw_atlas_flat_texture_enabled;
|
||||
uniform bool color_match_preview_active;
|
||||
uniform vec3 color_match_target_oklab;
|
||||
uniform float color_match_tolerance_sq;
|
||||
uniform vec4 color_match_highlight_color;
|
||||
uniform vec4 color_match_background_color;
|
||||
uniform PrintVolumeDetection print_volume;
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
varying vec2 tex_coord;
|
||||
|
||||
float texture_preview_coord(float uv)
|
||||
{
|
||||
if (uv >= -UV_EDGE_EPSILON && uv <= 1.0 + UV_EDGE_EPSILON)
|
||||
return clamp(uv, 0.0, 1.0);
|
||||
|
||||
return fract(uv);
|
||||
}
|
||||
|
||||
vec2 texture_preview_coord(vec2 uv)
|
||||
{
|
||||
return vec2(texture_preview_coord(uv.x), texture_preview_coord(uv.y));
|
||||
}
|
||||
|
||||
float invalid_texture_mapping_checker()
|
||||
{
|
||||
vec3 normal_axes = abs(world_normal);
|
||||
vec2 checker_pos = world_pos.xy;
|
||||
if (normal_axes.x > normal_axes.y && normal_axes.x > normal_axes.z)
|
||||
checker_pos = world_pos.yz;
|
||||
else if (normal_axes.y > normal_axes.z)
|
||||
checker_pos = world_pos.xz;
|
||||
return mod(floor(checker_pos.x * INVALID_TEXTURE_CHECKER_SCALE) + floor(checker_pos.y * INVALID_TEXTURE_CHECKER_SCALE), 2.0);
|
||||
}
|
||||
|
||||
float srgb_channel_to_linear(float c)
|
||||
{
|
||||
return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
vec3 oklab_from_srgb(vec3 c)
|
||||
{
|
||||
vec3 linear = vec3(srgb_channel_to_linear(c.r), srgb_channel_to_linear(c.g), srgb_channel_to_linear(c.b));
|
||||
float l = 0.4122214708 * linear.r + 0.5363325363 * linear.g + 0.0514459929 * linear.b;
|
||||
float m = 0.2119034982 * linear.r + 0.6806995451 * linear.g + 0.1073969566 * linear.b;
|
||||
float s = 0.0883024619 * linear.r + 0.2817188376 * linear.g + 0.6299787005 * linear.b;
|
||||
float l_ = pow(max(l, 0.0), 1.0 / 3.0);
|
||||
float m_ = pow(max(m, 0.0), 1.0 / 3.0);
|
||||
float s_ = pow(max(s, 0.0), 1.0 / 3.0);
|
||||
return vec3(0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec4 color = uniform_color;
|
||||
vec4 texture_color = texture2D(uniform_texture, texture_preview_coord(tex_coord));
|
||||
float normal_z = normalize(world_normal).z;
|
||||
bool flat_surface = abs(normal_z) >= CONTONING_FLAT_SURFACE_NORMAL_Z;
|
||||
if (raw_atlas_surface_filter_enabled) {
|
||||
bool raw_atlas_surface_allowed = flat_surface ? raw_atlas_flat_texture_enabled : raw_atlas_side_texture_enabled;
|
||||
if (!raw_atlas_surface_allowed) {
|
||||
if (color_match_preview_active)
|
||||
discard;
|
||||
texture_color = color;
|
||||
}
|
||||
}
|
||||
if (normal_z >= CONTONING_FLAT_SURFACE_NORMAL_Z) {
|
||||
if (contoning_flat_surface_texture_enabled)
|
||||
texture_color = texture2D(contoning_flat_surface_texture, texture_preview_coord(tex_coord));
|
||||
} else if (normal_z <= -CONTONING_FLAT_SURFACE_NORMAL_Z) {
|
||||
if (contoning_flat_surface_bottom_texture_enabled)
|
||||
texture_color = texture2D(contoning_flat_surface_bottom_texture, texture_preview_coord(tex_coord));
|
||||
}
|
||||
float mix_factor = clamp(texture_preview_mix, 0.0, 1.0);
|
||||
if (color_match_preview_active) {
|
||||
float source_alpha = clamp(texture_color.a, 0.0, 1.0);
|
||||
vec3 source_rgb = texture_color.rgb * source_alpha + color_match_background_color.rgb * (1.0 - source_alpha);
|
||||
vec3 delta = oklab_from_srgb(source_rgb) - color_match_target_oklab;
|
||||
if (dot(delta, delta) > color_match_tolerance_sq)
|
||||
discard;
|
||||
color = color_match_highlight_color;
|
||||
} else {
|
||||
color.rgb = mix(color.rgb, texture_color.rgb, mix_factor);
|
||||
if (invalid_texture_mapping) {
|
||||
float checker = invalid_texture_mapping_checker();
|
||||
vec3 checker_color = mix(vec3(0.0), vec3(1.0), checker);
|
||||
color.rgb = mix(color.rgb, checker_color, 0.62);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 pv_check_min = ZERO;
|
||||
vec3 pv_check_max = ZERO;
|
||||
if (print_volume.type == 0) {
|
||||
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
|
||||
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
|
||||
}
|
||||
else if (print_volume.type == 1) {
|
||||
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
|
||||
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
|
||||
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
|
||||
}
|
||||
|
||||
color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
|
||||
}
|
||||
49
resources/shaders/110/painted_texture_preview.vs
Normal file
@@ -0,0 +1,49 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
attribute vec2 v_tex_coord;
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
varying vec2 tex_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||
world_normal = normalize(vec3(volume_world_matrix * vec4(v_normal, 0.0)));
|
||||
tex_coord = v_tex_coord;
|
||||
gl_Position = projection_matrix * position;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
96
resources/shaders/110/painted_vertex_color_preview.fs
Normal file
@@ -0,0 +1,96 @@
|
||||
#version 110
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float INVALID_TEXTURE_CHECKER_SCALE = 0.2;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
int type;
|
||||
vec4 xy_data;
|
||||
vec2 z_data;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform float texture_preview_mix;
|
||||
uniform bool invalid_texture_mapping;
|
||||
uniform bool color_match_preview_active;
|
||||
uniform vec3 color_match_target_oklab;
|
||||
uniform float color_match_tolerance_sq;
|
||||
uniform vec4 color_match_highlight_color;
|
||||
uniform vec4 color_match_background_color;
|
||||
uniform PrintVolumeDetection print_volume;
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
varying vec4 vertex_color;
|
||||
|
||||
float invalid_texture_mapping_checker()
|
||||
{
|
||||
vec3 normal_axes = abs(world_normal);
|
||||
vec2 checker_pos = world_pos.xy;
|
||||
if (normal_axes.x > normal_axes.y && normal_axes.x > normal_axes.z)
|
||||
checker_pos = world_pos.yz;
|
||||
else if (normal_axes.y > normal_axes.z)
|
||||
checker_pos = world_pos.xz;
|
||||
return mod(floor(checker_pos.x * INVALID_TEXTURE_CHECKER_SCALE) + floor(checker_pos.y * INVALID_TEXTURE_CHECKER_SCALE), 2.0);
|
||||
}
|
||||
|
||||
float srgb_channel_to_linear(float c)
|
||||
{
|
||||
return c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
vec3 oklab_from_srgb(vec3 c)
|
||||
{
|
||||
vec3 linear = vec3(srgb_channel_to_linear(c.r), srgb_channel_to_linear(c.g), srgb_channel_to_linear(c.b));
|
||||
float l = 0.4122214708 * linear.r + 0.5363325363 * linear.g + 0.0514459929 * linear.b;
|
||||
float m = 0.2119034982 * linear.r + 0.6806995451 * linear.g + 0.1073969566 * linear.b;
|
||||
float s = 0.0883024619 * linear.r + 0.2817188376 * linear.g + 0.6299787005 * linear.b;
|
||||
float l_ = pow(max(l, 0.0), 1.0 / 3.0);
|
||||
float m_ = pow(max(m, 0.0), 1.0 / 3.0);
|
||||
float s_ = pow(max(s, 0.0), 1.0 / 3.0);
|
||||
return vec3(0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
||||
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
||||
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec4 color = uniform_color;
|
||||
float mix_factor = clamp(texture_preview_mix, 0.0, 1.0);
|
||||
if (color_match_preview_active) {
|
||||
float source_alpha = clamp(vertex_color.a, 0.0, 1.0);
|
||||
vec3 source_rgb = vertex_color.rgb * source_alpha + color_match_background_color.rgb * (1.0 - source_alpha);
|
||||
vec3 delta = oklab_from_srgb(source_rgb) - color_match_target_oklab;
|
||||
if (dot(delta, delta) > color_match_tolerance_sq)
|
||||
discard;
|
||||
color = color_match_highlight_color;
|
||||
} else {
|
||||
color.rgb = mix(color.rgb, vertex_color.rgb, mix_factor);
|
||||
if (invalid_texture_mapping) {
|
||||
float checker = invalid_texture_mapping_checker();
|
||||
vec3 checker_color = mix(vec3(0.0), vec3(1.0), checker);
|
||||
color.rgb = mix(color.rgb, checker_color, 0.62);
|
||||
}
|
||||
}
|
||||
|
||||
vec3 pv_check_min = ZERO;
|
||||
vec3 pv_check_max = ZERO;
|
||||
if (print_volume.type == 0) {
|
||||
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
|
||||
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
|
||||
}
|
||||
else if (print_volume.type == 1) {
|
||||
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
|
||||
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
|
||||
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
|
||||
}
|
||||
|
||||
color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
|
||||
}
|
||||
49
resources/shaders/110/painted_vertex_color_preview.vs
Normal file
@@ -0,0 +1,49 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal;
|
||||
attribute vec4 v_color;
|
||||
|
||||
varying vec2 intensity;
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 world_pos;
|
||||
varying vec3 world_normal;
|
||||
varying vec4 vertex_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 eye_normal = normalize(view_normal_matrix * v_normal);
|
||||
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec4 position = view_model_matrix * vec4(v_position, 1.0);
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
world_pos = volume_world_matrix * vec4(v_position, 1.0);
|
||||
world_normal = normalize(vec3(volume_world_matrix * vec4(v_normal, 0.0)));
|
||||
vertex_color = v_color;
|
||||
gl_Position = projection_matrix * position;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
}
|
||||
10
resources/shaders/140/flat_vertex_color.fs
Normal file
@@ -0,0 +1,10 @@
|
||||
#version 140
|
||||
|
||||
in vec4 vertex_color;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
out_color = vertex_color;
|
||||
}
|
||||
15
resources/shaders/140/flat_vertex_color.vs
Normal file
@@ -0,0 +1,15 @@
|
||||
#version 140
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
in vec3 v_position;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 vertex_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
vertex_color = v_color;
|
||||
gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0);
|
||||
}
|
||||
@@ -31,12 +31,24 @@ uniform mat3 view_normal_matrix;
|
||||
in vec3 clipping_planes_dots;
|
||||
in vec4 model_pos;
|
||||
in vec4 world_pos;
|
||||
in float smooth_world_normal_z;
|
||||
|
||||
struct SlopeDetection
|
||||
{
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
int preview_mode;
|
||||
float top_z;
|
||||
float bottom_z;
|
||||
vec4 highlight_color;
|
||||
bool override_all;
|
||||
vec4 override_mask0;
|
||||
vec4 override_mask1;
|
||||
vec4 override_mask2;
|
||||
vec4 override_mask3;
|
||||
int current_state;
|
||||
int base_state;
|
||||
};
|
||||
uniform SlopeDetection slope;
|
||||
|
||||
@@ -64,6 +76,77 @@ vec3 getWireframeColor(vec3 fill) {
|
||||
}
|
||||
uniform bool show_wireframe;
|
||||
|
||||
float slopeOverrideMaskSlot(int slot) {
|
||||
if (slot == 0)
|
||||
return slope.override_mask0.x;
|
||||
if (slot == 1)
|
||||
return slope.override_mask0.y;
|
||||
if (slot == 2)
|
||||
return slope.override_mask0.z;
|
||||
if (slot == 3)
|
||||
return slope.override_mask0.w;
|
||||
if (slot == 4)
|
||||
return slope.override_mask1.x;
|
||||
if (slot == 5)
|
||||
return slope.override_mask1.y;
|
||||
if (slot == 6)
|
||||
return slope.override_mask1.z;
|
||||
if (slot == 7)
|
||||
return slope.override_mask1.w;
|
||||
if (slot == 8)
|
||||
return slope.override_mask2.x;
|
||||
if (slot == 9)
|
||||
return slope.override_mask2.y;
|
||||
if (slot == 10)
|
||||
return slope.override_mask2.z;
|
||||
if (slot == 11)
|
||||
return slope.override_mask2.w;
|
||||
if (slot == 12)
|
||||
return slope.override_mask3.x;
|
||||
if (slot == 13)
|
||||
return slope.override_mask3.y;
|
||||
if (slot == 14)
|
||||
return slope.override_mask3.z;
|
||||
if (slot == 15)
|
||||
return slope.override_mask3.w;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
bool slopeOverrideMatches(int state) {
|
||||
if (slope.override_all)
|
||||
return true;
|
||||
if (state < 0 || state >= 256)
|
||||
return false;
|
||||
int slot = state / 16;
|
||||
int bit = state - slot * 16;
|
||||
float mask = slopeOverrideMaskSlot(slot);
|
||||
float divisor = pow(2.0, float(bit));
|
||||
return floor(mod(floor(mask / divisor), 2.0)) > 0.5;
|
||||
}
|
||||
|
||||
bool slopePreviewMatches(float world_normal_z) {
|
||||
if (slope.preview_mode == 1)
|
||||
return world_normal_z >= slope.top_z - EPSILON;
|
||||
if (slope.preview_mode == 2)
|
||||
return world_normal_z <= slope.bottom_z + EPSILON;
|
||||
if (slope.preview_mode == 3)
|
||||
return world_normal_z <= slope.top_z + EPSILON && world_normal_z >= slope.bottom_z - EPSILON;
|
||||
return false;
|
||||
}
|
||||
|
||||
float slopePreviewChecker(vec3 position, vec3 normal) {
|
||||
vec2 uv;
|
||||
vec3 abs_normal = abs(normal);
|
||||
if (abs_normal.z >= abs_normal.x && abs_normal.z >= abs_normal.y)
|
||||
uv = position.xy;
|
||||
else if (abs_normal.x >= abs_normal.y)
|
||||
uv = position.yz;
|
||||
else
|
||||
uv = position.xz;
|
||||
vec2 cells = floor(uv / 3.0);
|
||||
return mod(cells.x + cells.y, 2.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
@@ -82,15 +165,39 @@ void main()
|
||||
vec3 transformed_normal = normalize(slope.volume_world_normal_matrix * triangle_normal);
|
||||
|
||||
if (slope.actived) {
|
||||
if(world_pos.z<0.1&&world_pos.z>-0.1)
|
||||
{
|
||||
color = LightBlue;
|
||||
alpha = 1.0;
|
||||
}
|
||||
else if( transformed_normal.z < slope.normal_z - EPSILON)
|
||||
{
|
||||
color = color * 0.5 + LightRed * 0.5;
|
||||
alpha = 1.0;
|
||||
if (slope.preview_mode == 0) {
|
||||
if(world_pos.z<0.1&&world_pos.z>-0.1)
|
||||
{
|
||||
color = LightBlue;
|
||||
alpha = 1.0;
|
||||
}
|
||||
else if( transformed_normal.z < slope.normal_z - EPSILON)
|
||||
{
|
||||
color = color * 0.5 + LightRed * 0.5;
|
||||
alpha = 1.0;
|
||||
}
|
||||
} else if (slope.preview_mode == 4) {
|
||||
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
|
||||
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
|
||||
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
|
||||
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
|
||||
color = mix(preview_color_a, preview_color_b, preview_check);
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
} else if (slope.preview_mode == 5) {
|
||||
color = slope.highlight_color.rgb;
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
} else {
|
||||
int effective_state = slope.current_state == 0 ? slope.base_state : slope.current_state;
|
||||
if (slopeOverrideMatches(effective_state) && slopePreviewMatches(smooth_world_normal_z)) {
|
||||
float preview_luma = dot(slope.highlight_color.rgb, vec3(0.2126, 0.7152, 0.0722));
|
||||
vec3 preview_accent = preview_luma > 0.75 ? vec3(0.02, 0.08, 0.24) : vec3(1.0, 1.0, 1.0);
|
||||
float preview_check = slopePreviewChecker(world_pos.xyz, transformed_normal);
|
||||
vec3 preview_color_a = slope.highlight_color.rgb * 0.78 + preview_accent * 0.22;
|
||||
vec3 preview_color_b = slope.highlight_color.rgb * 0.25 + preview_accent * 0.75;
|
||||
color = mix(preview_color_a, preview_color_b, preview_check);
|
||||
alpha = max(alpha, slope.highlight_color.a);
|
||||
}
|
||||
}
|
||||
}
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
|
||||
@@ -12,11 +12,13 @@ uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
in vec3 v_position;
|
||||
in vec3 v_slope_normal;
|
||||
in vec3 v_barycentric;
|
||||
|
||||
out vec3 clipping_planes_dots;
|
||||
out vec4 model_pos;
|
||||
out vec4 world_pos;
|
||||
out float smooth_world_normal_z;
|
||||
out vec3 barycentric_coordinates;
|
||||
|
||||
struct SlopeDetection
|
||||
@@ -24,6 +26,17 @@ struct SlopeDetection
|
||||
bool actived;
|
||||
float normal_z;
|
||||
mat3 volume_world_normal_matrix;
|
||||
int preview_mode;
|
||||
float top_z;
|
||||
float bottom_z;
|
||||
vec4 highlight_color;
|
||||
bool override_all;
|
||||
vec4 override_mask0;
|
||||
vec4 override_mask1;
|
||||
vec4 override_mask2;
|
||||
vec4 override_mask3;
|
||||
int current_state;
|
||||
int base_state;
|
||||
};
|
||||
uniform SlopeDetection slope;
|
||||
void main()
|
||||
@@ -31,6 +44,7 @@ void main()
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
// Point in homogenous coordinates.
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
smooth_world_normal_z = normalize(slope.volume_world_normal_matrix * v_slope_normal).z;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
|
||||
331
resources/shaders/140/painted_surface_gradient_preview.fs
Normal file
@@ -0,0 +1,331 @@
|
||||
#version 140
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const float INVALID_TEXTURE_CHECKER_SCALE = 0.2;
|
||||
const int MAX_GRADIENT_COMPONENTS = 10;
|
||||
const int MAX_LINEAR_GRADIENT_LUT_COLORS = 33;
|
||||
const float EPSILON = 0.000001;
|
||||
|
||||
struct PrintVolumeDetection
|
||||
{
|
||||
int type;
|
||||
vec4 xy_data;
|
||||
vec2 z_data;
|
||||
};
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform float texture_preview_mix;
|
||||
uniform bool invalid_texture_mapping;
|
||||
uniform PrintVolumeDetection print_volume;
|
||||
|
||||
uniform int gradient_component_count;
|
||||
uniform vec3 gradient_base_color;
|
||||
uniform vec3 gradient_component_colors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_distances_mm[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_angles_deg[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_strength_factors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_minimum_offset_factors[MAX_GRADIENT_COMPONENTS];
|
||||
uniform float gradient_max_component_distance_mm;
|
||||
uniform float gradient_max_width_delta_limit_mm;
|
||||
uniform int gradient_angle_mode;
|
||||
uniform bool gradient_rotation_enabled;
|
||||
uniform float gradient_rotations;
|
||||
uniform float gradient_repeats;
|
||||
uniform bool gradient_reverse_repeats;
|
||||
uniform bool gradient_clockwise;
|
||||
uniform int gradient_fade_mode;
|
||||
uniform vec3 gradient_center;
|
||||
uniform float gradient_z_min;
|
||||
uniform float gradient_z_max;
|
||||
uniform bool gradient_linear_mode;
|
||||
uniform bool gradient_linear_radial_mode;
|
||||
uniform vec3 gradient_linear_start;
|
||||
uniform vec3 gradient_linear_end;
|
||||
uniform float gradient_linear_radius_mm;
|
||||
uniform int gradient_linear_lut_count;
|
||||
uniform vec3 gradient_linear_lut_colors[MAX_LINEAR_GRADIENT_LUT_COLORS];
|
||||
|
||||
in vec2 intensity;
|
||||
in vec3 clipping_planes_dots;
|
||||
in vec4 world_pos;
|
||||
in vec3 world_normal;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
float normalize_angle(float angle)
|
||||
{
|
||||
float out_angle = mod(angle, 360.0);
|
||||
if (out_angle < 0.0)
|
||||
out_angle += 360.0;
|
||||
return out_angle;
|
||||
}
|
||||
|
||||
float angular_distance_deg(float a, float b)
|
||||
{
|
||||
float d = abs(normalize_angle(a) - normalize_angle(b));
|
||||
return min(d, 360.0 - d);
|
||||
}
|
||||
|
||||
float angular_distance_cw(float from_deg, float to_deg)
|
||||
{
|
||||
float d = normalize_angle(to_deg) - normalize_angle(from_deg);
|
||||
if (d < 0.0)
|
||||
d += 360.0;
|
||||
return d;
|
||||
}
|
||||
|
||||
float repeated_rotation_progress(float progress01, float repeats, bool reverse_repeats)
|
||||
{
|
||||
float p = clamp(progress01, 0.0, 1.0);
|
||||
float r = max(1.0, repeats);
|
||||
if (r <= 1.0 + EPSILON)
|
||||
return p;
|
||||
|
||||
float repeated_pos = p * r;
|
||||
float segment_idx = floor(repeated_pos);
|
||||
float local = repeated_pos - segment_idx;
|
||||
|
||||
if (p >= 1.0 - EPSILON) {
|
||||
segment_idx = max(0.0, ceil(r) - 1.0);
|
||||
local = 1.0;
|
||||
}
|
||||
|
||||
if (reverse_repeats && mod(segment_idx, 2.0) >= 1.0)
|
||||
local = 1.0 - local;
|
||||
return clamp(local, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float offset_fade_factor(int fade_mode, float progress01)
|
||||
{
|
||||
float p = clamp(progress01, 0.0, 1.0);
|
||||
if (fade_mode == 1)
|
||||
return p;
|
||||
if (fade_mode == 2)
|
||||
return 1.0 - p;
|
||||
if (fade_mode == 3)
|
||||
return 1.0 - abs(2.0 * p - 1.0);
|
||||
if (fade_mode == 4)
|
||||
return abs(2.0 * p - 1.0);
|
||||
if (fade_mode == 5)
|
||||
return 1.0 - 2.0 * p;
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
float component_angular_influence(int component_idx, float theta_deg)
|
||||
{
|
||||
int count = min(gradient_component_count, MAX_GRADIENT_COMPONENTS);
|
||||
if (count <= 0)
|
||||
return 0.0;
|
||||
if (count == 1)
|
||||
return 1.0;
|
||||
|
||||
float self_angle = normalize_angle(gradient_angles_deg[component_idx]);
|
||||
float prev_angle = self_angle;
|
||||
float next_angle = self_angle;
|
||||
float prev_to_self_deg = 360.0;
|
||||
float self_to_next_deg = 360.0;
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count || i == component_idx)
|
||||
continue;
|
||||
float other_angle = normalize_angle(gradient_angles_deg[i]);
|
||||
float prev_distance = angular_distance_cw(other_angle, self_angle);
|
||||
float next_distance = angular_distance_cw(self_angle, other_angle);
|
||||
if (prev_distance < prev_to_self_deg) {
|
||||
prev_to_self_deg = prev_distance;
|
||||
prev_angle = other_angle;
|
||||
}
|
||||
if (next_distance < self_to_next_deg) {
|
||||
self_to_next_deg = next_distance;
|
||||
next_angle = other_angle;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev_to_self_deg <= 0.001 || self_to_next_deg <= 0.001) {
|
||||
float total_weight = 0.0;
|
||||
float active_weight = 0.0;
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float weight = max(0.0, 1.0 - angular_distance_deg(theta_deg, gradient_angles_deg[i]) / 180.0);
|
||||
total_weight += weight;
|
||||
if (i == component_idx)
|
||||
active_weight += weight;
|
||||
}
|
||||
if (total_weight <= EPSILON)
|
||||
return 0.0;
|
||||
return clamp(active_weight / total_weight, 0.0, 1.0);
|
||||
}
|
||||
|
||||
float theta_norm = normalize_angle(theta_deg);
|
||||
float prev_to_theta_deg = angular_distance_cw(prev_angle, theta_norm);
|
||||
if (prev_to_theta_deg <= prev_to_self_deg + 0.0001)
|
||||
return clamp(prev_to_theta_deg / prev_to_self_deg, 0.0, 1.0);
|
||||
|
||||
float self_to_theta_deg = angular_distance_cw(self_angle, theta_norm);
|
||||
if (self_to_theta_deg <= self_to_next_deg + 0.0001)
|
||||
return clamp(1.0 - self_to_theta_deg / self_to_next_deg, 0.0, 1.0);
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
float variable_width_delta(float inset_strength, float max_width_delta_limit_mm, float minimum_offset_factor, float strength_factor)
|
||||
{
|
||||
if (max_width_delta_limit_mm <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
float desired_width_factor = 1.0 - clamp(inset_strength, 0.0, 1.0);
|
||||
float min_width_factor = clamp(minimum_offset_factor, 0.0, 1.0);
|
||||
float adjusted_width_factor = min_width_factor + desired_width_factor * clamp(strength_factor, 0.0, 1.0) * (1.0 - min_width_factor);
|
||||
return clamp(max_width_delta_limit_mm * (1.0 - adjusted_width_factor), 0.0, max_width_delta_limit_mm);
|
||||
}
|
||||
|
||||
vec3 linear_gradient_lut_color(float t)
|
||||
{
|
||||
int count = min(gradient_linear_lut_count, MAX_LINEAR_GRADIENT_LUT_COLORS);
|
||||
if (count <= 0)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
if (count == 1)
|
||||
return clamp(gradient_linear_lut_colors[0], 0.0, 1.0);
|
||||
|
||||
float scaled = clamp(t, 0.0, 1.0) * float(count - 1);
|
||||
int lower = int(floor(scaled));
|
||||
int upper = min(lower + 1, count - 1);
|
||||
float f = scaled - float(lower);
|
||||
return clamp(mix(gradient_linear_lut_colors[lower], gradient_linear_lut_colors[upper], f), 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec3 linear_gradient_color(int count)
|
||||
{
|
||||
float t = 0.0;
|
||||
if (gradient_linear_radial_mode) {
|
||||
t = clamp(length(world_pos.xyz - gradient_linear_start) / max(gradient_linear_radius_mm, EPSILON), 0.0, 1.0);
|
||||
} else {
|
||||
vec3 direction_vec = gradient_linear_end - gradient_linear_start;
|
||||
float denom = dot(direction_vec, direction_vec);
|
||||
t = denom > EPSILON ? clamp(dot(world_pos.xyz - gradient_linear_start, direction_vec) / denom, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
if (count <= 0)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
return linear_gradient_lut_color(t);
|
||||
}
|
||||
|
||||
vec3 surface_gradient_color()
|
||||
{
|
||||
int count = min(gradient_component_count, MAX_GRADIENT_COMPONENTS);
|
||||
if (count <= 0)
|
||||
return uniform_color.rgb;
|
||||
if (gradient_linear_mode)
|
||||
return linear_gradient_color(count);
|
||||
|
||||
float z_span = gradient_z_max - gradient_z_min;
|
||||
float z_progress = z_span > EPSILON ? clamp((world_pos.z - gradient_z_min) / z_span, 0.0, 1.0) : 0.0;
|
||||
|
||||
float rotation_deg = 0.0;
|
||||
if (gradient_rotation_enabled) {
|
||||
float repeated = repeated_rotation_progress(z_progress, max(1.0, gradient_repeats), gradient_reverse_repeats);
|
||||
float direction = gradient_clockwise ? -1.0 : 1.0;
|
||||
rotation_deg = direction * 360.0 * gradient_rotations * repeated;
|
||||
}
|
||||
|
||||
vec2 direction_vec = vec2(0.0);
|
||||
if (gradient_angle_mode == 1)
|
||||
direction_vec = world_normal.xy;
|
||||
if (dot(direction_vec, direction_vec) <= EPSILON) {
|
||||
vec3 radial = world_pos.xyz - gradient_center;
|
||||
direction_vec = radial.xy;
|
||||
}
|
||||
if (dot(direction_vec, direction_vec) <= EPSILON)
|
||||
direction_vec = vec2(1.0, 0.0);
|
||||
|
||||
float theta_deg = normalize_angle(degrees(atan(direction_vec.y, direction_vec.x)) - rotation_deg);
|
||||
float signed_fade_factor = offset_fade_factor(gradient_fade_mode, z_progress);
|
||||
float fade_factor = abs(signed_fade_factor);
|
||||
float sample_theta_deg = signed_fade_factor < 0.0 ? normalize_angle(theta_deg + 180.0) : theta_deg;
|
||||
float influences[MAX_GRADIENT_COMPONENTS];
|
||||
float visibility_weights[MAX_GRADIENT_COMPONENTS];
|
||||
float min_visibility = 1.0;
|
||||
float max_visibility = 0.0;
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
influences[i] = 0.0;
|
||||
visibility_weights[i] = 0.0;
|
||||
if (i < count)
|
||||
influences[i] = component_angular_influence(i, sample_theta_deg);
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float raw_inset_mm = 0.0;
|
||||
for (int j = 0; j < MAX_GRADIENT_COMPONENTS; ++j) {
|
||||
if (j >= count || i == j)
|
||||
continue;
|
||||
raw_inset_mm += gradient_distances_mm[j] * influences[j];
|
||||
}
|
||||
float inset_strength = clamp(raw_inset_mm / max(gradient_max_component_distance_mm, EPSILON), 0.0, 1.0);
|
||||
float width_delta_mm = variable_width_delta(inset_strength * fade_factor,
|
||||
gradient_max_width_delta_limit_mm,
|
||||
gradient_minimum_offset_factors[i],
|
||||
gradient_strength_factors[i]);
|
||||
visibility_weights[i] = 1.0 - clamp(width_delta_mm / max(gradient_max_width_delta_limit_mm, EPSILON), 0.0, 1.0);
|
||||
min_visibility = min(min_visibility, visibility_weights[i]);
|
||||
max_visibility = max(max_visibility, visibility_weights[i]);
|
||||
}
|
||||
|
||||
vec3 target_color = vec3(0.0);
|
||||
float total_excess_weight = 0.0;
|
||||
for (int i = 0; i < MAX_GRADIENT_COMPONENTS; ++i) {
|
||||
if (i >= count)
|
||||
continue;
|
||||
float weight = max(0.0, visibility_weights[i] - min_visibility);
|
||||
target_color += gradient_component_colors[i] * weight;
|
||||
total_excess_weight += weight;
|
||||
}
|
||||
if (total_excess_weight <= EPSILON)
|
||||
return clamp(gradient_base_color, 0.0, 1.0);
|
||||
float contrast = clamp(max_visibility - min_visibility, 0.0, 1.0);
|
||||
target_color = clamp(target_color / total_excess_weight, 0.0, 1.0);
|
||||
return mix(clamp(gradient_base_color, 0.0, 1.0), target_color, contrast);
|
||||
}
|
||||
|
||||
float invalid_texture_mapping_checker()
|
||||
{
|
||||
vec3 normal_axes = abs(world_normal);
|
||||
vec2 checker_pos = world_pos.xy;
|
||||
if (normal_axes.x > normal_axes.y && normal_axes.x > normal_axes.z)
|
||||
checker_pos = world_pos.yz;
|
||||
else if (normal_axes.y > normal_axes.z)
|
||||
checker_pos = world_pos.xz;
|
||||
return mod(floor(checker_pos.x * INVALID_TEXTURE_CHECKER_SCALE) + floor(checker_pos.y * INVALID_TEXTURE_CHECKER_SCALE), 2.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec4 color = uniform_color;
|
||||
float mix_factor = clamp(texture_preview_mix, 0.0, 1.0);
|
||||
color.rgb = mix(color.rgb, surface_gradient_color(), mix_factor);
|
||||
if (invalid_texture_mapping) {
|
||||
float checker = invalid_texture_mapping_checker();
|
||||
vec3 checker_color = mix(vec3(0.0), vec3(1.0), checker);
|
||||
color.rgb = mix(color.rgb, checker_color, 0.62);
|
||||
}
|
||||
|
||||
vec3 pv_check_min = ZERO;
|
||||
vec3 pv_check_max = ZERO;
|
||||
if (print_volume.type == 0) {
|
||||
pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x);
|
||||
pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y);
|
||||
}
|
||||
else if (print_volume.type == 1) {
|
||||
float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy);
|
||||
pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x);
|
||||
pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y);
|
||||
}
|
||||
|
||||
color.rgb = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color.rgb, ZERO, 0.3333) : color.rgb;
|
||||
out_color = vec4(vec3(intensity.y) + color.rgb * intensity.x, color.a);
|
||||
}
|
||||