Merge remote-tracking branch 'upstream/main'

# Conflicts:
#	src/libslic3r/Preset.cpp
This commit is contained in:
Clifford Garwood
2026-05-02 01:17:02 -04:00
168 changed files with 11213 additions and 4000 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
+42 -17
View File
@@ -1,23 +1,48 @@
# Repository Guidelines
# CLAUDE.md
## Project Structure & Module Organization
OrcaSlicer’s C++17 sources live in `src/`, split by feature modules and platform adapters. User assets, icons, and printer presets are in `resources/`; translations stay in `localization/`. Tests sit in `tests/`, grouped by domain (`libslic3r/`, `sla_print/`, etc.) with fixtures under `tests/data/`. CMake helpers reside in `cmake/`, and longer references in `doc/` and `SoftFever_doc/`. Automation scripts belong in `scripts/` and `tools/`. Treat everything in `deps/` and `deps_src/` as vendored snapshots—do not modify without mirroring upstream tags.
OrcaSlicer — open-source C++17 3D slicer. wxWidgets GUI, CMake build system.
## Build, Test, and Development Commands
Use out-of-source builds:
- `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release` configures dependencies and generates build files.
- `cmake --build build --target OrcaSlicer --config Release` compiles the app; add `--parallel` to speed up.
- `cmake --build build --target tests` then `ctest --test-dir build --output-on-failure` runs automated suites.
Platform helpers such as `build_linux.sh`, `build_release_macos.sh`, and `build_release_vs2022.bat` wrap the same flow with toolchain flags. Use `build_release_macos.sh -sx` when reproducing macOS build issues, and `scripts/DockerBuild.sh` for reproducible container builds.
## Build Commands
## Coding Style & Naming Conventions
`.clang-format` enforces 4-space indents, a 140-column limit, aligned initializers, and brace wrapping for classes and functions. Run `clang-format -i <file>` before committing; the CMake `clang-format` target is available when LLVM tools are on your PATH. Prefer `CamelCase` for classes, `snake_case` for functions and locals, and `SCREAMING_CASE` for constants, matching conventions in `src/`. Keep headers self-contained and align include order with the IWYU pragmas.
```bash
# macOS
cmake --build build/arm64 --config RelWithDebInfo --target all --
## Testing Guidelines
Unit tests rely on Catch2 (`tests/catch2/`). Name specs after the component under test—for example `tests/libslic3r/TestPlanarHole.cpp`—and tag long-running cases so `ctest -L fast` remains useful. Cover new algorithms with deterministic fixtures or sample G-code stored in `tests/data/`. Document manual printer validation or regression slicer checks in your PR when automated coverage is insufficient.
# Linux
cmake --build build --config RelWithDebInfo --target all --
## Commit & Pull Request Guidelines
The history favors concise, sentence-style subject lines with optional issue references, e.g., `Fix grid lines origin for multiple plates (#10724)`. Squash fixups locally before opening a PR. Complete `.github/pull_request_template.md`, include reproduction steps or screenshots for UI changes, and mention impacted presets or translations. Link issues via `Closes #NNNN` when applicable, and call out dependency bumps or profile migrations for maintainer review.
# Windows (replace %build_type% with Debug/Release/RelWithDebInfo)
cmake --build . --config %build_type% --target ALL_BUILD -- -m
```
## Security & Configuration Tips
Follow `SECURITY.md` for vulnerability reporting. Keep API tokens and printer credentials out of tracked configs; use `sandboxes/` for experimental settings. When touching third-party code in `deps_src/`, record the upstream commit or release in your PR description and run the relevant platform build script to confirm integration.
## Testing
Catch2 framework. Tests in `tests/` directory.
```bash
cd build && ctest --output-on-failure # all tests
ctest --test-dir ./tests/libslic3r # individual suite
ctest --test-dir ./tests/fff_print
```
## Code Style
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
- `#pragma once` for headers. Smart pointers and RAII preferred
- Parallelization via TBB — be mindful of shared state
## Key Entry Points
- App startup: `src/OrcaSlicer.cpp`
- Slicing pipeline: `src/libslic3r/Print.cpp`
- All print/printer/material settings: `src/libslic3r/PrintConfig.cpp`
- GUI: `src/slic3r/GUI/`
- Core algorithms: `src/libslic3r/` (GCode/, Fill/, Support/, Geometry/, Format/, Arachne/)
- Printer profiles: `resources/profiles/[manufacturer].json`
## Critical Constraints
- **Backward compatibility required** for .3mf project files and printer profiles
- **Cross-platform** — all changes must work on Windows, macOS, and Linux
- Profile/format changes need version migration handling
- Dependencies built separately in `deps/build/`, then linked to main app
+1 -234
View File
@@ -1,234 +1 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
OrcaSlicer is an open-source 3D slicer application forked from Bambu Studio, built using C++ with wxWidgets for the GUI and CMake as the build system. The project uses a modular architecture with separate libraries for core slicing functionality, GUI components, and platform-specific code.
## Build Commands
### Building on Windows
**Always use this command to build the project when testing build issues on Windows.**
```bash
cmake --build . --config %build_type% --target ALL_BUILD -- -m
```
### Building on macOS
**Always use this command to build the project when testing build issues on macOS.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Build test:
**Always use this command to build the project when testing build issues on Windows.**
```bash
cmake --build . --config %build_type% --target ALL_BUILD -- -m
```
### Building on macOS
**Always use this command to build the project when testing build issues on macOS.**
```bash
cmake --build build/arm64 --config RelWithDebInfo --target all --
```
### Building on Linux
**Always use this command to build the project when testing build issues on Linux.**
```bash
cmake --build build --config RelWithDebInfo --target all --
```
### Build System
- Uses CMake with minimum version 3.13 (maximum 3.31.x on Windows)
- Primary build directory: `build/`
- Dependencies are built in `deps/build/`
- The build process is split into dependency building and main application building
- Windows builds use Visual Studio generators
- macOS builds use Xcode by default, Ninja with -x flag
- Linux builds use Ninja generator
### Testing
Tests are located in the `tests/` directory and use the Catch2 testing framework. Test structure:
- `tests/libslic3r/` - Core library tests (21 test files)
- Geometry processing, algorithms, file formats (STL, 3MF, AMF)
- Polygon operations, clipper utilities, Voronoi diagrams
- `tests/fff_print/` - Fused Filament Fabrication tests (12 test files)
- Slicing algorithms, G-code generation, print mechanics
- Fill patterns, extrusion, support material
- `tests/sla_print/` - Stereolithography tests (4 test files)
- SLA-specific printing algorithms, support generation
- `tests/libnest2d/` - 2D nesting algorithm tests
- `tests/slic3rutils/` - Utility function tests
- `tests/sandboxes/` - Experimental/sandbox test code
Run all tests after building:
```bash
cd build && ctest
```
Run tests with verbose output:
```bash
cd build && ctest --output-on-failure
```
Run individual test suites:
```bash
# From build directory
ctest --test-dir ./tests/libslic3r/libslic3r_tests
ctest --test-dir ./tests/fff_print/fff_print_tests
ctest --test-dir ./tests/sla_print/sla_print_tests
# and so on
```
## Architecture
### Core Libraries
- **libslic3r/**: Core slicing engine and algorithms (platform-independent)
- Main slicing logic, geometry processing, G-code generation
- Key classes: Print, PrintObject, Layer, GCode, Config
- Modular design with specialized subdirectories:
- `GCode/` - G-code generation, cooling, pressure equalization, thumbnails
- `Fill/` - Infill pattern implementations (gyroid, honeycomb, lightning, etc.)
- `Support/` - Tree supports and traditional support generation
- `Geometry/` - Advanced geometry operations, Voronoi diagrams, medial axis
- `Format/` - File I/O for 3MF, AMF, STL, OBJ, STEP formats
- `SLA/` - SLA-specific print processing and support generation
- `Arachne/` - Advanced wall generation using skeletal trapezoidation
- **src/slic3r/**: Main application framework and GUI
- GUI application built with wxWidgets
- Integration between libslic3r core and user interface
- Located in `src/slic3r/GUI/` (not shown in this directory but exists)
### Key Algorithmic Components
- **Arachne Wall Generation**: Variable-width perimeter generation using skeletal trapezoidation
- **Tree Supports**: Organic support generation algorithm
- **Lightning Infill**: Sparse infill optimization for internal structures
- **Adaptive Slicing**: Variable layer height based on geometry
- **Multi-material**: Multi-extruder and soluble support processing
- **G-code Post-processing**: Cooling, fan control, pressure advance, conflict checking
### File Format Support
- **3MF/BBS_3MF**: Native format with extensions for multi-material and metadata
- **STL**: Standard tessellation language for 3D models
- **AMF**: Additive Manufacturing Format with color/material support
- **OBJ**: Wavefront OBJ with material definitions
- **STEP**: CAD format support for precise geometry
- **G-code**: Output format with extensive post-processing capabilities
### External Dependencies
- **Clipper2**: Advanced 2D polygon clipping and offsetting
- **libigl**: Computational geometry library for mesh operations
- **TBB**: Intel Threading Building Blocks for parallelization
- **wxWidgets**: Cross-platform GUI framework
- **OpenGL**: 3D graphics rendering and visualization
- **CGAL**: Computational Geometry Algorithms Library (selective use)
- **OpenVDB**: Volumetric data structures for advanced operations
- **Eigen**: Linear algebra library for mathematical operations
## File Organization
### Resources and Configuration
- `resources/profiles/` - Printer and material profiles organized by manufacturer
- `resources/printers/` - Printer-specific configurations and G-code templates
- `resources/images/` - UI icons, logos, calibration images
- `resources/calib/` - Calibration test patterns and data
- `resources/handy_models/` - Built-in test models (benchy, calibration cubes)
### Internationalization and Localization
- `localization/i18n/` - Source translation files (.pot, .po)
- `resources/i18n/` - Runtime language resources
- Translation managed via `scripts/run_gettext.sh` / `scripts/run_gettext.bat`
### Platform-Specific Code
- `src/libslic3r/Platform.cpp` - Platform abstractions and utilities
- `src/libslic3r/MacUtils.mm` - macOS-specific utilities (Objective-C++)
- Windows-specific build scripts and configurations
- Linux distribution support scripts in `scripts/linux.d/`
### Build and Development Tools
- `cmake/modules/` - Custom CMake find modules and utilities
- `scripts/` - Python utilities for profile generation and validation
- `tools/` - Windows build tools (gettext utilities)
- `deps/` - External dependency build configurations
## Development Workflow
### Code Style and Standards
- **C++17 standard** with selective C++20 features
- **Naming conventions**: PascalCase for classes, snake_case for functions/variables
- **Header guards**: Use `#pragma once`
- **Memory management**: Prefer smart pointers, RAII patterns
- **Thread safety**: Use TBB for parallelization, be mindful of shared state
### Common Development Tasks
#### Adding New Print Settings
1. Define setting in `PrintConfig.cpp` with proper bounds and defaults
2. Add UI controls in appropriate GUI components
3. Update serialization in config save/load
4. Add tooltips and help text for user guidance
5. Test with different printer profiles
#### Modifying Slicing Algorithms
1. Core algorithms live in `libslic3r/` subdirectories
2. Performance-critical code should be profiled and optimized
3. Consider multi-threading implications (TBB integration)
4. Validate changes don't break existing profiles
5. Add regression tests where appropriate
#### GUI Development
1. GUI code resides in `src/slic3r/GUI/` (not visible in current tree)
2. Use existing wxWidgets patterns and custom controls
3. Support both light and dark themes
4. Consider DPI scaling on high-resolution displays
5. Maintain cross-platform compatibility
#### Adding Printer Support
1. Create JSON profile in `resources/profiles/[manufacturer].json`
2. Add printer-specific start/end G-code templates
3. Configure build volume, capabilities, and material compatibility
4. Test thoroughly with actual hardware when possible
5. Follow existing profile structure and naming conventions
### Dependencies and Build System
- **CMake-based** with separate dependency building phase
- **Dependencies** built once in `deps/build/`, then linked to main application
- **Cross-platform** considerations important for all changes
- **Resource files** embedded at build time, platform-specific handling
### Performance Considerations
- **Slicing algorithms** are CPU-intensive, profile before optimizing
- **Memory usage** can be substantial with complex models
- **Multi-threading** extensively used via TBB
- **File I/O** optimized for large 3MF files with embedded textures
- **Real-time preview** requires efficient mesh processing
## Important Development Notes
### Codebase Navigation
- Use search tools extensively - codebase has 500k+ lines
- Key entry points: `src/OrcaSlicer.cpp` for application startup
- Core slicing: `libslic3r/Print.cpp` orchestrates the slicing pipeline
- Configuration: `PrintConfig.cpp` defines all print/printer/material settings
### Compatibility and Stability
- **Backward compatibility** maintained for project files and profiles
- **Cross-platform** support essential (Windows/macOS/Linux)
- **File format** changes require careful version handling
- **Profile migrations** needed when settings change significantly
### Quality and Testing
- **Regression testing** important due to algorithm complexity
- **Performance benchmarks** help catch performance regressions
- **Memory leak** detection important for long-running GUI application
- **Cross-platform** testing required before releases
@AGENTS.md
+3 -1
View File
@@ -785,8 +785,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
set(_arch "x64")
elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "X86")
set(_arch "x86")
elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ARM64")
set(_arch "arm64")
else ()
message(FATAL_ERROR "Unable to detect architecture")
message(FATAL_ERROR "Unable to detect architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif ()
get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+10 -1
View File
@@ -504,6 +504,15 @@ if [[ -n "${USE_LLD}" ]] ; then
fi
fi
# Auto-detect ccache for faster rebuilds
export CMAKE_CCACHE_ARGS=()
if command -v ccache >/dev/null 2>&1 ; then
echo "ccache found at $(command -v ccache), enabling compiler caching..."
export CMAKE_CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
else
echo "Note: ccache not found. Install ccache for faster rebuilds."
fi
if [[ -n "${BUILD_DEPS}" ]] ; then
echo "Configuring dependencies..."
read -r -a BUILD_ARGS <<< "${DEPS_EXTRA_BUILD_ARGS}"
@@ -536,7 +545,7 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
BUILD_ARGS+=(-DORCA_UPDATER_SIG_KEY="${ORCA_UPDATER_SIG_KEY}")
fi
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" -G "Ninja Multi-Config" \
print_and_run cmake -S . -B $BUILD_DIR "${CMAKE_C_CXX_COMPILER_CLANG[@]}" "${CMAKE_LLD_LINKER_ARGS[@]}" "${CMAKE_CCACHE_ARGS[@]}" -G "Ninja Multi-Config" \
-DSLIC3R_PCH=${SLIC3R_PRECOMPILED_HEADERS} \
-DORCA_TOOLS=ON \
"${COLORED_OUTPUT}" \
+5 -1
View File
@@ -265,8 +265,12 @@ if (MSVC)
message(STATUS "\nDetected X86 compiler => building X86 deps bundle\n")
set(DEPS_ARCH "x86")
include("deps-windows.cmake")
elseif ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ARM64")
message(STATUS "\nDetected ARM64 compiler => building ARM64 deps bundle\n")
set(DEPS_ARCH "arm64")
include("deps-windows.cmake")
else ()
message(FATAL_ERROR "Unable to detect architecture")
message(FATAL_ERROR "Unable to detect architecture: ${CMAKE_SYSTEM_PROCESSOR}")
endif ()
elseif (APPLE)
message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}")
+7 -2
View File
@@ -36,8 +36,10 @@ if ("${DEPS_ARCH}" STREQUAL "x86")
set(DEP_PLATFORM "Win32")
elseif ("${DEPS_ARCH}" STREQUAL "x64")
set(DEP_PLATFORM "x64")
elseif ("${DEPS_ARCH}" STREQUAL "arm64")
set(DEP_PLATFORM "ARM64")
else ()
message(FATAL_ERROR "Unsupported OS architecture")
message(FATAL_ERROR "Unsupported OS architecture: ${DEPS_ARCH}")
endif ()
if (${DEP_DEBUG})
@@ -64,8 +66,11 @@ if ("${DEPS_ARCH}" STREQUAL "x86")
elseif ("${DEPS_ARCH}" STREQUAL "x64")
set(DEP_WXWIDGETS_TARGET "TARGET_CPU=X64")
set(DEP_WXWIDGETS_LIBDIR "vc_x64_lib")
elseif ("${DEPS_ARCH}" STREQUAL "arm64")
set(DEP_WXWIDGETS_TARGET "TARGET_CPU=ARM64")
set(DEP_WXWIDGETS_LIBDIR "vc_arm64_lib")
else ()
message(FATAL_ERROR "Unsupported OS architecture")
message(FATAL_ERROR "Unsupported OS architecture: ${DEPS_ARCH}")
endif ()
find_package(Git REQUIRED)
+16 -1
View File
@@ -111,6 +111,7 @@ var LangText = {
orca3: "Stealth Mode",
orca4: "This stops the transmission of data to Bambu's cloud services. Users who don't use BBL machines or use LAN mode only can safely turn on this function.",
orca5: "Enable Stealth Mode.",
orca6: "Bambu Cloud",
},
ca_ES: {
t1: "Benvingut a Orca Slicer",
@@ -220,6 +221,7 @@ var LangText = {
t113: "Pots canviar la teva elecció en les preferències en qualsevol moment.",
orca1: "Editar Informació del Projecte",
orca2: "no hi ha informació del model",
orca6: "Bambu Cloud",
},
es_ES: {
t1: "Bienvenido a Orca Slicer",
@@ -333,6 +335,7 @@ var LangText = {
orca3: "Modo sigiloso",
orca4: "Esta función detiene la transmisión de datos a los servicios en la nube de Bambu. Los usuarios que no utilicen máquinas BBL o que solo usen el modo LAN pueden activar esta función con seguridad.",
orca5: "Activar modo sigiloso.",
orca6: "Bambu Cloud",
},
it_IT: {
t1: "Benvenuti in OrcaSlicer",
@@ -445,6 +448,7 @@ var LangText = {
orca3: "Modalità invisibile",
orca4: "Con questa modalità, la trasmissione dei dati ai servizi cloud di Bambu sarà interrotta. Gli utenti che non utilizzano macchine BBL o che usano solo la modalità LAN possono attivare questa funzione in modo sicuro.",
orca5: "Abilita la modalità invisibile.",
orca6: "Bambu Cloud",
},
de_DE: {
t1: "Willkommen im Orca Slicer",
@@ -548,6 +552,7 @@ var LangText = {
t126: "Laden……",
orca1: "Edit Project Info",
orca2: "no model information",
orca6: "Bambu Cloud",
},
cs_CZ: {
t1: "Vítejte v Orca Slicer",
@@ -651,6 +656,7 @@ var LangText = {
t126: "Načtení probíhá……",
orca1: "Edit Project Info",
orca2: "no model information",
orca6: "Bambu Cloud",
},
fr_FR: {
t1: "Bienvenue sur Orca Slicer",
@@ -773,6 +779,7 @@ var LangText = {
wk14: "Par rapport au format STL, le format STEP apporte des informations plus efficaces. Grâce à la grande précision de ce format, de nombreuses trajectoires d'extrusion peuvent être générées sous forme d'arcs. Il inclut également la relation d'assemblage de chaque pièce d'un modèle, qui peut être utilisée pour restaurer la vue d'assemblage après la coupe d'un modèle.",
wk15: "Texte 3D",
wk16: "Avec l'outil Texte 3D, les utilisateurs peuvent facilement créer diverses formes de texte 3D dans le projet, ce qui rend le modèle plus personnalisé. Orca Slicer fournit des dizaines de polices et prend en charge les styles gras et italique pour donner au texte une plus grande flexibilité.",
orca6: "Bambu Cloud",
},
zh_CN: {
t1: "欢迎使用Orca Slicer",
@@ -899,6 +906,7 @@ var LangText = {
wk16: "使用3D文本工具,用户可以轻松地在项目中创建各种3D文本形状,使模型更加个性化。Orca Slicer提供了数十种字体,并支持粗体和斜体样式,使文本具有更大的灵活性。",
orca1: "编辑项目信息",
orca2: "该模型没有相关信息",
orca6: "Bambu Cloud",
},
zh_TW: {
t1: "歡迎使用 Orca Slicer",
@@ -1004,6 +1012,7 @@ var LangText = {
wk16: "使用3D文字工具,使用者可以輕鬆地在項目中建立各種3D文字形狀,使模型更加個性化。Orca Slicer 提供了數十種字體,並支援粗體和斜體樣式,使文字具有更大的靈活性。",
orca1: "編輯專案資訊",
orca2: "沒有模型相關資訊",
orca6: "Bambu Cloud",
},
ru_RU: {
t1: "Приветствуем в Orca Slicer!",
@@ -1116,7 +1125,8 @@ var LangText = {
orca2: "Информация отсутствует",
orca3: "Режим конфиденциальности",
orca4: "Это остановит передачу данных в облачные сервисы Bambu. Помешает только владельцам Bambu Lab, не использующим режим «Только LAN».",
orca5: "Включить режим конфиденциальности"
orca5: "Включить режим конфиденциальности",
orca6: "Bambu Cloud",
},
ko_KR: {
t1: "Orca Slicer에 오신 것을 환영합니다",
@@ -1209,6 +1219,7 @@ var LangText = {
t126: "로딩 중……",
orca1: "Edit Project Info",
orca2: "no model information",
orca6: "Bambu Cloud",
},
tr_TR: {
t1: "Orca Slicer'a hoş geldiniz",
@@ -1322,6 +1333,7 @@ var LangText = {
orca3: "Gizli Mod",
orca4: "Bu, Bambu'nun bulut hizmetlerine veri iletimini durdurur. BBL makinelerini kullanmayan veya yalnızca LAN modunu kullanan kullanıcılar bu işlevi güvenle açabilir.",
orca5: "Gizli Modu etkinleştirin.",
orca6: "Bambu Cloud",
},
pl_PL: {
t1: "Witamy w Orca Slicer",
@@ -1435,6 +1447,7 @@ var LangText = {
orca3: "Tryb «Niewidzialny»",
orca4: "To wyłączy przesyłanie danych do usług chmurowych Bambu. Użytkownicy, którzy nie korzystają z maszyn BBL lub używają tylko trybu LAN, mogą bez obaw włączyć tę opcję.",
orca5: "Włącz tryb «Niewidzialny»",
orca6: "Bambu Cloud",
},
pt_BR: {
t1: "Bem-vindo ao Orca Slicer",
@@ -1548,6 +1561,7 @@ var LangText = {
orca3: "Modo Furtivo",
orca4: "Isso interrompe a transmissão de dados para os serviços de nuvem da Bambu. Usuários que não usam máquinas BBL ou usam somente o modo LAN podem ativar essa função com segurança.",
orca5: "Habilita Modo Furtivo.",
orca6: "Bambu Cloud",
},
lt_LT: {
t1: "Pasisveikinkite su Orca Slicer",
@@ -1660,6 +1674,7 @@ var LangText = {
orca3: "Slaptas režimas",
orca4: "Tai sustabdo duomenų perdavimą į Bambu debesijos paslaugas. Vartotojai, kurie nenaudoja BBL mašinų arba naudoja tik LAN režimą, gali drąsiai įjungti šią funkciją.",
orca5: "Įjungti slaptą režimą.",
orca6: "Bambu Cloud",
},
};
@@ -0,0 +1,75 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=0" />
<title>Export Presets</title>
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<script src="./index.js"></script>
</head>
<body onLoad="OnInit()">
<!-- ORCA column browser -->
<div class="cbr-browser-container">
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="1"/>
<span class="cbr-search-placeholder trans" tid="t15">printer</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="MachineList">
<div class="CValues">
<label><input type="checkbox" mode="all" onClick="ChooseAllMachine()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="2"/>
<span class="cbr-search-placeholder trans" tid="t16">filament</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="FilatypeList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllFilament()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
<div class="cbr-column">
<div class="cbr-column-title-container">
<div class="search-icon"></div>
<input type="text" class="cbr-search-bar" placeholder=" " tabindex="3"/>
<span class="cbr-search-placeholder trans" tid="t17">presets</span>
<div class="clear-icon"></div>
</div>
<div class="cbr-content thin-scroll" id="PresetList">
<div class="CValues">
<label><input type="checkbox" class="trans" tid="t11" onClick="ChooseAllPreset()" /><span class="trans" tid="t11">all</span></label>
</div>
<div class="cbr-no-items">No items</div>
</div>
</div>
</div>
<div id="AcceptArea">
<div id="export_cloud_btn" class="ButtonStyleConfirm ButtonTypeChoice">Export to OrcaCloud</div>
<div id="export_local_btn" class="ButtonStyleRegular ButtonTypeChoice">Export to folder</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
</div>
</body>
</html>
@@ -0,0 +1,374 @@
var g_profile = {
machines: [],
filaments: [],
presets: []
};
var g_search = {
machine: "",
filament: "",
preset: ""
};
function OnInit()
{
if (typeof TranslatePage === "function")
TranslatePage();
InstallInputSafeKeydown();
BindSearchInputs();
BindClearIcons();
BindBottomButtons();
// Always load demo data first so the page works without C++ backend.
// LoadDemoProfile();
RequestProfile();
}
function InstallInputSafeKeydown()
{
// common.js blocks all key events globally; allow typing in text inputs.
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
var target = e && e.target ? e.target : null;
var tag = target && target.tagName ? String(target.tagName).toUpperCase() : "";
var type = target && target.type ? String(target.type).toLowerCase() : "";
var editable =
!!(target && target.isContentEditable) ||
tag === "TEXTAREA" ||
(tag === "INPUT" && type !== "checkbox" && type !== "radio" && type !== "button" && type !== "submit");
if (editable)
return true;
if (e && e.keyCode === 27 && typeof ClosePage === "function")
ClosePage();
if (window.event) {
try { e.keyCode = 0; } catch (err) { }
e.returnValue = false;
}
if (e && typeof e.preventDefault === "function")
e.preventDefault();
return false;
};
}
function RequestProfile()
{
SendMessage("request_export_preset_profile", {});
}
function HandleStudio(pVal)
{
var payload = (typeof pVal === "string") ? SafeJsonParse(pVal) : pVal;
if (!payload || typeof payload !== "object")
return;
var cmd = String(payload.command || "");
if (cmd === "response_export_preset_profile" ) {
ApplyProfile(payload.data);
}
}
function ApplyProfile(profile)
{
g_profile.machines = BuildNameRows(profile.printers);
g_profile.filaments = BuildNameRows(profile.filaments);
g_profile.presets = BuildNameRows(profile.process);
RenderColumn("MachineList", g_profile.machines, "mode", "MachineClick");
RenderColumn("FilatypeList", g_profile.filaments, "filatype", "FilaClick");
RenderColumn("PresetList", g_profile.presets, "preset", "PresetClick");
ApplyColumnSearch("MachineList", g_search.machine);
ApplyColumnSearch("FilatypeList", g_search.filament);
ApplyColumnSearch("PresetList", g_search.preset);
}
function BuildNameRows(names)
{
var src = Array.isArray(names) ? names : [];
var out = [];
for (var n = 0; n < src.length; n++) {
var row = src[n];
if (row === undefined || row === null)
continue;
var name = String(row);
out.push({ id: name, label: name, checked: false });
}
return out;
}
function RenderColumn(listId, items, attrName, onChangeFn)
{
var root = $("#" + listId + " .CValues");
if (!root.length)
return;
root.find("label:gt(0)").remove();
var html = "";
for (var n = 0; n < items.length; n++) {
var one = items[n];
html += '<label data-dynamic="1">' +
'<input type="checkbox" data-key="' + EscapeAttr(one.id) + '" ' + attrName + '="' + EscapeAttr(one.id) + '"' +
(one.checked ? ' checked="checked"' : "") +
' onChange="' + onChangeFn + '()" />' +
'<span title="' + EscapeAttr(one.label) + '">' + EscapeHtml(one.label) + '</span>' +
'</label>';
}
root.append(html);
SyncMasterCheckbox(listId);
ToggleNoItems(listId, items.length === 0);
}
function ChooseAllMachine()
{
var checked = !!$("#MachineList .CValues input:first").prop("checked");
$("#MachineList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("MachineList", g_profile.machines);
}
function MachineClick()
{
SyncMasterCheckbox("MachineList");
SyncListFromDom("MachineList", g_profile.machines);
}
function ChooseAllFilament()
{
var checked = !!$("#FilatypeList .CValues input:first").prop("checked");
$("#FilatypeList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("FilatypeList", g_profile.filaments);
}
function FilaClick()
{
SyncMasterCheckbox("FilatypeList");
SyncListFromDom("FilatypeList", g_profile.filaments);
}
function ChooseAllPreset()
{
var checked = !!$("#PresetList .CValues input:first").prop("checked");
$("#PresetList .CValues input:gt(0)").prop("checked", checked);
SyncListFromDom("PresetList", g_profile.presets);
}
function PresetClick()
{
SyncMasterCheckbox("PresetList");
SyncListFromDom("PresetList", g_profile.presets);
}
function SyncMasterCheckbox(listId)
{
var all = $("#" + listId + " .CValues input:gt(0)");
var master = $("#" + listId + " .CValues input:first");
if (!all.length) {
master.prop("checked", false);
return;
}
master.prop("checked", all.length === all.filter(":checked").length);
}
function SyncListFromDom(listId, store)
{
var map = {};
for (var n = 0; n < store.length; n++)
map[store[n].id] = store[n];
$("#" + listId + " .CValues input:gt(0)").each(function () {
var id = String($(this).attr("data-key") || "");
if (map[id])
map[id].checked = !!$(this).prop("checked");
});
}
function BindSearchInputs()
{
var inputs = document.querySelectorAll(".cbr-search-bar");
if (inputs.length > 0) {
inputs[0].addEventListener("input", function () {
g_search.machine = String(this.value || "").toLowerCase();
ApplyColumnSearch("MachineList", g_search.machine);
});
}
if (inputs.length > 1) {
inputs[1].addEventListener("input", function () {
g_search.filament = String(this.value || "").toLowerCase();
ApplyColumnSearch("FilatypeList", g_search.filament);
});
}
if (inputs.length > 2) {
inputs[2].addEventListener("input", function () {
g_search.preset = String(this.value || "").toLowerCase();
ApplyColumnSearch("PresetList", g_search.preset);
});
}
}
function ApplyColumnSearch(listId, query)
{
var rows = $("#" + listId + " .CValues label:gt(0)");
var visibleCount = 0;
rows.each(function () {
var row = $(this);
var text = String(row.text() || "").toLowerCase();
var key = String(row.find("input").attr("data-key") || "").toLowerCase();
if (!query || text.indexOf(query) >= 0 || key.indexOf(query) >= 0) {
row.show();
visibleCount++;
}
else {
row.hide();
}
});
ToggleNoItems(listId, visibleCount === 0);
}
function ToggleNoItems(listId, show)
{
var node = $("#" + listId + " .cbr-no-items");
if (!node.length)
return;
if (show)
node.addClass("show");
else
node.removeClass("show");
}
function BindClearIcons()
{
var icons = document.querySelectorAll(".clear-icon");
for (var n = 0; n < icons.length; n++) {
icons[n].addEventListener("click", function () {
var parent = this.parentElement;
if (!parent)
return;
var input = parent.querySelector("input[type='text']");
if (!input)
return;
input.value = "";
input.dispatchEvent(new Event("input", { bubbles: true }));
input.focus();
});
}
}
function BindBottomButtons()
{
var backBtn = document.getElementById("back_btn");
var exportCloud = document.getElementById("export_cloud_btn")
var exportLocal = document.getElementById("export_local_btn");
var closeBtn = document.getElementById("close_btn");
backBtn?.addEventListener("click", function () {
SendMessage("navigate_back", {});
});
exportLocal?.addEventListener("click", function () {
SendMessage("export_local", BuildResultPayload());
});
closeBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "close_page"
};
SendWXMessage(JSON.stringify(tSend));
});
}
function BuildResultPayload()
{
return {
machines: g_profile.machines.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
filaments: g_profile.filaments.filter(function (x) { return x.checked; }).map(function (x) { return x.id; }),
presets: g_profile.presets.filter(function (x) { return x.checked; }).map(function (x) { return x.id; })
};
}
function LoadDemoProfile()
{
ApplyProfile({
machines: [
{ id: "printer_x1c_04", name: "X1 Carbon 0.4 nozzle", selected: 1 },
{ id: "printer_p1s_04", name: "P1S 0.4 nozzle", selected: 1 },
{ id: "printer_a1_04", name: "A1 0.4 nozzle", selected: 0 },
{ id: "printer_prusa_mk4_04", name: "Prusa MK4 0.4 nozzle", selected: 1 }
],
filaments: [
{ id: "filament_generic_pla", name: "Generic PLA", selected: 1 },
{ id: "filament_generic_petg", name: "Generic PETG", selected: 1 },
{ id: "filament_bambu_abs", name: "Bambu ABS", selected: 0 },
{ id: "filament_esun_pla_plus", name: "eSUN PLA+", selected: 1 }
],
presets: [
{ id: "preset_quality_020", name: "Quality 0.20mm", selected: 1 },
{ id: "preset_quality_012", name: "Quality 0.12mm", selected: 0 },
{ id: "preset_speed_024", name: "Speed 0.24mm", selected: 1 },
{ id: "preset_draft_028", name: "Draft 0.28mm", selected: 0 }
]
});
}
function SendMessage(command, data)
{
var msg = {};
msg.sequence_id = Math.round(new Date() / 1000);
msg.command = command;
if (data && typeof data === "object")
msg.data = data;
if (typeof SendWXMessage === "function")
SendWXMessage(JSON.stringify(msg));
}
function SafeJsonParse(str)
{
try {
return JSON.parse(str);
}
catch (err) {
return null;
}
}
function EscapeHtml(str)
{
return String(str)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function EscapeAttr(str)
{
return EscapeHtml(str);
}
@@ -0,0 +1,191 @@
:root {
--cbr-border-color: #d2d2d7;
--cbr-header-bg: #f6f7f9;
--cbr-panel-bg: #ffffff;
--cbr-input-bg: #ffffff;
--cbr-input-focus-bg: #f2f8f7;
--cbr-label-color: #7b7b84;
--cbr-icon-color: #75757f;
}
@media (prefers-color-scheme: dark) {
:root {
--cbr-border-color: #4a4a51;
--cbr-header-bg: #2f2f34;
--cbr-panel-bg: #2d2d31;
--cbr-input-bg: #2d2d31;
--cbr-input-focus-bg: #3b3b41;
--cbr-label-color: #b9b9bc;
--cbr-icon-color: #b9b9bc;
}
}
.cbr-browser-container {
flex: 1 1 auto;
min-height: 0;
margin: 10px 15px 0;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: minmax(0, 1fr);
border: 1px solid var(--cbr-border-color);
background: var(--cbr-panel-bg);
box-sizing: border-box;
}
.cbr-column {
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.cbr-column:not(:last-child) {
border-right: 1px solid var(--cbr-border-color);
}
.cbr-column-title-container {
position: relative;
display: flex;
align-items: center;
min-height: 36px;
padding: 3px 6px;
background: var(--cbr-header-bg);
border-bottom: 1px solid var(--cbr-border-color);
}
.cbr-search-bar {
width: 100%;
min-width: 0;
box-sizing: border-box;
font-size: 14px;
line-height: 22px;
padding: 2px 26px 2px 26px;
border: 1px solid transparent;
border-radius: 4px;
background: var(--cbr-input-bg);
}
.cbr-search-bar:hover,
.cbr-search-bar:focus {
border-color: var(--main-color);
outline: none;
}
.cbr-search-bar:focus {
background: var(--cbr-input-focus-bg);
}
.cbr-search-placeholder {
position: absolute;
left: 33px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
line-height: 20px;
color: var(--cbr-label-color);
pointer-events: none;
}
.cbr-search-bar:not(:placeholder-shown) + .cbr-search-placeholder {
opacity: 0;
}
.search-icon,
.clear-icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
color: var(--cbr-icon-color);
line-height: 1;
}
.search-icon {
left: 11px;
pointer-events: none;
}
.search-icon::before {
content: "";
width: 9px;
height: 9px;
box-sizing: border-box;
border: 1.8px solid currentColor;
border-radius: 50%;
}
.search-icon::after {
content: "";
position: absolute;
width: 5px;
height: 1.8px;
background: currentColor;
transform: rotate(45deg);
right: 0;
bottom: 2px;
}
.clear-icon {
right: 11px;
cursor: pointer;
display: none;
}
.clear-icon::before {
content: "";
position: absolute;
width: 10px;
height: 1.8px;
background: currentColor;
transform: rotate(45deg);
}
.clear-icon::after {
content: "";
position: absolute;
width: 10px;
height: 1.8px;
background: currentColor;
transform: rotate(-45deg);
}
.cbr-search-bar:not(:placeholder-shown) ~ .clear-icon {
display: flex;
}
.cbr-content {
min-height: 0;
overflow-y: auto;
padding: 4px 8px;
}
.cbr-column .CValues {
display: grid;
}
.CValues label {
margin: 0 !important;
padding: 1px 0;
}
.cbr-content .cbr-no-items {
display: none;
color: var(--cbr-label-color);
font-size: 12px;
padding-top: 6px;
}
.cbr-content .cbr-no-items.show {
display: block;
}
#AcceptArea {
border-top: 1px solid var(--cbr-border-color);
}
@@ -0,0 +1,71 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Preset Bundle</title>
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" type="text/css" href="../../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="../css/common.css" />
<!-- <link rel="stylesheet" type="text/css" href="23.css" /> -->
<link rel="stylesheet" type="text/css" href="../css/dark.css" />
<script type="text/javascript" src="../js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../js/json2.js"></script>
<script type="text/javascript" src="../../data/text.js"></script>
<script type="text/javascript" src="../js/globalapi.js"></script>
<script type="text/javascript" src="../js/common.js"></script>
<!-- <script type="text/javascript" src="./23.js"></script> -->
<script src="./index.js"></script>
</head>
<body onLoad="OnInit()">
<div class="app">
<div class="split">
<div class="top-toolbar">
<button id="refresh_btn" class="ButtonStyleConfirm ButtonTypeChoice toolbar-btn">Refresh</button>
<!-- <label class="auto-update-switch" for="auto_update_toggle">
<span class="auto-update-label">Auto update</span>
<input id="auto_update_toggle" type="checkbox" />
<span class="auto-update-slider" aria-hidden="true">
<span class="auto-update-text auto-update-text-off">OFF</span>
<span class="auto-update-text auto-update-text-on">ON</span>
</span>
</label> -->
</div>
<section class="pane top">
<div class="hdr top-cols">
<span>Name</span>
<span>Type</span>
<span>Version</span>
<span>Update</span>
</div>
<div id="topList" class="body"></div>
</section>
<section class="pane bottom">
<div class="hdr bot-cols">
<span>Name</span>
<span>Type</span>
</div>
<div id="bottomList" class="body"></div>
</section>
</div>
</div>
<!-- Footer -->
<div id="AcceptArea">
<div id="export_btn" class="ButtonStyleConfirm ButtonTypeChoice" hidden>Export Presets</div>
<div id="close_btn" class="ButtonStyleRegular ButtonTypeChoice">Close</div>
</div>
<!-- Context Menu -->
<div id="ctxMenu" class="ctx" hidden>
<button class="ctx-item" data-action="open_folder">Open folder in explorer</button>
<button id = "delete_btn" class="ctx-item-delete" data-action="delete_bundle" hidden>Delete bundle</button>
<button id = "unsubscribe_btn" class="ctx-item-subscribed" data-action="unsubscribe_bundle" hidden>Unsubscribe bundle</button>
</div>
</body>
</html>
@@ -0,0 +1,375 @@
// ========= Data Stores =========
const bundlesById = new Map(); // bundleId -> bundle object
const printersByBundle = new Map(); // bundleId -> Map(index -> printerName)
const filamentsByBundle = new Map(); // bundleId -> Map(index -> filamentName)
const presetsByBundle = new Map(); // bundleId -> Map(index -> presetName)
const UPDATE_TOOLTIP = "Update available";
const UNAUTHORIZED_TOOLTIP = "Unauthorized bundle";
// ========= DOM =========
let topList = null;
let bottomList = null;
let ctxMenu = null;
let contextRow = null;
let ctxMenuSubscribed = null;
let ctxMenuDelete = null;
let selectedBundleId = null;
// ========= Init =========
function OnInit() {
topList = document.getElementById("topList");
bottomList = document.getElementById("bottomList");
ctxMenu = document.getElementById("ctxMenu");
ctxMenuSubscribed = document.getElementById("unsubscribe_btn");
ctxMenuDelete = document.getElementById("delete_btn");
const closeBtn = document.getElementById("close_btn");
const exportbtn = document.getElementById("export_btn");
const refreshBtn = document.getElementById("refresh_btn");
const autoUpdateToggle = document.getElementById("auto_update_toggle");
if (!topList || !bottomList) return;
TranslatePage();
// If wx side needs to request bundles after page load:
RequestBundles();
refreshBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "refresh_bundles"
};
SendWXMessage(JSON.stringify(tSend));
});
autoUpdateToggle?.addEventListener("change", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "set_auto_update",
enabled: !!autoUpdateToggle.checked
};
SendWXMessage(JSON.stringify(tSend));
});
// Hook selection on top list
topList.addEventListener("click", (e) => {
const cloudLink = e.target.closest(".bundle-cloud-link");
if (cloudLink) {
e.preventDefault();
e.stopPropagation();
const row = cloudLink.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
sendOpenBundleOnCloud(selectedBundleId);
return;
}
const updateBtn = e.target.closest(".bundle-update-btn");
if (updateBtn) {
e.stopPropagation();
if (updateBtn.disabled) return;
const row = updateBtn.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
sendUpdateBundleCommand(selectedBundleId);
return;
}
const row = e.target.closest(".row");
if (!row) return;
selectTopRow(row);
selectedBundleId = String(row.dataset.id || "");
renderBottomForBundle(selectedBundleId);
});
// for top list rows if right click open context menu
topList.addEventListener("contextmenu", (e) => {
const row = e.target.closest(".row");
if (!row) return; // top rows only
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
if (bundleType !== "subscribed") return;
e.preventDefault();
selectTopRow(row);
contextRow = row;
showSubscribedMenu(e.clientX, e.clientY);
});
// for top list rows except subscribed if right click open regular context menu
topList.addEventListener("contextmenu", (e) => {
const row = e.target.closest(".row");
if (!row) return; // top rows only
const bundleType = String(row.dataset.bundleType || "").toLowerCase();
if (bundleType === "subscribed") return;
e.preventDefault();
selectTopRow(row);
contextRow = row;
showMenu(e.clientX, e.clientY);
});
ctxMenu?.addEventListener("click", (e) => {
const btn = e.target.closest("[data-action]");
if (!btn || !contextRow) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "top_row_menu_action",
action: String(btn.dataset.action || ""),
bundle_id: String(contextRow.dataset.id || "")
};
SendWXMessage(JSON.stringify(tSend));
hideMenu();
});
closeBtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "close_page"
};
SendWXMessage(JSON.stringify(tSend));
});
exportbtn?.addEventListener("click", () => {
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "export_page"
};
SendWXMessage(JSON.stringify(tSend));
});
document.addEventListener("click", (e) => {
if (!e.target.closest(".ctx")) hideMenu();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") hideMenu();
});
}
// ========= wx bridge requests =========
function RequestBundles() {
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="request_bundles";
SendWXMessage(JSON.stringify(tSend));
}
function HandleStudio(pVal) {
const msg = (typeof pVal === "string") ? safeJsonParse(pVal) : pVal;
if (!msg || typeof msg !== "object") return;
const strCmd = msg.command;
if (strCmd === "list_bundles") {
unpackPayload(msg);
renderTop();
// auto-select first bundle if none selected
autoSelectFirstBundle();
const autoUpdateToggle = document.getElementById("auto_update_toggle");
if (autoUpdateToggle) {
autoUpdateToggle.checked = !!msg.auto_update_enabled;
}
}
}
// ========= Parse / store =========
function unpackPayload(payload) {
bundlesById.clear();
printersByBundle.clear();
filamentsByBundle.clear();
presetsByBundle.clear();
const list = payload?.data || [];
for (const bundle of list) {
const id = String(bundle.id ?? "");
if (!id) continue;
bundlesById.set(id, {
id,
name: bundle.name ?? "",
type: bundle.type ?? "",
version: bundle.version ?? "",
path: bundle.path ?? "",
update_available: Boolean(bundle.update_available) ,
unauthorized: Boolean(bundle.unauthorized)
});
printersByBundle.set(id, new Map((bundle.printers || []).map((name, i) => [i, name])));
filamentsByBundle.set(id, new Map((bundle.filaments || []).map((name, i) => [i, name])));
presetsByBundle.set(id, new Map((bundle.presets || []).map((name, i) => [i, name])));
}
}
// ========= Render: top =========
function renderTop() {
const bundles = Array.from(bundlesById.values());
topList.innerHTML = bundles.map(b => `
<div class="row" data-id="${escapeAttr(b.id)}" data-bundle-type="${escapeAttr(String(b.type || "").toLowerCase())}">
<div class="cell bundle-name-cell" title="${escapeAttr(b.name)}">
${b.unauthorized
? `<span class="bundle-status-icon bundle-status-icon-unauthorized" title="${escapeAttr(UNAUTHORIZED_TOOLTIP)}" aria-label="${escapeAttr(UNAUTHORIZED_TOOLTIP)}">!</span>`
: b.update_available
? `<span class="bundle-status-icon bundle-status-icon-update" title="${escapeAttr(UPDATE_TOOLTIP)}" aria-label="${escapeAttr(UPDATE_TOOLTIP)}">&uarr;</span>`
: `<span class="bundle-status-icon-spacer" aria-hidden="true"></span>`}
${
b.type === "Subscribed" ?
`<a href="#" class="bundle-name-text bundle-cloud-link" title="Open this bundle in your browser">${escapeHtml(b.name)}</a>`
: `<span class="bundle-name-text">${escapeHtml(b.name)}</span>`
}
</div>
<span title="${escapeAttr(b.type)}">${escapeHtml(b.type)}</span>
<span title="${escapeAttr(b.version)}">${escapeHtml(b.version)}</span>
<div class="cell bundle-update-cell">
<button
type="button"
class="bundle-update-btn ${(!b.unauthorized && b.update_available) ? "is-enabled" : "is-disabled"}"
${(!b.unauthorized && b.update_available) ? "" : "disabled"}
data-id="${escapeAttr(b.id)}"
>Update</button>
</div>
</div>
`).join("");
}
function sendOpenBundleOnCloud(bundleId) {
const bundle = bundlesById.get(String(bundleId || ""));
if (!bundle) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "open_bundle_on_cloud",
bundle_id: String(bundle.id || "")
};
SendWXMessage(JSON.stringify(tSend));
}
function sendUpdateBundleCommand(bundleId) {
const bundle = bundlesById.get(String(bundleId || ""));
if (!bundle || bundle.unauthorized || !bundle.update_available) return;
const tSend = {
sequence_id: Math.round(Date.now() / 1000),
command: "update_bundle",
bundle_id: String(bundle.id || "")
};
SendWXMessage(JSON.stringify(tSend));
}
// ========= Render: bottom (for a selected bundle) =========
function renderBottomForBundle(bundleId) {
const key = String(bundleId || "");
const printers = printersByBundle.get(key) || new Map();
const filaments = filamentsByBundle.get(key) || new Map();
const presets = presetsByBundle.get(key) || new Map();
// Convert to a flat list of rows { typeLabel, name }
const rows = [];
for (const [, name] of printers) rows.push({ type: "Printer", name });
for (const [, name] of filaments) rows.push({ type: "Filament", name });
for (const [, name] of presets) rows.push({ type: "Preset", name });
bottomList.innerHTML = rows.map((r, idx) => `
<div class="row" data-id="${escapeAttr(bundleId)}" data-idx="${idx}">
<span>${escapeHtml(r.name)}</span>
<span title="${escapeAttr(r.type)}">${escapeHtml(r.type)}</span>
</div>
`).join("");
}
// ========= Selection helpers =========
function clearSelection() {
document.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
}
function selectTopRow(rowEl) {
// only clear selection in top list, not bottom
topList.querySelectorAll(".row.selected").forEach(r => r.classList.remove("selected"));
rowEl.classList.add("selected");
}
function autoSelectFirstBundle() {
if (selectedBundleId && bundlesById.has(selectedBundleId)) {
// reselect existing
const el = topList.querySelector(`.row[data-id="${cssEscape(selectedBundleId)}"]`);
if (el) selectTopRow(el);
renderBottomForBundle(selectedBundleId);
return;
}
const first = topList.querySelector(".row");
if (!first) {
bottomList.innerHTML = "";
selectedBundleId = null;
return;
}
selectTopRow(first);
selectedBundleId = first.dataset.id;
renderBottomForBundle(selectedBundleId);
}
function showSubscribedMenu(x, y) {
if (!ctxMenu) return;
ctxMenu.style.left = `${x}px`;
ctxMenu.style.top = `${y}px`;
ctxMenu.hidden = false;
ctxMenuDelete.hidden = true;
ctxMenuSubscribed.hidden = false;
}
function showMenu(x, y) {
if (!ctxMenu) return;
ctxMenu.style.left = `${x}px`;
ctxMenu.style.top = `${y}px`;
ctxMenu.hidden = false;
ctxMenuDelete.hidden = false;
ctxMenuSubscribed.hidden = true;
}
function hideMenu() {
if (!ctxMenu) return;
ctxMenu.hidden = true;
ctxMenuSubscribed.hidden = true;
contextRow = null;
}
// ========= Utilities =========
function safeJsonParse(s) {
try { return JSON.parse(s); } catch { return null; }
}
function escapeHtml(str) {
return String(str)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function escapeAttr(str) {
// minimal attribute escaping
return escapeHtml(str);
}
function cssEscape(str) {
// basic css escape for attribute selectors
return String(str).replaceAll('"', '\\"');
}
@@ -0,0 +1,394 @@
:root {
--bg: #ffffff;
--panel: #ffffff;
--border: #d8d8d8;
--border-strong: #e6e6e6;
--border-soft: #f0f0f0;
--col-sep: #e1e1e1;
--text: #1f2328;
--row-hover: #f7f9fb;
--row-selected: #eaf2ff;
--row-selected-outline: #b7d0ff;
--footer-bg: #fafafa;
--btn-bg: #ffffff;
--btn-border: #cccccc;
--btn-hover: #f0f0f0;
--ctx-bg: #ffffff;
--ctx-border: #cccccc;
--ctx-hover: #efefef;
}
html, body {
height: 100%;
margin: 0;
font-family: sans-serif;
background: var(--bg);
color: var(--text);
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
}
/* App layout: split content + footer */
.app {
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
}
/* Split panes */
.split {
height: 100%;
display: grid;
grid-template-rows: auto minmax(0, 1.25fr) minmax(140px, 0.75fr);
gap: 8px;
padding: 8px;
box-sizing: border-box;
min-height: 0;
overflow: hidden;
}
.top-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 2px;
}
.toolbar-btn {
min-width: 92px;
}
.auto-update-switch {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 13px;
font-weight: 600;
color: var(--text);
user-select: none;
cursor: pointer;
position: relative;
}
.auto-update-switch input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.auto-update-label {
white-space: nowrap;
}
.auto-update-slider {
position: relative;
width: 52px;
height: 24px;
flex: 0 0 52px;
border-radius: 999px;
background: #b9b9b9;
transition: background 0.2s ease;
box-sizing: border-box;
overflow: hidden;
}
.auto-update-slider::before {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.28);
transition: transform 0.2s ease;
z-index: 2;
}
.auto-update-text {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 9px;
font-weight: 700;
line-height: 1;
letter-spacing: 0.3px;
z-index: 1;
pointer-events: none;
}
.auto-update-text-off {
right: 7px;
color: #ffffff;
}
.auto-update-text-on {
left: 7px;
color: #ffffff;
opacity: 0.55;
}
.auto-update-switch input:checked + .auto-update-slider {
background: var(--main-color);
}
.auto-update-switch input:checked + .auto-update-slider::before {
transform: translateX(28px);
}
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-on {
opacity: 1;
}
.auto-update-switch input:checked + .auto-update-slider .auto-update-text-off {
opacity: 0.55;
}
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-on {
opacity: 0.55;
}
.auto-update-switch input:not(:checked) + .auto-update-slider .auto-update-text-off {
opacity: 1;
}
.auto-update-switch input:focus-visible + .auto-update-slider {
outline: 2px solid var(--main-color);
outline-offset: 2px;
}
.auto-update-switch input:disabled + .auto-update-slider {
opacity: 0.5;
}
.pane {
min-height: 0;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--panel);
display: grid;
grid-template-rows: auto 1fr;
}
.hdr {
font-weight: 600;
font-size: 12px;
border-bottom: 1px solid var(--border-strong);
padding: 8px 10px;
display: grid;
gap: 10px;
}
.top-cols { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
.bot-cols { grid-template-columns: 2fr 1fr; }
.top .row { grid-template-columns: minmax(0, 2fr) 1fr 1fr 88px; }
.bottom .row { grid-template-columns: 2fr 1fr; }
.body {
min-height: 0;
overflow: hidden;
}
.hdr > *,
.row > * {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 8px;
border-right: 1px solid var(--col-sep);
box-sizing: border-box;
}
.hdr > *:last-child,
.row > *:last-child {
border-right: none;
padding-right: 0;
}
.bundle-name-cell {
display: flex;
align-items: center;
gap: 8px;
}
.bundle-name-text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.bundle-cloud-link {
color: inherit;
cursor: pointer;
text-decoration: none;
text-underline-offset: 2px;
}
.bundle-cloud-link:hover,
.bundle-cloud-link:focus-visible {
color: inherit;
text-decoration: underline;
}
.bundle-status-icon,
.bundle-status-icon-spacer {
flex: 0 0 14px;
width: 14px;
height: 14px;
}
.bundle-status-icon {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f59e0b;
color: #fff;
font-size: 10px;
font-weight: 700;
line-height: 14px;
}
.bundle-status-icon-unauthorized {
background: #dc2626;
}
.bundle-status-icon-update {
background: var(--main-color);
font-size: 12px;
}
.bundle-update-cell {
display: flex;
align-items: center;
justify-content: flex-start;
}
.bundle-update-btn {
box-sizing: border-box;
min-width: 64px;
height: 20px;
line-height: 18px;
padding: 0 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
border: 1px solid transparent;
}
.bundle-update-btn.is-enabled {
background: var(--main-color);
color: var(--button-fg-light);
}
.bundle-update-btn.is-enabled:hover {
background: var(--main-color-hover);
}
.bundle-update-btn.is-disabled {
background: var(--button-bg-disabled);
color: var(--button-fg-disabled);
cursor: not-allowed;
}
#topList,
#bottomList {
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
/* Column separators + clipping */
.row {
padding: 8px 10px;
border-bottom: 1px solid var(--border-soft);
display: grid;
gap: 10px;
font-size: 13px;
white-space: nowrap;
cursor: pointer;
}
.row:hover { background: var(--row-hover); }
.row.selected {
background: var(--row-selected);
outline: 1px solid var(--row-selected-outline);
}
/* Footer styling
.footer {
border-top: 1px solid var(--border);
padding: 10px 16px;
background: var(--footer-bg);
display: flex;
justify-content: flex-end;
gap: 12px;
}
.footer-btn {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid var(--btn-border);
background: var(--btn-bg);
color: var(--text);
cursor: pointer;
font-size: 13px;
}
.footer-btn:hover { background: var(--btn-hover); } */
/* Context menu */
.ctx {
position: fixed;
min-width: 180px;
background: var(--ctx-bg);
border: 1px solid var(--ctx-border);
border-radius: 6px;
box-shadow: 0 8px 20px rgba(0,0,0,.18);
padding: 4px;
z-index: 9999;
}
.ctx-item,
.ctx-item-subscribed, .ctx-item-delete {
display: block;
width: 100%;
text-align: left;
border: 0;
background: transparent;
color: var(--text);
padding: 8px 10px;
cursor: pointer;
border-radius: 4px;
}
.ctx-item:hover,
.ctx-item-subscribed:hover ,
.ctx-item-delete:hover
{ background: var(--ctx-hover); }
.ctx-item[hidden],
.ctx-item-subscribed[hidden] ,
.ctx-item-delete[hidden]{
display: none;
}
#AcceptArea {
flex: 0 0 auto;
}
+173
View File
@@ -0,0 +1,173 @@
*
{
padding: 0;
border: 0;
margin: 0;
font-family: "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-sans;
user-select: none;
}
html
{
height:100%;
background-color: #626262;
}
body
{
height:100%;
max-height: 660px;
max-width: 820px;
padding: 0;
border: 0;
margin: 0;
overflow: hidden;
background-color: #fff;
font-size: 14px;
line-height: 22px;
}
.TextPoint
{
font-size:1px;
}
.ZScrol::-webkit-scrollbar {/*滚动条整体样式*/
width: 12px; /*高宽分别对应横竖滚动条的尺寸*/
height: 12px;
padding: 2px;
}
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
border-radius: 6px;
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
background-color: #AAAAAA;
}
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
box-shadow: inset 0 0 5px rgba(0,0,0,0.2);
border-radius: 10px;
background: #EDEDED;
}
/*----Three Part----*/
body
{
display:flex;
flex-direction: column;
}
#Title
{
height: 12%;
display: flex;
text-align: center;
flex-direction:column;
justify-content: center;
}
#Title div
{
font-size:28px;
line-height: 28px;
color: #009688;
padding: 0px 10mm;
}
#Content
{
height: 76%;
padding: 20px 40px;
overflow-x: hidden;
overflow-y: auto;
text-align: left;
font-size: 14px;
line-height: 22px;
color: #464646;
position: relative;
display:flex;
flex-direction: column;
}
#Content div
{
}
#AcceptArea
{
height:var(--dialog-button-sizer-height); /*----ORCA Use fixed size to prevent position change----*/
max-height:var(--dialog-button-sizer-height);
min-height:var(--dialog-button-sizer-height);
padding: 0 var(--dialog-button-gap);
text-align: left;
display: flex;
justify-content:flex-end;
align-items: center;
}
/*---HyperLink---*/
.HyperLink
{
color: #009688;
text-decoration: underline;
font-weight: 700;
cursor: pointer;
}
/*---Checkboxes ORCA ---*/
label:has(input[type="checkbox"]){
margin:0;
padding: 0;
margin-right: 20px;
}
label:has(input[type="checkbox"])>span{
vertical-align: middle;
margin:0;
}
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
background-color:#FFFFFF;
margin:0;
margin-right: 6px;
width: 1em;
height: 1em;
border: 0.1em solid #DBDBDB;
border-radius: 0.15em;
display: inline-flex;
place-content: center;
background-color:#FFFFFF;
}
input[type="checkbox"]::before {
content: "";
width: 0.8em;
height: 0.8em;
transform: scale(0);
box-shadow: inset 1em 1em #FFFFFF;
clip-path: polygon(7% 37%, 0 45%, 33% 78%, 100% 30%, 95% 21%, 33% 64%);
}
input[type="checkbox"]:checked {
border-color:#009688;
background-color:#009688;
}
input[type="checkbox"]:checked::before {
transform: scale(1);
}
/*----------------Light Mode-------------------*/
+100
View File
@@ -0,0 +1,100 @@
:root {
--bg: #1b1f24;
--panel: #242a31;
--border: #3a424d;
--border-strong: #3a424d;
--border-soft: #313843;
--col-sep: #3a424d;
--text: #e6ebf0;
--row-hover: #2b3340;
--row-selected: #244945;
--row-selected-outline: #00bfa5;
--footer-bg: #20262d;
--btn-bg: #2a313a;
--btn-border: #4b5664;
--btn-hover: #333c47;
--ctx-bg: #2a313a;
--ctx-border: #4b5664;
--ctx-hover: #3a4451;
}
*
{
color: #efeff0;
border-color: #B9B9BC;
}
body
{
background-color:#2D2D31; /* ORCA match background color */
color: #efeff0;
}
.ZScrol::-webkit-scrollbar-thumb {/*滚动条里面小方块*/
background-color: #939594;
}
.ZScrol::-webkit-scrollbar-track {/*滚动条里面轨道*/
background: #161817;
}
#Title div
{
color: #009688;
}
.search>input[type=text]{
background-color:#2D2D31;
}
/*---Checkboxes ORCA---*/
input[type=checkbox]{
background-color:#2D2D31;
border-color:#4A4A51;
}
input[type=checkbox]:checked{
background-color:#009688;
}
/*-------Text------*/
.TextS1
{
}
.TextS2
{
color:#B9B9BC;
}
/*---Policy---*/
.TextArea1
{
background-color: #4A4A51;
color: #BEBEC0;
}
/*----Region---*/
.RegionItem:hover
{
background-color:#4C4C55;
}
.RegionSelected:hover
{
background-color:#009688;
color: #fff;
}
/*----Menu----*/
#Title div.TitleUnselected
{
color: #BEBEC0;
}
+35
View File
@@ -0,0 +1,35 @@
#FullArea
{
height:100%;
}
#LoadProgress
{
position:fixed;
top: 0px;
left: 0px;
width: 100%;
height: 3mm;
display: none;
}
#PercentTip
{
width:70%;
height: 100%;
background-color: #335DFC;
}
#PageArea
{
width:100%;
height: 100%;
}
#IEPage
{
height:100%;
width: 100%;
}
+58
View File
@@ -0,0 +1,58 @@
*
{
padding: 0;
border: 0;
margin: 0;
}
html,body
{
height:100%;
padding: 0;
border: 0;
margin: 0;
overflow: hidden;
background-color: #fff;
}
#PageArea
{
height:100%;
width: 100%;
}
.swiper {
height: 100%;
top: 0px;
left: 0px;
bottom: 35px;
}
.swiper-slide {
font-size: 18px;
height: 100%;
width: 100%;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.swiper-slide iframe
{
width:100%;
height: 100%;
overflow-y: hidden;
}
.CBtn
{
padding: 3mm 100m;
background-color: #77EFF9;
display: inline-block;
height: 30px;
width: 150px;
text-align: center;
z-index: 99;
position: absolute;
top:30px;
}
+21
View File
@@ -0,0 +1,21 @@
function ClosePage() {
var tSend = {};
tSend['sequence_id'] = Math.round(new Date() / 1000);
tSend['command'] = "close_page";
SendWXMessage(JSON.stringify(tSend));
}
document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (window.event) {
try { e.keyCode = 0; } catch (e) { }
e.returnValue = false;
}
};
window.addEventListener('wheel', function (event) {
if (event.ctrlKey === true || event.metaKey) {
event.preventDefault();
}
}, { passive: false });
@@ -277,4 +277,63 @@ function SendWXMessage( strMsg )
}
}
/*------CSS Link Control----*/
function RemoveCssLink( LinkPath )
{
let pNow=$("head link[href='"+LinkPath+"']");
let nTotal=pNow.length;
for( let n=0;n<nTotal;n++ )
{
pNow[n].remove();
}
}
function AddCssLink( LinkPath )
{
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.href = LinkPath;
link.rel = 'stylesheet';
link.type = 'text/css';
head.appendChild(link);
}
function CheckCssLinkExist( LinkPath )
{
let pNow=$("head link[href='"+LinkPath+"']");
let nTotal=pNow.length;
return nTotal;
}
/*------Dark Mode------*/
function SwitchDarkMode( DarkCssPath )
{
ExecuteDarkMode( DarkCssPath );
setInterval("ExecuteDarkMode('"+DarkCssPath+"')",1000);
}
function ExecuteDarkMode( DarkCssPath )
{
let nMode=0;
let bDarkMode=navigator.userAgent.match( RegExp('dark','i') );
if( bDarkMode!=null )
nMode=1;
let nNow=CheckCssLinkExist(DarkCssPath);
if( nMode==0 )
{
if(nNow>0)
RemoveCssLink(DarkCssPath);
}
else
{
if(nNow==0)
AddCssLink(DarkCssPath);
}
}
SwitchDarkMode( "../css/dark.css" );
+12
View File
@@ -0,0 +1,12 @@
function NextSlide()
{
$('.swiper-button-next').click();
}
function PreSlide()
{
$('.swiper-button-prev').click();
}
File diff suppressed because one or more lines are too long
+6 -1
View File
@@ -127,4 +127,9 @@ body
{
background: rgba(54, 54, 60, 0.88);
border: 1px solid rgba(129, 129, 131, 0.64);
}
}
/*--- Bambu Cloud Section ---*/
#BambuCloudHeader { color: #818183; }
#BambuCloudHeader:hover { color: #efeff0; }
.bambu-chevron svg path { stroke: currentColor; }
+168 -25
View File
@@ -79,40 +79,151 @@ body
#LoginArea
{
height: 180px;
min-height: 180px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
position: relative;
width:262px;
}
#OrcaLoginSection
{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
}
#Login1
#OrcaLogin1
{
height:36px;
line-height: 36px;
display: flex;
flex-direction: column; /*ORCA*/
align-items: center; /*Allow icon centered vertically*/
justify-content: center; /*and use login button in new line*/
user-select: none;
padding: 10px 0;
}
#OrcaLogin2
{
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 14px 0 16px;
box-sizing: border-box;
width: 262px;
}
/* --- Bambu Cloud Section --- */
#BambuCloudSection
{
display: none; /* shown by cloud_providers_info from backend */
border-top: 1px solid;
width: 262px;
}
#BambuCloudHeader
{
height: 40px;
display: flex;
align-items: center;
padding: 0 20px;
cursor: pointer;
font-size: 13px;
color: #A8A8A8;
gap: 8px;
}
#BambuCloudHeader:hover
{
color: inherit;
}
.bambu-chevron
{
width: 12px;
height: 12px;
transition: transform 0.2s ease;
transform: rotate(0deg);
}
.bambu-chevron.expanded
{
transform: rotate(90deg);
}
.bambu-status-dot
{
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #A8A8A8;
margin-left: auto;
}
.bambu-status-dot.online
{
background-color: #4CAF50;
}
#BambuCloudBody
{
max-height: 0;
overflow: hidden;
transition: max-height 0.25s ease;
display: flex;
flex-direction: column;
align-items: center;
}
#BambuCloudBody.expanded
{
max-height: 200px;
}
#NoPluginTip
{
position: absolute;
top: 0px;
left: 0px;
bottom: 0px;
right: 0px;
z-index: 1;
position: static;
display: none;
flex-direction: column;
justify-content: flex-end;
padding: 5px;
padding: 5px 10px;
z-index: auto;
}
#BambuLogin1
{
padding: 10px 0;
}
#BambuLogin2
{
display: none;
flex-direction: column;
align-items: center;
text-align: center;
padding: 8px 0;
width: 262px;
}
#BambuAvatarIcon
{
height: 40px;
border-radius: 50%;
}
#BambuUserName
{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 80%;
text-align: center;
font-size: 13px;
}
@@ -132,18 +243,14 @@ body
height:96px; /*ORCA use bigger icon to fit logo size*/
}
#Login2
{
display: none;
flex-direction: column;
align-items: center;
text-align: center;
width: 262px;
}
#UserAvatarIcon
{
height: 85px;
width: 64px;
height: 64px;
display: block;
margin: 2px auto 10px;
object-fit: cover;
border-radius: 50%;
}
#UserName
@@ -153,11 +260,13 @@ body
text-align: center;
overflow: hidden;
width: 80%;
max-width: 190px;
line-height: 22px;
}
#LogoutBtn
{
margin-top: 5px;
margin-top: 8px;
}
/*------------------*/
@@ -201,6 +310,38 @@ body
height: 20px;
}
.BtnShortcut
{
padding-right: 18px;
}
.ShortcutBrandIcon
{
width: 20px;
height: 20px;
border-radius: 5px;
display: block;
}
.ShortcutTextRow
{
display: flex;
align-items: center;
gap: 8px;
width: 100%;
min-width: 0;
}
.ShortcutMark
{
width: 14px;
height: 14px;
margin-left: auto;
color: currentColor;
opacity: 0.72;
flex: 0 0 auto;
}
/*--------------------*/
#RightBoard
@@ -321,6 +462,7 @@ body
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
#RecentTitleBlock
@@ -348,6 +490,7 @@ body
flex-wrap: wrap;
align-content: flex-start;
overflow-y: auto;
min-height: 0;
}
.FileItem
@@ -735,4 +878,4 @@ body
opacity: 1!important;
cursor: pointer!important;
pointer-events: auto!important;
}
}
+57 -17
View File
@@ -20,32 +20,72 @@
<script type="text/javascript" src="js/home.js"></script>
</head>
<body class="ZScrol" onLoad="OnInit()">
<div id="LeftBoard" style="display: none;">
<div id="LeftBoard">
<div id="LoginArea">
<div id="Login1">
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
</div>
<div id="Login2">
<div>
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
<!-- Orca Login Section -->
<div id="OrcaLoginSection">
<div id="OrcaLogin1">
<div id="Icon1"><img id="BBLIcon" src="../image/logo.png" /></div> <!-- ORCA use square icon for better consistency on UI -->
<div id="LoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnLoginOrRegister()"><span class="trans" tid="t26">login</span> / <span class="trans" tid="t27">register</span></div>
</div>
<div id="OrcaLogin2">
<div>
<img id="UserAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
</div>
<div id="UserName" class="TextS1"></div>
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
</div>
<div id="UserName" class="TextS1"></div>
<div id="LogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnLogOut()">log out</div>
</div>
<div id="NoPluginTip">
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
</div>
<!-- Bambu Cloud Section -->
<div id="BambuCloudSection">
<div id="BambuCloudHeader" onClick="ToggleBambuSection()">
<svg class="bambu-chevron" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="trans" tid="orca6">Bambu Cloud</span>
<span class="bambu-status-dot"></span>
</div>
<div id="BambuCloudBody">
<div id="BambuLogin1">
<div id="BambuLoginBtn" class="ButtonStyleRegular ButtonTypeWindow" onClick="OnBambuLoginOrRegister()"><span class="trans" tid="t26">login</span></div>
</div>
<div id="BambuLogin2">
<div>
<img id="BambuAvatarIcon" src="img/c.jpg" onerror="this.onerror=null;this.src='img/c.jpg';" />
</div>
<div id="BambuUserName" class="TextS1"></div>
<div id="BambuLogoutBtn" class="ButtonStyleAlert ButtonTypeWindow trans" tid="t50" onClick="OnBambuLogOut()">log out</div>
</div>
<div id="NoPluginTip">
<div id="NoPluginText"><a class="RedFont trans" tid="t76">Network plugin not detected. Click </a><a Class="LinkBtn trans" onClick="BeginDownloadNetworkPlugin()" tid="t77">here</a><a class="RedFont trans" tid="t78"> to install it.</a></div>
</div>
</div>
</div>
</div>
<div id="BtnArea">
<div menu="recent" class="BtnItem BtnItemSelected" onClick="GotoMenu('recent')">
<div class="BtnIcon "><img class="LeftIcon" src="img/i2.png" /></div>
<div class="trans" tid="t28">recent</div>
</div>
</div>
<div class="BtnItem BtnShortcut" onClick="OpenUrlInLocalBrowser('https://cloud.orcaslicer.com')">
<div class="BtnIcon "><img class="ShortcutBrandIcon" src="../image/logo.png" /></div>
<div class="ShortcutTextRow">
<div>OrcaCloud</div>
<svg class="ShortcutMark" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M6 3.5H3.5C2.95 3.5 2.5 3.95 2.5 4.5V12.5C2.5 13.05 2.95 13.5 3.5 13.5H11.5C12.05 13.5 12.5 13.05 12.5 12.5V10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.5 3.5H13.5V8.5" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 3.5L7 10" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
</div>
</div>
+95 -39
View File
@@ -1,6 +1,7 @@
//var TestData={"sequence_id":"0","command":"get_recent_projects","response":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};
var m_HotModelList=null;
var bambuSectionExpanded = false;
function OnInit()
{
@@ -8,6 +9,7 @@ function OnInit()
TranslatePage();
SendMsg_GetLoginInfo();
SendMsg_GetBambuLoginInfo();
SendMsg_GetRecentFile();
SendMsg_GetStaffPick();
}
@@ -77,11 +79,7 @@ function Set_RecentFile_MouseRightBtn_Event()
function SetLoginPanelVisibility(visible) {
var leftBoard = document.getElementById("LeftBoard");
if (visible) {
leftBoard.style.display = "block";
} else {
leftBoard.style.display = "none";
}
leftBoard.style.display = "block";
}
function HandleStudio( pVal )
@@ -90,24 +88,41 @@ function HandleStudio( pVal )
if (strCmd == "get_recent_projects") {
ShowRecentFileList(pVal["response"]);
} else if (strCmd == "studio_userlogin") {
SetLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
} else if (strCmd == "studio_useroffline") {
SetUserOffline();
} else if (strCmd == "orca_userlogin") {
SetOrcaLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
} else if (strCmd == "orca_useroffline") {
SetOrcaUserOffline();
} else if (strCmd == "studio_bambu_userlogin") {
SetBambuLoginInfo(pVal["data"]["avatar"], pVal["data"]["name"]);
} else if (strCmd == "studio_bambu_useroffline") {
SetBambuUserOffline();
} else if (strCmd == "studio_set_mallurl") {
SetMallUrl(pVal["data"]["url"]);
} else if (strCmd == "studio_clickmenu") {
let strName = pVal["data"]["menu"];
GotoMenu(strName);
} else if (strCmd == "cloud_providers_info") {
if (pVal["data"]["providers"] && pVal["data"]["providers"].indexOf("bbl") >= 0) {
$("#BambuCloudSection").show();
} else {
$("#BambuCloudSection").hide();
}
} else if (strCmd == "network_plugin_installtip") {
let nShow = pVal["show"] * 1;
if (nShow == 1) {
// Auto-expand Bambu section to show the tip
if (!bambuSectionExpanded) ToggleBambuSection();
$("#BambuLogin1").hide();
$("#NoPluginTip").show();
$("#NoPluginTip").css("display", "flex");
} else {
$("#NoPluginTip").hide();
// Only restore login button if not already logged in
if ($("#BambuLogin2").is(":hidden")) {
$("#BambuLogin1").show();
}
}
} else if (strCmd == "modelmall_model_advise_get") {
//alert('hot');
@@ -120,8 +135,8 @@ function HandleStudio( pVal )
m_HotModelList = pVal["hits"];
ShowStaffPick(m_HotModelList);
} else if (data.cmd === "SetLoginPanelVisibility") {
SetLoginPanelVisibility(data.visible);
} else if (strCmd == "SetLoginPanelVisibility") {
SetLoginPanelVisibility(pVal["data"]["visible"]);
}
}
@@ -146,32 +161,32 @@ function GotoMenu( strMenu )
}
}
function SetLoginInfo( strAvatar, strName )
function SetOrcaLoginInfo( strAvatar, strName )
{
$("#Login1").hide();
$("#OrcaLogin1").hide();
$("#UserName").text(strName);
let OriginAvatar=$("#UserAvatarIcon").prop("src");
if(strAvatar!=OriginAvatar)
if(strAvatar != null && strAvatar.trim() !== '' && strAvatar!=OriginAvatar)
$("#UserAvatarIcon").prop("src",strAvatar);
else
{
//alert('Avatar is Same');
}
$("#Login2").show();
$("#Login2").css("display","flex");
$("#OrcaLogin2").show();
$("#OrcaLogin2").css("display","flex");
}
function SetUserOffline()
function SetOrcaUserOffline()
{
$("#UserAvatarIcon").prop("src","img/c.jpg");
$("#UserName").text('');
$("#Login2").hide();
$("#Login1").show();
$("#Login1").css("display","flex");
$("#OrcaLogin2").hide();
$("#OrcaLogin1").show();
$("#OrcaLogin1").css("display","flex");
}
function SetMallUrl( strUrl )
@@ -246,6 +261,17 @@ function SendMsg_GetLoginInfo()
SendWXMessage( JSON.stringify(tSend) );
}
function SendSimpleCommand(command) {
var tSend = {};
tSend['sequence_id'] = Math.round(new Date() / 1000);
tSend['command'] = command;
SendWXMessage(JSON.stringify(tSend));
}
function OnOrcaLoginOrRegister() { SendSimpleCommand("homepage_orca_login_or_register"); }
function OnOrcaLogOut() { SendSimpleCommand("homepage_orca_logout"); }
function SendMsg_GetOrcaLoginInfo() { SendSimpleCommand("get_orca_login_info"); }
function SendMsg_GetRecentFile()
{
@@ -373,10 +399,52 @@ function OnLogOut()
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="homepage_logout";
SendWXMessage( JSON.stringify(tSend) );
SendWXMessage( JSON.stringify(tSend) );
}
// --- Bambu Cloud Section ---
function ToggleBambuSection() {
var body = document.getElementById('BambuCloudBody');
var chevron = document.querySelector('.bambu-chevron');
if (!body || !chevron) return;
bambuSectionExpanded = !bambuSectionExpanded;
if (bambuSectionExpanded) {
body.classList.add('expanded');
chevron.classList.add('expanded');
} else {
body.classList.remove('expanded');
chevron.classList.remove('expanded');
}
}
function SetBambuLoginInfo(strAvatar, strName) {
$("#BambuLogin1").hide();
$("#BambuUserName").text(strName);
if (strAvatar && strAvatar.trim() !== '') {
$("#BambuAvatarIcon").prop("src", strAvatar);
}
$("#BambuLogin2").show();
$("#BambuLogin2").css("display", "flex");
$(".bambu-status-dot").addClass("online");
}
function SetBambuUserOffline() {
$("#BambuAvatarIcon").prop("src", "img/c.jpg");
$("#BambuUserName").text('');
$("#BambuLogin2").hide();
if ($("#NoPluginTip").is(":hidden")) {
$("#BambuLogin1").show();
$("#BambuLogin1").css("display", "flex");
}
$(".bambu-status-dot").removeClass("online");
}
function OnBambuLoginOrRegister() { SendSimpleCommand("homepage_bambu_login_or_register"); }
function OnBambuLogOut() { SendSimpleCommand("homepage_bambu_logout"); }
function SendMsg_GetBambuLoginInfo() { SendSimpleCommand("get_bambu_login_info"); }
function BeginDownloadNetworkPlugin()
{
var tSend={};
@@ -431,19 +499,7 @@ function InitStaffPick()
},
slidesPerView : 'auto',
slidesPerGroup : 3
// autoplay: {
// delay: 3000,
// stopOnLastSlide: false,
// disableOnInteraction: true,
// disableOnInteraction: false
// },
// pagination: {
// el: '.swiper-pagination',
// },
// scrollbar: {
// el: '.swiper-scrollbar',
// draggable: true
// }
});
}
-50
View File
@@ -1,50 +0,0 @@
*
{
padding:0px;
border: 0px;
margin: 0px;
font-size: 14px;
}
html, body {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px;
line-height: 20px;
}
/*------------------*/
body
{
display:flex;
justify-content: center;
align-items: center;
max-height: 900px;
max-width: 1300px;
background-color: #fff;
}
#ErrorBlock
{
display:flex;
flex-direction: column;
align-items: center;
}
#ErrorIcon
{
width:100px;
}
#ErrorTip
{
font-size: 18px;
line-height: 50px;
}
#ErrorBtn
{
width:100px;
display: none;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

-35
View File
@@ -1,35 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Cache-Control" content="max-age=7200" />
<title>Network Error</title>
<link rel="stylesheet" type="text/css" href="../include/global.css" /> <!-- ORCA One for all-->
<link rel="stylesheet" type="text/css" href="css/login.css" />
<script type="text/javascript" src="js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="../data/text.js"></script>
<script type="text/javascript" src="js/json2.js"></script>
<script type="text/javascript" src="js/globalapi.js"></script>
<script type="text/javascript" src="js/login.js"></script>
</head>
<body onLoad="TranslatePage()">
<div id="ErrorBlock">
<img id="ErrorIcon" src="disconnect3.png" />
<div id="ErrorTip" class="trans" title="t40" >Network disconnect, please check and try again later.</div>
<div id="ErrorBtn" class="ButtonStyleRegular ButtonTypeChoice">Retry</div>
</div>
</body>
</html>
-157
View File
@@ -1,157 +0,0 @@
/*var TestData={"sequence_id":"0","command":"studio_send_recentfile","data":[{"path":"D:\\work\\Models\\Toy\\3d-puzzle-cube-model_files\\3d-puzzle-cube.3mf","time":"2022\/3\/24 20:33:10"},{"path":"D:\\work\\Models\\Art\\Carved Stone Vase - remeshed+drainage\\Carved Stone Vase.3mf","time":"2022\/3\/24 17:11:51"},{"path":"D:\\work\\Models\\Art\\Kity & Cat\\Cat.3mf","time":"2022\/3\/24 17:07:55"},{"path":"D:\\work\\Models\\Toy\\鐩村墤.3mf","time":"2022\/3\/24 17:06:02"},{"path":"D:\\work\\Models\\Toy\\minimalistic-dual-tone-whistle-model_files\\minimalistic-dual-tone-whistle.3mf","time":"2022\/3\/22 21:12:22"},{"path":"D:\\work\\Models\\Toy\\spiral-city-model_files\\spiral-city.3mf","time":"2022\/3\/22 18:58:37"},{"path":"D:\\work\\Models\\Toy\\impossible-dovetail-puzzle-box-model_files\\impossible-dovetail-puzzle-box.3mf","time":"2022\/3\/22 20:08:40"}]};*/
function OnInit()
{
TranslatePage();
$("#HotspotWEB").prop("src","https://www.bambulab.com");
}
function HandleStudio( pVal )
{
let strCmd = pVal['command'];
//alert(strCmd);
if(strCmd=='studio_send_recentfile')
{
ShowRecentFileList(pVal['data']);
}
else if(strCmd=='studio_userlogin')
{
SetLoginInfo(pVal['data']['avatar'],pVal['data']['name']);
}
else if(strCmd=='studio_useroffline')
{
SetUserOffline();
}
else if( strCmd=="studio_set_mallurl" )
{
SetMallUrl( pVal['data']['url'] );
}
else if( strCmd=="studio_clickmenu" )
{
let strName=pVal['data']['menu'];
GotoMenu(strName);
}
}
function GotoMenu( strMenu )
{
let MenuList=$(".BtnItem");
let nAll=MenuList.length;
for(let n=0;n<nAll;n++)
{
let OneBtn=MenuList[n];
if( $(OneBtn).attr("menu")==strMenu )
{
$(".BtnItem").removeClass("BtnItemSelected");
$(OneBtn).addClass("BtnItemSelected");
$("div[board]").hide();
$("div[board=\'"+strMenu+"\']").show();
}
}
}
function SetLoginInfo( strAvatar, strName )
{
$("#Login1").hide();
$("#UserAvatarIcon").prop("src","../../image/cache/"+strAvatar);
$("#UserName").text(strName);
$("#Login2").show();
}
function SetUserOffline()
{
$("#UserAvatarIcon").prop("src","img/c.jpg");
$("#UserName").text('');
$("#Login2").hide();
$("#Login1").show();
}
function SetMallUrl( strUrl )
{
$("#MallWeb").prop("src",strUrl);
}
function ShowRecentFileList( pList )
{
let nTotal=pList.length;
let strHtml='';
for(let n=0;n<nTotal;n++)
{
let OneFile=pList[n];
let sImg=OneFile["image"];
let sPath=OneFile['path'];
let sTime=OneFile['time'];
let index=sPath.lastIndexOf('\\')>0?sPath.lastIndexOf('\\'):sPath.lastIndexOf('\/');
let sShortName=sPath.substring(index+1,sPath.length);
let TmpHtml='<div class="FileItem" onClick="OnOpenRecentFile(\''+ encodeURI(sPath)+'\')" >'+
'<a class="FileTip" title="'+sPath+'"></a>'+
'<div class="FileImg" ><img src="..\..\img\temp\\'+sImg+'" onerror="this.onerror=null;this.src=\'img/d.png\';" alt="No Image" /></div>'+
'<a>'+sShortName+'</a>'+
'<div class="FileDate">'+sTime+'</div>'+
'</div>';
strHtml+=TmpHtml;
}
$("#FileList").html(strHtml);
}
/*-------MX Message------*/
function OnLoginOrRegister()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="homepage_login_or_register";
SendWXMessage( JSON.stringify(tSend) );
}
function OnClickNewProject()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="homepage_newproject";
SendWXMessage( JSON.stringify(tSend) );
}
function OnClickOpenProject()
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="homepage_openproject";
SendWXMessage( JSON.stringify(tSend) );
}
function OnOpenRecentFile( strPath )
{
var tSend={};
tSend['sequence_id']=Math.round(new Date() / 1000);
tSend['command']="homepage_open_recentfile";
tSend['data']={};
tSend['data']['path']=decodeURI(strPath);
SendWXMessage( JSON.stringify(tSend) );
}
-927
View File
@@ -1,927 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OrcaCloud Login</title>
<style>
:root {
--bg-primary: #f5f5f7;
--bg-card: #ffffff;
--text-primary: #1d1d1f;
--text-secondary: #86868b;
--text-tertiary: #6e6e73;
--border-color: #d2d2d7;
--accent-color: #009688;
--accent-hover: #00796b;
--error-color: #d32f2f;
--success-color: #388e3c;
--google-color: #4285f4;
--apple-color: #000000;
--github-color: #24292e;
--input-bg: #f5f5f7;
--shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1d1d1f;
--bg-card: #2d2d2f;
--text-primary: #f5f5f7;
--text-secondary: #a1a1a6;
--text-tertiary: #86868b;
--border-color: #424245;
--input-bg: #3a3a3c;
--shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
}
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-card {
background: var(--bg-card);
border-radius: 16px;
box-shadow: var(--shadow);
width: 100%;
max-width: 400px;
padding: 40px 32px;
position: relative;
}
.header {
text-align: center;
margin-bottom: 32px;
}
.header .logo {
width: 64px;
height: 64px;
margin-bottom: 16px;
}
.header h1 {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 8px;
}
.header p {
font-size: 14px;
color: var(--text-secondary);
}
.tab-container {
display: flex;
background: var(--input-bg);
border-radius: 10px;
padding: 4px;
margin-bottom: 24px;
}
.tab {
flex: 1;
padding: 10px 16px;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
}
.tab.active {
background: var(--bg-card);
color: var(--text-primary);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.tab:hover:not(.active) {
color: var(--text-primary);
}
.form-container {
margin-bottom: 16px;
}
.input-group {
margin-bottom: 16px;
}
.input-group label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 6px;
}
.input-group input {
width: 100%;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 10px;
font-size: 15px;
background: var(--input-bg);
color: var(--text-primary);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.input-group input:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(0, 150, 136, 0.1);
}
.input-group input::placeholder {
color: var(--text-tertiary);
}
.input-group.error input {
border-color: var(--error-color);
}
.input-group .error-text {
font-size: 12px;
color: var(--error-color);
margin-top: 4px;
display: none;
}
.input-group.error .error-text {
display: block;
}
.confirm-password-group {
display: none;
margin-bottom: 16px;
}
.confirm-password-group.visible {
display: block;
}
.primary-btn {
width: 100%;
padding: 14px 24px;
background: var(--accent-color);
color: white;
border: none;
border-radius: 10px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease, transform 0.1s ease;
}
.primary-btn:hover {
background: var(--accent-hover);
}
.primary-btn:active {
transform: scale(0.98);
}
.primary-btn:disabled {
background: var(--text-tertiary);
cursor: not-allowed;
}
.forgot-link {
display: block;
text-align: center;
color: var(--accent-color);
font-size: 13px;
text-decoration: none;
margin-top: 12px;
cursor: pointer;
}
.forgot-link:hover {
text-decoration: underline;
}
.divider {
display: flex;
align-items: center;
margin: 24px 0;
}
.divider::before,
.divider::after {
content: "";
flex: 1;
height: 1px;
background: var(--border-color);
}
.divider span {
padding: 0 16px;
font-size: 13px;
color: var(--text-tertiary);
}
.providers {
display: flex;
flex-direction: column;
gap: 12px;
}
.provider-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: 100%;
padding: 12px 24px;
border: 1px solid var(--border-color);
border-radius: 10px;
background: var(--bg-card);
color: var(--text-primary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.provider-btn:hover {
border-color: var(--text-tertiary);
background: var(--input-bg);
}
.provider-btn svg {
width: 20px;
height: 20px;
}
.provider-btn.google:hover {
border-color: var(--google-color);
}
.provider-btn.apple:hover {
border-color: var(--apple-color);
}
.provider-btn.github:hover {
border-color: var(--github-color);
}
.message {
margin-top: 16px;
padding: 12px 16px;
border-radius: 8px;
font-size: 13px;
text-align: center;
display: none;
}
.message.error {
display: block;
background: rgba(211, 47, 47, 0.1);
color: var(--error-color);
border: 1px solid rgba(211, 47, 47, 0.2);
}
.message.success {
display: block;
background: rgba(56, 142, 60, 0.1);
color: var(--success-color);
border: 1px solid rgba(56, 142, 60, 0.2);
}
.loading-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
border-radius: 16px;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 16px;
z-index: 10;
}
@media (prefers-color-scheme: dark) {
.loading-overlay {
background: rgba(45, 45, 47, 0.95);
}
}
.loading-overlay.visible {
display: flex;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--border-color);
border-top-color: var(--accent-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-text {
font-size: 14px;
color: var(--text-secondary);
}
.back-link {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-secondary);
font-size: 13px;
text-decoration: none;
cursor: pointer;
margin-bottom: 16px;
}
.back-link:hover {
color: var(--text-primary);
}
.back-link svg {
width: 16px;
height: 16px;
}
.reset-view {
display: none;
}
.reset-view.visible {
display: block;
}
.auth-view {
display: block;
}
.auth-view.hidden {
display: none;
}
.reset-description {
font-size: 14px;
color: var(--text-secondary);
text-align: center;
margin-bottom: 24px;
line-height: 1.5;
}
#debug {
margin-top: 20px;
font-size: 10px;
color: var(--text-tertiary);
text-align: left;
width: 100%;
white-space: pre-wrap;
display: none;
max-height: 150px;
overflow-y: auto;
background: var(--input-bg);
padding: 8px;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="login-card">
<!-- Auth View (Sign In / Sign Up) -->
<div id="auth-view" class="auth-view">
<div class="header">
<svg class="logo" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="64" height="64" rx="14" fill="#009688"/>
<path d="M32 16C23.163 16 16 23.163 16 32C16 40.837 23.163 48 32 48C40.837 48 48 40.837 48 32C48 23.163 40.837 16 32 16ZM32 44C25.373 44 20 38.627 20 32C20 25.373 25.373 20 32 20C38.627 20 44 25.373 44 32C44 38.627 38.627 44 32 44Z" fill="white"/>
<circle cx="32" cy="32" r="6" fill="white"/>
</svg>
<h1>OrcaCloud</h1>
<p id="header-subtitle">Sign in to sync your settings</p>
</div>
<div class="tab-container">
<button class="tab active" data-mode="signin" onclick="setMode('signin')">Sign In</button>
<button class="tab" data-mode="signup" onclick="setMode('signup')">Sign Up</button>
</div>
<form id="auth-form" class="form-container" onsubmit="handleSubmit(event)">
<div class="input-group" id="email-group">
<label for="email">Email</label>
<input type="email" id="email" placeholder="you@example.com" autocomplete="email" required>
<span class="error-text" id="email-error"></span>
</div>
<div class="input-group" id="password-group">
<label for="password">Password</label>
<input type="password" id="password" placeholder="Enter your password" autocomplete="current-password" required>
<span class="error-text" id="password-error"></span>
</div>
<div class="input-group confirm-password-group" id="confirm-group">
<label for="confirm-password">Confirm Password</label>
<input type="password" id="confirm-password" placeholder="Confirm your password" autocomplete="new-password">
<span class="error-text" id="confirm-error"></span>
</div>
<button type="submit" class="primary-btn" id="submit-btn">Sign In</button>
</form>
<a class="forgot-link" id="forgot-link" onclick="showResetView()">Forgot password?</a>
<div class="divider"><span>or continue with</span></div>
<div class="providers">
<button class="provider-btn google" onclick="handleOAuthProvider('google')">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"/>
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/>
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/>
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/>
</svg>
Continue with Google
</button>
<button class="provider-btn apple" onclick="handleOAuthProvider('apple')">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" fill="currentColor"/>
</svg>
Continue with Apple
</button>
<button class="provider-btn github" onclick="handleOAuthProvider('github')">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.17 6.839 9.49.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.464-1.11-1.464-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.167 22 16.418 22 12c0-5.523-4.477-10-10-10z" fill="currentColor"/>
</svg>
Continue with GitHub
</button>
</div>
</div>
<!-- Reset Password View -->
<div id="reset-view" class="reset-view">
<a class="back-link" onclick="hideResetView()">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Back to sign in
</a>
<div class="header">
<h1>Reset Password</h1>
</div>
<p class="reset-description">
Enter your email address and we'll send you a link to reset your password.
</p>
<form id="reset-form" class="form-container" onsubmit="handleResetSubmit(event)">
<div class="input-group" id="reset-email-group">
<label for="reset-email">Email</label>
<input type="email" id="reset-email" placeholder="you@example.com" autocomplete="email" required>
<span class="error-text" id="reset-email-error"></span>
</div>
<button type="submit" class="primary-btn" id="reset-btn">Send Reset Link</button>
</form>
</div>
<!-- Message Display -->
<div id="message" class="message"></div>
<!-- Loading Overlay -->
<div id="loading" class="loading-overlay">
<div class="spinner"></div>
<span class="loading-text" id="loading-text">Signing in...</span>
</div>
<!-- Debug Output -->
<div id="debug"></div>
</div>
<script>
// Configuration (populated from C++ via get_login_cmd)
let config = {
backend_url: '',
apikey: '',
pkce: null
};
// State
let currentMode = 'signin'; // 'signin' | 'signup'
// Debug logging
function log(msg) {
console.log(msg);
var d = document.getElementById('debug');
if (d) {
// Uncomment the next line to show debug panel
// d.style.display = 'block';
d.innerText += new Date().toISOString().substr(11, 8) + ' ' + msg + '\n';
d.scrollTop = d.scrollHeight;
}
}
// Send message to C++ via JavaScript bridge
function sendMessage(cmd, data) {
log('Sending: ' + cmd);
var msg = JSON.stringify({ command: cmd, data: data || {} });
try {
if (window.wx) {
window.wx.postMessage(msg);
} else if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.wx) {
window.webkit.messageHandlers.wx.postMessage(msg);
} else {
log('Error: No bridge found (wx or webkit)');
}
} catch (e) {
log('Send error: ' + e.toString());
}
}
// Handle messages from C++
window.addEventListener('message', function(event) {
log('Received message');
var msg = event.data;
if (typeof msg === 'string') {
try {
msg = JSON.parse(msg);
} catch (e) {
log('Parse error: ' + e.toString());
return;
}
}
log('Message action: ' + (msg.action || msg.command || 'unknown'));
// Handle login config from C++ (received once on page load)
if (msg.action === 'login_config') {
handleLoginConfig(msg);
return;
}
});
// Tab switching
function setMode(mode) {
currentMode = mode;
clearMessage();
clearErrors();
// Update tabs
document.querySelectorAll('.tab').forEach(function(tab) {
tab.classList.toggle('active', tab.dataset.mode === mode);
});
// Update form
var confirmGroup = document.getElementById('confirm-group');
var submitBtn = document.getElementById('submit-btn');
var forgotLink = document.getElementById('forgot-link');
var subtitle = document.getElementById('header-subtitle');
var passwordInput = document.getElementById('password');
if (mode === 'signup') {
confirmGroup.classList.add('visible');
submitBtn.textContent = 'Create Account';
forgotLink.style.display = 'none';
subtitle.textContent = 'Create your account';
passwordInput.autocomplete = 'new-password';
} else {
confirmGroup.classList.remove('visible');
submitBtn.textContent = 'Sign In';
forgotLink.style.display = 'block';
subtitle.textContent = 'Sign in to sync your settings';
passwordInput.autocomplete = 'current-password';
}
}
// Form submission - calls Supabase directly
async function handleSubmit(e) {
e.preventDefault();
clearMessage();
clearErrors();
var email = document.getElementById('email').value.trim();
var password = document.getElementById('password').value;
// Validate email
if (!isValidEmail(email)) {
showFieldError('email', 'Please enter a valid email address');
return;
}
// Validate password
if (password.length < 6) {
showFieldError('password', 'Password must be at least 6 characters');
return;
}
if (currentMode === 'signup') {
var confirmPassword = document.getElementById('confirm-password').value;
if (password !== confirmPassword) {
showFieldError('confirm', 'Passwords do not match');
return;
}
await handlePasswordSignup(email, password);
} else {
await handlePasswordLogin(email, password);
}
}
// Password login - direct Supabase call
async function handlePasswordLogin(email, password) {
if (!config.backend_url || !config.apikey) {
showMessage('Configuration not loaded. Please try again.', 'error');
return;
}
showLoading('Signing in...');
try {
var url = config.backend_url + '/auth/v1/token?grant_type=password';
var response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': config.apikey
},
body: JSON.stringify({ email: email, password: password })
});
var data = await response.json();
if (response.ok && data.access_token) {
// Success - send tokens to C++
sendUserLogin(data);
} else {
hideLoading();
var errorMsg = data.error_description || data.msg || data.error || 'Login failed';
showMessage(errorMsg, 'error');
}
} catch (err) {
hideLoading();
log('Login error: ' + err.toString());
showMessage('Network error. Please check your connection.', 'error');
}
}
// Password signup - direct Supabase call
async function handlePasswordSignup(email, password) {
if (!config.backend_url || !config.apikey) {
showMessage('Configuration not loaded. Please try again.', 'error');
return;
}
showLoading('Creating account...');
try {
var url = config.backend_url + '/auth/v1/signup';
var response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': config.apikey
},
body: JSON.stringify({ email: email, password: password })
});
var data = await response.json();
if (response.ok) {
if (data.access_token) {
// Auto-confirmed - send tokens to C++
sendUserLogin(data);
} else {
// Email verification required
hideLoading();
showMessage('Account created! Please check your email to verify your account.', 'success');
}
} else {
hideLoading();
var errorMsg = data.error_description || data.msg || data.error || 'Signup failed';
showMessage(errorMsg, 'error');
}
} catch (err) {
hideLoading();
log('Signup error: ' + err.toString());
showMessage('Network error. Please check your connection.', 'error');
}
}
// Password reset - direct Supabase call
async function handleResetSubmit(e) {
e.preventDefault();
clearMessage();
var email = document.getElementById('reset-email').value.trim();
if (!isValidEmail(email)) {
document.getElementById('reset-email-group').classList.add('error');
document.getElementById('reset-email-error').textContent = 'Please enter a valid email address';
return;
}
if (!config.backend_url || !config.apikey) {
showMessage('Configuration not loaded. Please try again.', 'error');
return;
}
showLoading('Sending reset link...');
try {
var url = config.backend_url + '/auth/v1/recover';
var response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': config.apikey
},
body: JSON.stringify({ email: email })
});
hideLoading();
// Supabase returns 200 even if email doesn't exist (for security)
if (response.ok) {
showMessage('Password reset email sent. Please check your inbox.', 'success');
} else {
showMessage('Failed to send reset email. Please try again.', 'error');
}
} catch (err) {
hideLoading();
log('Reset error: ' + err.toString());
showMessage('Network error. Please check your connection.', 'error');
}
}
// Send successful login to C++ (unified for all auth methods)
function sendUserLogin(data) {
var user = data.user || {};
var userMeta = user.user_metadata || {};
sendMessage('user_login', {
token: data.access_token,
refresh_token: data.refresh_token || '',
user_id: user.id || '',
username: userMeta.preferred_username || user.email || '',
name: userMeta.full_name || userMeta.name || user.email || '',
nickname: userMeta.preferred_username || userMeta.name || '',
avatar: userMeta.avatar_url || '',
state: config.pkce ? config.pkce.state : ''
});
}
// OAuth provider click - builds URL directly using stored config
function handleOAuthProvider(provider) {
log('OAuth provider: ' + provider);
if (!config.backend_url || !config.pkce) {
showMessage('Configuration not loaded. Please try again.', 'error');
return;
}
showLoading('Connecting to ' + provider.charAt(0).toUpperCase() + provider.slice(1) + '...');
var pkce = config.pkce;
// Construct Supabase Authorize URL
var url = config.backend_url + '/auth/v1/authorize';
var params = new URLSearchParams();
params.append('provider', provider);
params.append('code_challenge', pkce.code_challenge);
params.append('code_challenge_method', pkce.code_challenge_method);
params.append('redirect_uri', pkce.redirect_uri);
params.append('state', pkce.state);
params.append('response_type', 'code');
var fullUrl = url + '?' + params.toString();
document.getElementById('loading-text').textContent = 'Opening browser for secure login...';
// Request C++ to open this URL in system browser
sendMessage('thirdparty_login', { url: fullUrl });
// Show message after a delay
setTimeout(function() {
hideLoading();
showMessage('Browser opened. Complete login there, then this window will close automatically.', 'success');
}, 2000);
}
// Handle login config from C++ (received once on page load)
function handleLoginConfig(msg) {
log('Received login config');
// Store config for all auth methods
config.backend_url = msg.backend_url || config.backend_url;
config.apikey = msg.apikey || config.apikey;
config.pkce = msg.pkce || config.pkce;
log('Config stored: backend_url=' + config.backend_url);
}
// View switching
function showResetView() {
document.getElementById('auth-view').classList.add('hidden');
document.getElementById('reset-view').classList.add('visible');
clearMessage();
document.getElementById('reset-email').value = document.getElementById('email').value;
}
function hideResetView() {
document.getElementById('auth-view').classList.remove('hidden');
document.getElementById('reset-view').classList.remove('visible');
clearMessage();
}
// Loading state
function showLoading(text) {
document.getElementById('loading').classList.add('visible');
document.getElementById('loading-text').textContent = text || 'Loading...';
disableForm(true);
}
function hideLoading() {
document.getElementById('loading').classList.remove('visible');
disableForm(false);
}
function disableForm(disabled) {
document.querySelectorAll('input, button').forEach(function(el) {
el.disabled = disabled;
});
}
// Messages
function showMessage(text, type) {
var msg = document.getElementById('message');
msg.textContent = text;
msg.className = 'message ' + type;
}
function clearMessage() {
var msg = document.getElementById('message');
msg.textContent = '';
msg.className = 'message';
}
// Field errors
function showFieldError(field, message) {
var groupId = field + '-group';
var errorId = field + '-error';
var group = document.getElementById(groupId);
var error = document.getElementById(errorId);
if (group) group.classList.add('error');
if (error) error.textContent = message;
}
function clearErrors() {
document.querySelectorAll('.input-group').forEach(function(group) {
group.classList.remove('error');
});
document.querySelectorAll('.error-text').forEach(function(error) {
error.textContent = '';
});
}
// Validation
function isValidEmail(email) {
var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Initialize on load
window.onload = function() {
log('Window loaded');
// Request login config from C++ to get backend URL and API key
setTimeout(function() {
sendMessage('get_login_cmd');
}, 300);
};
</script>
</body>
</html>
View File
@@ -75,7 +75,7 @@ void generate_custom_presets(PresetBundle* preset_bundle, AppConfig& app_config)
fs::path folder(dir_user_presets);
if (!fs::exists(folder))
fs::create_directory(folder);
std::vector<std::string> need_to_delete_list; // store setting ids of preset
std::map<std::string, std::string> need_to_delete_list; // store setting ids of preset
preset_bundle->prints.save_user_presets(dir_user_presets, PRESET_PRINT_NAME, need_to_delete_list);
preset_bundle->filaments.save_user_presets(dir_user_presets, PRESET_FILAMENT_NAME, need_to_delete_list);
+18 -19
View File
@@ -161,10 +161,11 @@ namespace AABBTreeLines {
// on centroids of the lines.
// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies
// during tree traversal.
template <typename LineType>
inline AABBTreeIndirect::Tree<2, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(const std::vector<LineType>& lines)
template<typename LineType>
inline AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar> build_aabb_tree_over_indexed_lines(
const std::vector<LineType>& lines)
{
using TreeType = AABBTreeIndirect::Tree<2, typename LineType::Scalar>;
using TreeType = AABBTreeIndirect::Tree<LineType::Dim, typename LineType::Scalar>;
// using CoordType = typename TreeType::CoordType;
using VectorType = typename TreeType::VectorType;
using BoundingBox = typename TreeType::BoundingBox;
@@ -303,7 +304,7 @@ namespace AABBTreeLines {
private:
std::vector<LineType> lines;
AABBTreeIndirect::Tree<2, Scalar> tree;
AABBTreeIndirect::Tree<LineType::Dim, Scalar> tree;
public:
explicit LinesDistancer(const std::vector<LineType>& lines)
@@ -321,15 +322,15 @@ namespace AABBTreeLines {
LinesDistancer() = default;
// 1 true, -1 false, 0 cannot determine
int outside(const Vec<2, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); }
int outside(const Vec<LineType::Dim, Scalar>& point) const { return point_outside_closed_contours(lines, tree, point); }
// negative sign means inside
template <bool SIGNED_DISTANCE>
std::tuple<Floating, size_t, Vec<2, Floating>> distance_from_lines_extra(const Vec<2, Scalar>& point) const
template<bool SIGNED_DISTANCE>
std::tuple<Floating, size_t, Vec<LineType::Dim, Floating>> distance_from_lines_extra(const Vec<LineType::Dim, Scalar>& point) const
{
size_t nearest_line_index_out = size_t(-1);
Vec<2, Floating> nearest_point_out = Vec<2, Floating>::Zero();
Vec<2, Floating> p = point.template cast<Floating>();
Vec<LineType::Dim, Floating> nearest_point_out = Vec<LineType::Dim, Floating>::Zero();
Vec<LineType::Dim, Floating> p = point.template cast<Floating>();
auto distance = AABBTreeLines::squared_distance_to_indexed_lines(lines, tree, p, nearest_line_index_out, nearest_point_out);
if (distance < 0) {
@@ -344,22 +345,20 @@ namespace AABBTreeLines {
return { distance, nearest_line_index_out, nearest_point_out };
}
template <bool SIGNED_DISTANCE>
Floating distance_from_lines(const Vec<2, typename LineType::Scalar>& point) const
template<bool SIGNED_DISTANCE> Floating distance_from_lines(const Vec<LineType::Dim, typename LineType::Scalar>& point) const
{
auto [dist, idx, np] = distance_from_lines_extra<SIGNED_DISTANCE>(point);
return dist;
}
std::vector<size_t> all_lines_in_radius(const Vec<2, Scalar> &point, Floating radius)
{
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
template <bool sorted>
std::vector<std::pair<Vec<2, Scalar>, size_t>> intersections_with_line(const LineType& line) const
std::vector<size_t> all_lines_in_radius(const Vec<LineType::Dim, Scalar>& point, Floating radius)
{
return get_intersections_with_line<sorted, Vec<2, Scalar>>(lines, tree, line);
return AABBTreeLines::all_lines_in_radius(this->lines, this->tree, point.template cast<Floating>(), radius * radius);
}
template<bool sorted> std::vector<std::pair<Vec<LineType::Dim, Scalar>, size_t>> intersections_with_line(const LineType& line) const
{
return get_intersections_with_line<sorted, Vec<LineType::Dim, Scalar>>(lines, tree, line);
}
const LineType& get_line(size_t line_idx) const { return lines[line_idx]; }
+79 -1
View File
@@ -10,6 +10,7 @@
#include "format.hpp"
#include "nlohmann/json.hpp"
#include <algorithm>
#include <utility>
#include <vector>
#include <stdexcept>
@@ -40,7 +41,7 @@ using namespace nlohmann;
namespace Slic3r {
static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest";
static const std::string PROFILE_UPDATE_URL = "https://api.github.com/repos/OrcaSlicer/orcaslicer-profiles/releases/tags";
static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile";
static const std::string MODELS_STR = "models";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
@@ -320,6 +321,11 @@ void AppConfig::set_defaults()
if (get("allow_abnormal_storage").empty()) {
set_bool("allow_abnormal_storage", false);
}
if (get("preset_bundle_auto_update").empty()) {
set_bool("preset_bundle_auto_update", false);
}
#ifdef __linux__
if (get(SETTING_USE_ENCRYPTED_TOKEN_FILE).empty())
set_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE, true);
@@ -357,6 +363,10 @@ void AppConfig::set_defaults()
set_bool("show_daily_tips", true);
}
if (get("show_shared_profiles_notification").empty()) {
set_bool("show_shared_profiles_notification", true);
}
if (get("auto_calculate_flush").empty()){
set("auto_calculate_flush","all");
}
@@ -802,6 +812,18 @@ std::string AppConfig::load()
if (it_section->second.empty())
m_storage.erase(it_section);
}
// Default for new installs
if (get(SETTING_CLOUD_PROVIDERS).empty()) {
// Migrate add bbl cloud if installed_networking is true
bool enable_bbl_cloud = get_bool("installed_networking");
if (enable_bbl_cloud) {
// Legacy Bambu-only user: give them both providers
set(SETTING_CLOUD_PROVIDERS, "orca;bbl");
} else {
set(SETTING_CLOUD_PROVIDERS, "orca");
}
}
}
// Override missing or keys with their defaults.
@@ -1572,6 +1594,62 @@ void AppConfig::clear_remind_network_update_later()
set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, false);
}
std::vector<std::string> AppConfig::get_cloud_providers() const
{
std::vector<std::string> result;
std::string providers = get(SETTING_CLOUD_PROVIDERS);
if (providers.empty()) {
result.push_back("orca");
return result;
}
std::stringstream ss(providers);
std::string provider;
while (std::getline(ss, provider, ';')) {
if (!provider.empty())
result.push_back(provider);
}
// Ensure "orca" is always present
if (std::find(result.begin(), result.end(), "orca") == result.end()) {
result.insert(result.begin(), "orca");
}
return result;
}
void AppConfig::set_cloud_providers(const std::vector<std::string>& providers)
{
std::string joined;
for (size_t i = 0; i < providers.size(); ++i) {
if (i > 0) joined += ";";
joined += providers[i];
}
set(SETTING_CLOUD_PROVIDERS, joined);
}
bool AppConfig::has_cloud_provider(const std::string& provider) const
{
auto providers = get_cloud_providers();
return std::find(providers.begin(), providers.end(), provider) != providers.end();
}
void AppConfig::add_cloud_provider(const std::string& provider)
{
auto providers = get_cloud_providers();
if (std::find(providers.begin(), providers.end(), provider) == providers.end()) {
providers.push_back(provider);
set_cloud_providers(providers);
}
}
void AppConfig::remove_cloud_provider(const std::string& provider)
{
if (provider == "orca")
return; // Cannot remove orca
auto providers = get_cloud_providers();
providers.erase(std::remove(providers.begin(), providers.end(), provider), providers.end());
set_cloud_providers(providers);
}
void AppConfig::reset_selections()
{
auto it = m_storage.find("presets");
+8
View File
@@ -29,6 +29,7 @@ using namespace nlohmann;
#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled"
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
#define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file"
#define SETTING_CLOUD_PROVIDERS "cloud_providers"
#if defined(_WIN32) || defined(_WIN64)
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
@@ -374,6 +375,13 @@ public:
void set_remind_network_update_later(bool remind);
void clear_remind_network_update_later();
// Cloud providers (semicolon-delimited, e.g. "orca;bambu")
std::vector<std::string> get_cloud_providers() const;
void set_cloud_providers(const std::vector<std::string>& providers);
bool has_cloud_provider(const std::string& provider) const;
void add_cloud_provider(const std::string& provider);
void remove_cloud_provider(const std::string& provider);
private:
template<typename T>
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const
+12 -1
View File
@@ -236,7 +236,18 @@ class BoundingBox3 : public BoundingBox3Base<Vec3crd>
public:
BoundingBox3() : BoundingBox3Base<Vec3crd>() {}
BoundingBox3(const Vec3crd &pmin, const Vec3crd &pmax) : BoundingBox3Base<Vec3crd>(pmin, pmax) {}
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>(points) {}
BoundingBox3(const Points3& points) : BoundingBox3Base<Vec3crd>() {
if (!points.empty()) {
this->min = points.front();
this->max = points.front();
for (const auto &p : points) {
this->min = this->min.cwiseMin(static_cast<const Vec3crd&>(p));
this->max = this->max.cwiseMax(static_cast<const Vec3crd&>(p));
}
this->defined = true;
}
}
BoundingBox3(const std::vector<Vec3crd>& points) : BoundingBox3Base<Vec3crd>(points) {}
};
class BoundingBoxf : public BoundingBoxBase<Vec2d>
+1
View File
@@ -100,6 +100,7 @@ set(lisbslic3r_sources
CommonDefs.hpp
Config.cpp
Config.hpp
ContourZ.cpp
CustomGCode.cpp
CustomGCode.hpp
CutUtils.cpp
+113
View File
@@ -3,6 +3,7 @@
#include <cmath>
#include <cassert>
#include "Geometry.hpp"
#include "Polygon.hpp"
//BBS: Refer to ArcWelderLib for the arc fitting functions
@@ -94,6 +95,12 @@ bool Circle::try_create_circle(const Points& points, const double max_radius, co
return found_circle;
}
bool Circle::try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle)
{
return Circle::try_create_circle(to_points(points), max_radius, tolerance, new_circle);
}
double Circle::get_polar_radians(const Point& p1) const
{
double polar_radians = atan2(p1.y() - center.y(), p1.x() - center.x());
@@ -291,6 +298,31 @@ bool ArcSegment::try_create_arc(
return false;
}
bool ArcSegment::try_create_arc(
const Points3& points,
ArcSegment& target_arc,
double approximate_length,
double max_radius,
double tolerance,
double path_tolerance_percent)
{
Circle test_circle = (Circle)target_arc;
if (!Circle::try_create_circle(points, max_radius, tolerance, test_circle))
return false;
int mid_point_index = ((points.size() - 2) / 2) + 1;
ArcSegment test_arc;
if (!ArcSegment::try_create_arc(test_circle, points[0].to_point(), points[mid_point_index].to_point(), points[points.size() - 1].to_point(), test_arc, approximate_length, path_tolerance_percent))
return false;
if (ArcSegment::are_points_within_slice(test_arc, points))
{
target_arc = test_arc;
return true;
}
return false;
}
bool ArcSegment::try_create_arc(
const Circle& c,
const Point& start_point,
@@ -455,6 +487,87 @@ bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Point
return true;
}
bool ArcSegment::are_points_within_slice(const ArcSegment& test_arc, const Points3& points)
{
//BBS: Check all the points and see if they fit inside of the angles
double previous_polar = test_arc.polar_start_theta;
bool will_cross_zero = false;
bool crossed_zero = false;
const int point_count = points.size();
Vec2d start_norm(((double)test_arc.start_point.x() - (double)test_arc.center.x()) / test_arc.radius,
((double)test_arc.start_point.y() - (double)test_arc.center.y()) / test_arc.radius);
Vec2d end_norm(((double)test_arc.end_point.x() - (double)test_arc.center.x()) / test_arc.radius,
((double)test_arc.end_point.y() - (double)test_arc.center.y()) / test_arc.radius);
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
will_cross_zero = test_arc.polar_start_theta > test_arc.polar_end_theta;
else
will_cross_zero = test_arc.polar_start_theta < test_arc.polar_end_theta;
//BBS: check if point 1 to point 2 cross zero
double polar_test;
for (int index = point_count - 2; index < point_count; index++)
{
if (index < point_count - 1)
polar_test = test_arc.get_polar_radians(points[index].to_point());
else
polar_test = test_arc.polar_end_theta;
//BBS: First ensure the test point is within the arc
if (test_arc.direction == ArcDirection::Arc_Dir_CCW)
{
//BBS: Only check to see if we are within the arc if this isn't the endpoint
if (index < point_count - 1) {
if (will_cross_zero) {
if (!(polar_test > test_arc.polar_start_theta || polar_test < test_arc.polar_end_theta))
return false;
} else if (!(test_arc.polar_start_theta < polar_test && polar_test < test_arc.polar_end_theta))
return false;
}
//BBS: check the angles are increasing
if (previous_polar > polar_test) {
if (!will_cross_zero)
return false;
//BBS: Allow the angle to cross zero once
if (crossed_zero)
return false;
crossed_zero = true;
}
} else {
if (index < point_count - 1) {
if (will_cross_zero) {
if (!(polar_test < test_arc.polar_start_theta || polar_test > test_arc.polar_end_theta))
return false;
} else if (!(test_arc.polar_start_theta > polar_test && polar_test > test_arc.polar_end_theta))
return false;
}
//BBS: Now make sure the angles are decreasing
if (previous_polar < polar_test)
{
if (!will_cross_zero)
return false;
//BBS: Allow the angle to cross zero once
if (crossed_zero)
return false;
crossed_zero = true;
}
}
// BBS: check if the segment intersects either of the vector from the center of the circle to the endpoints of the arc
Line segmemt(points[index - 1].to_point(), points[index].to_point());
if ((index != 1 && ray_intersects_segment(test_arc.center, start_norm, segmemt)) ||
(index != point_count - 1 && ray_intersects_segment(test_arc.center, end_norm, segmemt)))
return false;
previous_polar = polar_test;
}
//BBS: Ensure that all arcs that cross zero
if (will_cross_zero != crossed_zero)
return false;
return true;
}
// BBS: this function is used to detect whether a ray cross the segment
bool ArcSegment::ray_intersects_segment(const Point &rayOrigin, const Vec2d &rayDirection, const Line& segment)
{
+9
View File
@@ -28,6 +28,7 @@ public:
static bool try_create_circle(const Point &p1, const Point &p2, const Point &p3, const double max_radius, Circle& new_circle);
static bool try_create_circle(const Points& points, const double max_radius, const double tolerance, Circle& new_circle);
static bool try_create_circle(const Points3& points, const double max_radius, const double tolerance, Circle& new_circle);
double get_polar_radians(const Point& p1) const;
bool is_over_deviation(const Points& points, const double tolerance);
bool get_deviation_sum_squared(const Points& points, const double tolerance, double& sum_deviation);
@@ -111,8 +112,16 @@ public:
double max_radius = DEFAULT_SCALED_MAX_RADIUS,
double tolerance = DEFAULT_SCALED_RESOLUTION,
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
static bool try_create_arc(
const Points3 &points,
ArcSegment& target_arc,
double approximate_length,
double max_radius = DEFAULT_SCALED_MAX_RADIUS,
double tolerance = DEFAULT_SCALED_RESOLUTION,
double path_tolerance_percent = DEFAULT_ARC_LENGTH_PERCENT_TOLERANCE);
static bool are_points_within_slice(const ArcSegment& test_arc, const Points &points);
static bool are_points_within_slice(const ArcSegment& test_arc, const Points3 &points);
// BBS: this function is used to detect whether a ray cross the segment
static bool ray_intersects_segment(const Point& rayOrigin, const Vec2d& rayDirection, const Line& segment);
// BBS: these three functions are used to calculate related arguments of arc in unscale_field.
+13
View File
@@ -411,6 +411,19 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::SinglePathProvider(polyline.points), delta, joinType, miterLimit, end_type))); }
Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{
assert(delta > 0);
return to_polygons(
clipper_union<ClipperLib::Paths>(
raw_offset_polyline(
ClipperUtils::SinglePathProvider(polyline.to_polyline().points),
delta,
joinType,
miterLimit,
end_type)));
}
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib::EndType end_type)
{ assert(delta > 0); return to_polygons(clipper_union<ClipperLib::Paths>(raw_offset_polyline(ClipperUtils::PolylinesProvider(polylines), delta, joinType, miterLimit, end_type))); }
+3
View File
@@ -334,6 +334,7 @@ Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, Clipp
// Wherever applicable, please use the expand() / shrink() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polyline3 &polyline, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = DefaultLineJoinType, double miterLimit = DefaultLineMiterLimit, ClipperLib::EndType end_type = DefaultEndType);
Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
@@ -524,6 +525,8 @@ Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::Polygon &clip);
Slic3r::Polylines3 intersection_pl(const Slic3r::Polylines3 &subject, const Slic3r::ExPolygon &clip);
inline Slic3r::Lines intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip)
{
+236
View File
@@ -0,0 +1,236 @@
#include "Exception.hpp"
#include "ExtrusionEntity.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "Layer.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "SLA/IndexedMesh.hpp"
#include "libslic3r.h"
#include <cfloat>
#include <cmath>
#include <initializer_list>
#include <string>
namespace Slic3r {
static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr);
static double follow_slope_down(double angle_rad, double dist)
{
return -dist * std::sin(angle_rad);
}
static double slope_from_normal(const Eigen::Vector3d& normal)
{
// Ensure the normal is normalized
Eigen::Vector3d n = normal.normalized();
// Compute angle between normal and z-axis
double angle_rad = std::acos(std::abs(n.z())); // angle between normal and vertical
return angle_rad;
}
static bool contour_extrusion_path(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionPath &path)
{
if (path.role() != erTopSolidInfill && path.role() != erIroning && path.role() != erExternalPerimeter && path.role() != erPerimeter) {
return false;
}
Layer *layer = region->layer();
coordf_t mesh_z = layer->print_z + mesh.ground_level();
coordf_t min_z = region->region().config().zaa_min_z;
const Points3 &points = path.polyline.points;
double resolution_mm = 0.1;
coordf_t height = layer->height;
double minimize_perimeter_height_angle = region->region().config().zaa_minimize_perimeter_height;
Pointf3s contoured_points;
bool was_contoured = false;
for (Points3::const_iterator it = points.begin(); it != points.end()-1; ++it) {
Vec2d p1d(unscale_(it->x()), unscale_(it->y()));
Vec2d p2d(unscale_((it+1)->x()), unscale_((it+1)->y()));
Linef line(p1d, p2d);
double length_mm = line.length();
int num_segments = int(std::ceil(length_mm / resolution_mm));
Vec2d delta = line.vector();
if (num_segments == 0) {
continue;
}
for (int i = 0; i < num_segments + 1; i++) {
Vec2d p = p1d + delta * i / num_segments;
coordf_t x = p.x();
coordf_t y = p.y();
sla::IndexedMesh::hit_result hit_up = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, 1.0});
sla::IndexedMesh::hit_result hit_down = mesh.query_ray_hit({x, y, mesh_z}, {0.0, 0.0, -1.0});
double up = hit_up.distance();
double down = hit_down.distance();
double d = up < down ? up : -down;
const Vec3d &normal = (up < down ? hit_up : hit_down).normal();
double max_up = min_z;
double min_down = -(height - min_z);
double half_width = path.width / 2.0;
if (path.role() == erIroning) {
max_up = height;
min_down = -(height + 0.1);
}
if (is_perimeter(path.role())) {
double slope_rad = slope_from_normal(normal);
double slope_degrees = slope_rad * 180.0 / M_PI;
if (d > min_down && minimize_perimeter_height_angle > 0 && minimize_perimeter_height_angle < slope_degrees) {
double adjustment = follow_slope_down(slope_rad, half_width);
if (adjustment > 0) {
throw RuntimeError("ContourZ: got positive adjustment");
}
d += adjustment;
if (d < min_down) {
d = min_down;
}
}
}
if (d < -height || d > max_up + 0.03) {
// this point is too far from the mesh edge, probably because this is not a top surface. Do not contour it.
d = 0;
}
if (d < min_down) {
d = min_down;
} else if (d > max_up) {
d = max_up;
}
if (is_perimeter(path.role()) && d > 0) {
// do not increase height of perimeters as this may create an appearance of a seam
d = 0;
}
if (std::abs(d) > EPSILON) {
was_contoured = true;
}
Vec3d new_point = {p.x(), p.y(), d};
if (contoured_points.size() >= 2) {
double dist = Linef3::distance_to_infinite_squared(new_point, contoured_points[contoured_points.size() - 2],
contoured_points[contoured_points.size() - 1]);
if (dist < EPSILON * EPSILON) {
contoured_points[contoured_points.size() - 1] = new_point;
continue;
}
}
contoured_points.push_back(new_point);
}
}
if (!was_contoured) {
return false;
}
Polyline3 polyline;
for (const Vec3d &point : contoured_points) {
polyline.append(Point3(scale_(point.x()), scale_(point.y()), scale_(point.z())));
}
path.polyline = std::move(polyline);
path.z_contoured = true;
return true;
}
static void contour_extrusion_multipath(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionMultiPath &multipath)
{
for (ExtrusionPath &path : multipath.paths) {
contour_extrusion_path(region, mesh, path);
}
}
static void contour_extrusion_loop(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionLoop &loop)
{
for (ExtrusionPath &path : loop.paths) {
contour_extrusion_path(region, mesh, path);
}
}
static void contour_extrusion_entitiy_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection)
{
for (ExtrusionEntity *entity : collection.entities) {
contour_extrusion_entity(region, mesh, entity);
}
}
static void contour_extrusion_entity(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntity *extr)
{
const ExtrusionPathSloped *sloped = dynamic_cast<const ExtrusionPathSloped*>(extr);
if (sloped != nullptr) {
throw RuntimeError("ExtrusionPathSloped not implemented");
return;
}
ExtrusionMultiPath *multipath = dynamic_cast<ExtrusionMultiPath*>(extr);
if (multipath != nullptr) {
contour_extrusion_multipath(region, mesh, *multipath);
return;
}
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(extr);
if (path != nullptr) {
contour_extrusion_path(region, mesh, *path);
return;
}
ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop*>(extr);
if (loop != nullptr) {
contour_extrusion_loop(region, mesh, *loop);
return;
}
const ExtrusionLoopSloped *loop_sloped = dynamic_cast<const ExtrusionLoopSloped*>(extr);
if (loop_sloped != nullptr) {
throw RuntimeError("ExtrusionLoopSloped not implemented");
return;
}
ExtrusionEntityCollection *collection = dynamic_cast<ExtrusionEntityCollection*>(extr);
if (collection != nullptr) {
contour_extrusion_entitiy_collection(region, mesh, *collection);
return;
}
throw RuntimeError("ContourZ: ExtrusionEntity type not implemented: " + std::string(typeid(*extr).name()));
return;
}
static void handle_extrusion_collection(LayerRegion *region, const sla::IndexedMesh &mesh, ExtrusionEntityCollection &collection, std::initializer_list<ExtrusionRole> roles) {
for (ExtrusionEntity* extr : collection.entities) {
if (!contains(roles, extr->role())) {
continue;
}
contour_extrusion_entity(region, mesh, extr);
}
}
void Layer::make_contour_z(const sla::IndexedMesh &mesh)
{
for (LayerRegion *region : this->regions()) {
if (!region->region().config().zaa_enabled)
continue;
handle_extrusion_collection(region, mesh, region->fills, {erTopSolidInfill, erIroning, erPerimeter, erExternalPerimeter, erMixed});
handle_extrusion_collection(region, mesh, region->perimeters, {erPerimeter, erExternalPerimeter, erMixed});
}
}
} // namespace Slic3r
+19
View File
@@ -192,6 +192,25 @@ inline Linesf to_unscaled_linesf(const ExPolygons &src)
return lines;
}
inline Linesf3 to_unscaled_linesf3(const ExPolygons& src)
{
Linesf3 lines;
lines.reserve(count_points(src));
for (ExPolygons::const_iterator it_expoly = src.begin(); it_expoly != src.end(); ++it_expoly) {
for (size_t i = 0; i <= it_expoly->holes.size(); ++i) {
const Points& points = ((i == 0) ? it_expoly->contour : it_expoly->holes[i - 1]).points;
Vec2d unscaled_a = unscaled(points.front());
Vec2d unscaled_b = unscaled_a;
for (Points::const_iterator it = points.begin() + 1; it != points.end(); ++it) {
unscaled_b = unscaled(*(it));
lines.push_back(Linef3(unscaled_a, unscaled_b, 0));
unscaled_a = unscaled_b;
}
lines.push_back(Linef3(unscaled_a, unscaled(points.front()), 0));
}
}
return lines;
}
inline Points to_points(const ExPolygons &src)
{
+73 -28
View File
@@ -17,12 +17,12 @@ static const double slope_inner_outer_wall_gap = 0.4;
void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval);
this->_inflate_collection(intersection_pl(Polylines{ polyline.to_polyline() }, collection), retval);
}
void ExtrusionPath::subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(diff_pl(Polylines{ this->polyline }, collection), retval);
this->_inflate_collection(diff_pl(Polylines{ this->polyline.to_polyline() }, collection), retval);
}
void ExtrusionPath::clip_end(double distance)
@@ -32,11 +32,17 @@ void ExtrusionPath::clip_end(double distance)
void ExtrusionPath::simplify(double tolerance)
{
if (this->z_contoured) {
return;
}
this->polyline.simplify(tolerance);
}
void ExtrusionPath::simplify_by_fitting_arc(double tolerance)
{
if (this->z_contoured) {
return;
}
this->polyline.simplify_by_fitting_arc(tolerance);
}
@@ -45,15 +51,23 @@ double ExtrusionPath::length() const
return this->polyline.length();
}
void ExtrusionPath::collect_points(Points &dst) const
{
dst.reserve(dst.size() + this->polyline.points.size());
for (const Point3 &point : this->polyline.points) {
dst.emplace_back(point.x(), point.y());
}
}
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (const Polyline &polyline : polylines)
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
collection->entities.emplace_back(new ExtrusionPath(Polyline3(polyline), *this));
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon));
polygons_append(out, offset(this->polyline.to_polyline(), float(scale_(this->width/2)) + scaled_epsilon));
}
void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
@@ -64,7 +78,7 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
// SoftFever: TODO Mac trigger assersion errors
// assert(! bridge || this->width == this->height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
polygons_append(out, offset(this->polyline.to_polyline(), 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}
void ExtrusionMultiPath::reverse()
@@ -116,9 +130,10 @@ Polyline ExtrusionMultiPath::as_polyline() const
len -= paths.size() - 1;
assert(len > 0);
out.points.reserve(len);
out.points.push_back(paths.front().polyline.points.front());
out.points.push_back(paths.front().polyline.points.front().to_point());
for (size_t i_path = 0; i_path < paths.size(); ++ i_path)
out.points.insert(out.points.end(), paths[i_path].polyline.points.begin() + 1, paths[i_path].polyline.points.end());
for (auto it = paths[i_path].polyline.points.begin() + 1; it != paths[i_path].polyline.points.end(); ++it)
out.points.push_back(it->to_point());
}
return out;
}
@@ -149,7 +164,9 @@ Polygon ExtrusionLoop::polygon() const
Polygon polygon;
for (const ExtrusionPath &path : this->paths) {
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
for (auto it = path.polyline.points.begin(); it != path.polyline.points.end() - 1; ++it) {
polygon.points.push_back(it->to_point());
}
}
return polygon;
}
@@ -168,7 +185,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi
if (int idx = path->polyline.find_point(point, scaled_epsilon); idx != -1) {
if (this->paths.size() == 1) {
// just change the order of points
Polyline p1, p2;
Polyline3 p1, p2;
path->polyline.split_at_index(idx, &p1, &p2);
if (p1.is_valid() && p2.is_valid()) {
p2.append(std::move(p1));
@@ -178,7 +195,7 @@ bool ExtrusionLoop::split_at_vertex(const Point &point, const double scaled_epsi
} else {
// new paths list starts with the second half of current path
ExtrusionPaths new_paths;
Polyline p1, p2;
Polyline3 p1, p2;
path->polyline.split_at_index(idx, &p1, &p2);
new_paths.reserve(this->paths.size() + 1);
{
@@ -218,16 +235,17 @@ ExtrusionLoop::ClosestPathPoint ExtrusionLoop::get_closest_path_and_point(const
ClosestPathPoint best_non_overhang{0, 0};
double min2_non_overhang = std::numeric_limits<double>::max();
for (const ExtrusionPath &path : this->paths) {
std::pair<int, Point> foot_pt_ = foot_pt(path.polyline.points, point);
double d2 = (foot_pt_.second - point).cast<double>().squaredNorm();
std::pair<int, Point3> foot_pt_ = foot_pt(path.polyline.points, Point3(point));
Point foot_pt_2d = Point(foot_pt_.second.x(), foot_pt_.second.y());
double d2 = (foot_pt_2d - point).cast<double>().squaredNorm();
if (d2 < min2) {
out.foot_pt = foot_pt_.second;
out.foot_pt = foot_pt_2d;
out.path_idx = &path - &this->paths.front();
out.segment_idx = foot_pt_.first;
min2 = d2;
}
if (prefer_non_overhang && !is_bridge(path.role()) && d2 < min2_non_overhang) {
best_non_overhang.foot_pt = foot_pt_.second;
best_non_overhang.foot_pt = foot_pt_2d;
best_non_overhang.path_idx = &path - &this->paths.front();
best_non_overhang.segment_idx = foot_pt_.first;
min2_non_overhang = d2;
@@ -249,16 +267,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
// Snap p to start or end of segment_idx if closer than scaled_epsilon.
{
const Point *p1 = this->paths[path_idx].polyline.points.data() + segment_idx;
const Point *p2 = p1;
const Point3 *p1 = this->paths[path_idx].polyline.points.data() + segment_idx;
const Point3 *p2 = p1;
++p2;
double d2_1 = (point - *p1).cast<double>().squaredNorm();
double d2_2 = (point - *p2).cast<double>().squaredNorm();
Point p1_2d = Point(p1->x(), p1->y());
Point p2_2d = Point(p2->x(), p2->y());
double d2_1 = (point - p1_2d).cast<double>().squaredNorm();
double d2_2 = (point - p2_2d).cast<double>().squaredNorm();
const double thr2 = scaled_epsilon * scaled_epsilon;
if (d2_1 < d2_2) {
if (d2_1 < thr2) p = *p1;
if (d2_1 < thr2) p = p1_2d;
} else {
if (d2_2 < thr2) p = *p2;
if (d2_2 < thr2) p = p2_2d;
}
}
@@ -266,8 +286,9 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang, const
const ExtrusionPath &path = this->paths[path_idx];
ExtrusionPath p1(path.role(), path.mm3_per_mm, path.width, path.height);
ExtrusionPath p2(path.role(), path.mm3_per_mm, path.width, path.height);
p1.z_contoured = p2.z_contoured = path.z_contoured;
path.polyline.split_at(p, &p1.polyline, &p2.polyline);
if (this->paths.size() == 1) {
if (!p1.polyline.is_valid()) {
std::swap(this->paths.front().polyline.points, p2.polyline.points);
@@ -411,16 +432,16 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
: ExtrusionLoop(role)
{
// create slopes
const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline &poly, double ratio_begin, double ratio_end) {
const auto add_slop = [this, slope_max_segment_length, seam_gap](const ExtrusionPath &path, const Polyline3 &poly, double ratio_begin, double ratio_end) {
if (poly.empty()) { return; }
// Ensure `slope_max_segment_length`
Polyline detailed_poly;
Polyline3 detailed_poly;
{
detailed_poly.append(poly.first_point());
// Recursively split the line into half until no longer than `slope_max_segment_length`
const std::function<void(const Line &)> handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line &line) {
const std::function<void(const Line3 &)> handle_line = [slope_max_segment_length, &detailed_poly, &handle_line](const Line3 &line) {
if (line.length() <= slope_max_segment_length) {
detailed_poly.append(line.b);
} else {
@@ -441,8 +462,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
const auto seg_length = detailed_poly.length();
if (seg_length > seam_gap) {
// Split the segment and remove the last `seam_gap` bit
const Polyline orig = detailed_poly;
Polyline tmp;
const Polyline3 orig = detailed_poly;
Polyline3 tmp;
orig.split_at_length(seg_length - seam_gap, &detailed_poly, &tmp);
ratio_end = lerp(ratio_begin, ratio_end, (seg_length - seam_gap) / seg_length);
@@ -464,8 +485,8 @@ ExtrusionLoopSloped::ExtrusionLoopSloped(ExtrusionPaths& original_paths,
const double path_len = unscale_(path->length());
if (path_len > remaining_length) {
// Split current path into slope and non-slope part
Polyline slope_path;
Polyline flat_path;
Polyline3 slope_path;
Polyline3 flat_path;
path->polyline.split_at_length(scale_(remaining_length), &slope_path, &flat_path);
add_slop(*path, slope_path, start_ratio, 1);
@@ -631,4 +652,28 @@ ExtrusionRole ExtrusionEntity::string_to_role(const std::string_view role)
return erNone;
}
// ExtrusionPathContoured implementation
ExtrusionEntity *ExtrusionPathContoured::clone() const {
return new ExtrusionPathContoured(*this);
}
ExtrusionEntity *ExtrusionPathContoured::clone_move() {
return new ExtrusionPathContoured(std::move(*this));
}
void ExtrusionPathContoured::simplify(double tolerance) {
// Do not simplify contoured paths
return;
}
void ExtrusionPathContoured::simplify_by_fitting_arc(double tolerance) {
// Do not simplify contoured paths
return;
}
void ExtrusionPathContoured::reverse() {
this->polyline.reverse();
std::reverse(this->z_diffs.begin(), this->z_diffs.end());
}
}
+80 -30
View File
@@ -116,8 +116,8 @@ public:
virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {}
virtual void reverse() = 0;
virtual const Point& first_point() const = 0;
virtual const Point& last_point() const = 0;
virtual Point first_point() const = 0;
virtual Point last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
@@ -150,13 +150,17 @@ typedef std::vector<ExtrusionEntity*> ExtrusionEntitiesPtr;
class ExtrusionPath : public ExtrusionEntity
{
public:
Polyline polyline;
Polyline3 polyline;
double overhang_degree = 0;
int curve_degree = 0;
// Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator.
double mm3_per_mm;
// Width of the extrusion, used for visualization purposes.
float width;
// Height of the extrusion, used for visualization purposes.
float height;
double smooth_speed = 0;
bool z_contoured = false;
ExtrusionPath() : mm3_per_mm(-1), width(-1), height(-1), m_role(erNone), m_no_extrusion(false) {}
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), m_role(role), m_no_extrusion(false) {}
@@ -164,36 +168,52 @@ public:
ExtrusionPath(const ExtrusionPath &rhs)
: polyline(rhs.polyline)
, overhang_degree(rhs.overhang_degree)
, curve_degree(rhs.curve_degree)
, mm3_per_mm(rhs.mm3_per_mm)
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
, z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
ExtrusionPath(ExtrusionPath &&rhs)
: polyline(std::move(rhs.polyline))
, overhang_degree(rhs.overhang_degree)
, curve_degree(rhs.curve_degree)
, mm3_per_mm(rhs.mm3_per_mm)
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
, z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs)
ExtrusionPath(const Polyline3 &polyline, const ExtrusionPath &rhs)
: polyline(polyline)
, overhang_degree(rhs.overhang_degree)
, curve_degree(rhs.curve_degree)
, mm3_per_mm(rhs.mm3_per_mm)
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
, z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
{}
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs)
ExtrusionPath(Polyline3 &&polyline, const ExtrusionPath &rhs)
: polyline(std::move(polyline))
, overhang_degree(rhs.overhang_degree)
, curve_degree(rhs.curve_degree)
, mm3_per_mm(rhs.mm3_per_mm)
, width(rhs.width)
, height(rhs.height)
, smooth_speed(rhs.smooth_speed)
, z_contoured(rhs.z_contoured)
, m_can_reverse(rhs.m_can_reverse)
, m_role(rhs.m_role)
, m_no_extrusion(rhs.m_no_extrusion)
@@ -206,6 +226,10 @@ public:
this->mm3_per_mm = rhs.mm3_per_mm;
this->width = rhs.width;
this->height = rhs.height;
this->smooth_speed = rhs.smooth_speed;
this->z_contoured = rhs.z_contoured;
this->overhang_degree = rhs.overhang_degree;
this->curve_degree = rhs.curve_degree;
this->polyline = rhs.polyline;
return *this;
}
@@ -216,6 +240,10 @@ public:
this->mm3_per_mm = rhs.mm3_per_mm;
this->width = rhs.width;
this->height = rhs.height;
this->smooth_speed = rhs.smooth_speed;
this->z_contoured = rhs.z_contoured;
this->overhang_degree = rhs.overhang_degree;
this->curve_degree = rhs.curve_degree;
this->polyline = std::move(rhs.polyline);
return *this;
}
@@ -224,8 +252,10 @@ public:
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); }
const Point& first_point() const override { return this->polyline.points.front(); }
const Point& last_point() const override { return this->polyline.points.back(); }
Point first_point() const override { return this->polyline.points.front().to_point(); }
Point3 first_point3() const { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back().to_point(); }
Point3 last_point3() const { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
@@ -236,7 +266,7 @@ public:
// Currently not used.
void subtract_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const;
void clip_end(double distance);
void simplify(double tolerance);
virtual void simplify(double tolerance);
double length() const override;
ExtrusionRole role() const override { return m_role; }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
@@ -252,9 +282,10 @@ public:
{ Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; }
// Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm.
double min_mm3_per_mm() const override { return this->mm3_per_mm; }
Polyline as_polyline() const override { return this->polyline; }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); }
void collect_points(Points &dst) const override { append(dst, this->polyline.points); }
Polyline as_polyline() const override { return this->polyline.to_polyline(); }
void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline.to_polyline()); }
void collect_points(Points &dst) const override;
void collect_points3(Points3 &dst) const { append(dst, this->polyline.points); }
double total_volume() const override { return mm3_per_mm * unscale<double>(length()); }
//BBS: add new simplifing method by fitting arc
@@ -274,6 +305,23 @@ private:
bool m_no_extrusion = false;
};
class ExtrusionPathContoured : public ExtrusionPath {
public:
std::vector<double> z_diffs;
ExtrusionPathContoured(Polyline3 &&polyline, const ExtrusionPath &rhs, std::vector<double> &&z_diffs)
: ExtrusionPath(std::move(polyline), rhs), z_diffs(std::move(z_diffs))
{}
virtual ExtrusionEntity *clone() const override;
virtual ExtrusionEntity *clone_move() override;
void simplify(double tolerance) override;
virtual void simplify_by_fitting_arc(double tolerance);
void reverse() override;
};
class ExtrusionPathSloped : public ExtrusionPath
{
public:
@@ -292,10 +340,10 @@ public:
ExtrusionPathSloped(ExtrusionPath&& rhs, const Slope& begin, const Slope& end)
: ExtrusionPath(std::move(rhs)), slope_begin(begin), slope_end(end)
{}
ExtrusionPathSloped(const Polyline& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end)
ExtrusionPathSloped(const Polyline3& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end)
: ExtrusionPath(polyline, rhs), slope_begin(begin), slope_end(end)
{}
ExtrusionPathSloped(Polyline&& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end)
ExtrusionPathSloped(Polyline3&& polyline, const ExtrusionPath& rhs, const Slope& begin, const Slope& end)
: ExtrusionPath(std::move(polyline), rhs), slope_begin(begin), slope_end(end)
{}
@@ -354,8 +402,8 @@ public:
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override;
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
Point last_point() const override { return this->paths.back().polyline.points.back().to_point(); }
size_t size() const { return this->paths.size(); }
bool empty() const { return this->paths.empty(); }
double length() const override;
@@ -379,7 +427,7 @@ public:
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
dst.reserve(dst.size() + n);
for (const ExtrusionPath &p : this->paths)
append(dst, p.polyline.points);
append(dst, to_points(p.polyline.points));
}
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
@@ -410,8 +458,8 @@ public:
bool is_clockwise() { return this->polygon().is_clockwise(); }
bool is_counter_clockwise() { return this->polygon().is_counter_clockwise(); }
void reverse() override;
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
Point first_point() const override { return this->paths.front().polyline.points.front().to_point(); }
Point last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back().to_point()); return this->first_point(); }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled<double>(0.001));
@@ -449,7 +497,7 @@ public:
size_t n = std::accumulate(paths.begin(), paths.end(), 0, [](const size_t n, const ExtrusionPath &p){ return n + p.polyline.size(); });
dst.reserve(dst.size() + n);
for (const ExtrusionPath &p : this->paths)
append(dst, p.polyline.points);
append(dst, to_points(p.polyline.points));
}
double total_volume() const override { double volume =0.; for (const auto& path : paths) volume += path.total_volume(); return volume; }
// check if the loop is smooth, angle_threshold is in radians, default is 10 degrees
@@ -495,7 +543,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, Ex
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.emplace_back(role, mm3_per_mm, width, height);
dst.back().polyline = polyline;
dst.back().polyline = Polyline3(polyline);
}
}
@@ -505,7 +553,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E
for (Polyline &polyline : polylines)
if (polyline.is_valid()) {
dst.emplace_back(role, mm3_per_mm, width, height);
dst.back().polyline = std::move(polyline);
dst.back().polyline = Polyline3(std::move(polyline));
}
polylines.clear();
}
@@ -515,7 +563,7 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polyline &&polyline, Ext
dst.reserve(dst.size() + 1);
if (polyline.is_valid()) {
dst.emplace_back(role, mm3_per_mm, width, height);
dst.back().polyline = std::move(polyline);
dst.back().polyline = Polyline3(std::move(polyline));
}
}
@@ -526,7 +574,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
extrusion_path->polyline = Polyline3(polyline);
}
}
@@ -537,7 +585,7 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = std::move(polyline);
extrusion_path->polyline = Polyline3(std::move(polyline));
}
polylines.clear();
}
@@ -557,7 +605,7 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst,
Point temp = polyline.first_point() - last_end_point;
if (Vec2d(temp.x(), temp.y()).norm() <= 3 * scaled(width)) {
multi_path->paths.emplace_back(role, mm3_per_mm, width, height, true);
multi_path->paths.back().polyline = std::move(Polyline(last_end_point, polyline.first_point()));
multi_path->paths.back().polyline = Polyline3(Polyline(last_end_point, polyline.first_point()));
} else {
dst.push_back(multi_path);
multi_path = new ExtrusionMultiPath();
@@ -565,9 +613,9 @@ inline void extrusion_entities_append_paths_with_wipe(ExtrusionEntitiesPtr &dst,
}
multi_path->paths.emplace_back(role, mm3_per_mm, width, height);
multi_path->paths.back().polyline = std::move(polyline);
multi_path->paths.back().polyline = Polyline3(std::move(polyline));
last_end_point_valid = true;
last_end_point = multi_path->paths.back().polyline.last_point();
last_end_point = multi_path->paths.back().polyline.last_point().to_point();
}
}
if (!multi_path->empty())
@@ -582,7 +630,9 @@ inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons
for (Polygon &poly : loops) {
if (poly.is_valid()) {
ExtrusionPath path(role, mm3_per_mm, width, height);
path.polyline.points = std::move(poly.points);
path.polyline.points.reserve(poly.points.size() + 1);
for (const Point &pt : poly.points)
path.polyline.points.emplace_back(Point3(pt, 0));
path.polyline.points.push_back(path.polyline.points.front());
dst.emplace_back(new ExtrusionLoop(std::move(path)));
}
@@ -597,11 +647,11 @@ inline void extrusion_entities_append_loops_and_paths(ExtrusionEntitiesPtr &dst,
if (polyline.is_valid()) {
if (polyline.is_closed()) {
ExtrusionPath extrusion_path(role, mm3_per_mm, width, height);
extrusion_path.polyline = std::move(polyline);
extrusion_path.polyline = Polyline3(std::move(polyline));
dst.emplace_back(new ExtrusionLoop(std::move(extrusion_path)));
} else {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
extrusion_path->polyline = std::move(polyline);
extrusion_path->polyline = Polyline3(std::move(polyline));
dst.emplace_back(extrusion_path);
}
}
+2 -2
View File
@@ -118,8 +118,8 @@ public:
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const
{ return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse() override;
const Point& first_point() const override { return this->entities.front()->first_point(); }
const Point& last_point() const override { return this->entities.back()->last_point(); }
Point first_point() const override { return this->entities.front()->first_point(); }
Point last_point() const override { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;
+1 -1
View File
@@ -962,7 +962,7 @@ void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const
w = scale_(path.mm3_per_mm / path.height) * scalex;
// printf("scalex: %f, scaley: %f\n", scalex, scaley);
// printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
for (Points3::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
// printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y);
ExtrusionPoint ept;
ept.center = V2f(float((*it)(0)+shift.x()-bbox.min.x()) * scalex, float((*it)(1)+shift.y()-bbox.min.y()) * scaley);
+9
View File
@@ -1218,6 +1218,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
{
const auto &rcfg = m_regions[surface_fill.region_id]->region().config();
f->dont_alternate_fill_direction = rcfg.zaa_enabled && rcfg.zaa_dont_alternate_fill_direction;
}
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
@@ -1418,6 +1422,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id() - this->object()->get_layer(0)->id(); // We need to subtract raft layers.
{
const auto &rcfg = m_regions[surface_fill.region_id]->region().config();
f->dont_alternate_fill_direction = rcfg.zaa_enabled && rcfg.zaa_dont_alternate_fill_direction;
}
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->fixed_angle = surface_fill.params.fixed_angle;
@@ -1595,6 +1603,7 @@ void Layer::make_ironing()
for (size_t i = 0; i < by_extruder.size();) {
// Find span of regions equivalent to the ironing operation.
IroningParams &ironing_params = by_extruder[i];
f->dont_alternate_fill_direction = ironing_params.layerm->region().config().zaa_enabled && ironing_params.layerm->region().config().zaa_dont_alternate_fill_direction;
// Create the filler object.
if( f_pattern != ironing_params.pattern )
{
+3 -1
View File
@@ -308,7 +308,9 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
} else if (this->layer_id != size_t(-1) && !fixed_angle) {
// alternate fill direction
//Orca: Do not alternate direction if Fill.fixed_angle is true
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
if (!this->dont_alternate_fill_direction) {
out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers);
}
} else {
// printf("Layer_ID undefined!\n");
}
+1
View File
@@ -141,6 +141,7 @@ public:
// BBS: all no overlap expolygons in same layer
ExPolygons no_overlap_expolygons;
bool dont_alternate_fill_direction = false;
static float infill_anchor;
static float infill_anchor_max;
+13 -14
View File
@@ -3088,12 +3088,13 @@ bool FillRectilinear::fill_surface_trapezoidal(
case 0: // Grid / Trapezoidal
{
// Generate a non-crossing trapezoidal pattern to avoid overextrusion at intersections when `multiline > 1`.
// P1--P2
// / \
// P0/ \P3__P4
// P2--P3
// / \
// P0_P1/ \P4_
//
// P1x-P2x=P3x-P4x=d1
// P0y-P1y=P2y-P3y=d2
// P0xP1x=P4xP0x=d1/2
// P2xP3x=d1
// P1yP2y=P2yP3y=d2
const coord_t d2 = coord_t(0.5 * period - d1);
@@ -3113,11 +3114,11 @@ bool FillRectilinear::fill_surface_trapezoidal(
// Build complete rows from xmin to xmax
for (coord_t x = xmin; x < xmax; x += period) {
// Normal row
base_row_normal.points.emplace_back(Point(x, d1 / 2)); // P0
base_row_normal.points.emplace_back(Point(x + d1, d1 / 2)); // P1
base_row_normal.points.emplace_back(Point(x + d1 + d2, d1 / 2 + d2)); // P2
base_row_normal.points.emplace_back(Point(x + 2 * d1 + d2, d1 / 2 + d2)); // P3
base_row_normal.points.emplace_back(Point(x + 2 * d1 + 2 * d2, d1 / 2)); // P4
base_row_normal.points.emplace_back(Point(x, d1 / 2)); // P0
base_row_normal.points.emplace_back(Point(x + d1 / 2, d1 / 2)); // P1
base_row_normal.points.emplace_back(Point(x + d1 / 2 + d2, d1 / 2 + d2)); // P2
base_row_normal.points.emplace_back(Point(x + d1 / 2 + d2 + d1, d1 / 2 + d2)); // P3
base_row_normal.points.emplace_back(Point(x + period - d1 / 2, d1 / 2)); // P4
}
// Flipped row (mirrored vertically)
@@ -3150,8 +3151,6 @@ bool FillRectilinear::fill_surface_trapezoidal(
for (Polyline& pl : polylines) {
for (Point& p : pl.points) {
std::swap(p.x(), p.y());
p.x() += d1 / 2;
p.y() -= d1 / 2;
}
}
}
@@ -3165,8 +3164,8 @@ bool FillRectilinear::fill_surface_trapezoidal(
// / \
// P0/ \P3_P4
// ----------------
// P1x-P2x=P3x-P4x=d2
// P0y-P1y=P2y-P3y=h-2d1
// P1xP2x=P3xP4x=d2
// P0yP1y=P2yP3y=h-2d1
//
// Triangular pattern density adjustment:
+144 -57
View File
@@ -1,5 +1,6 @@
#include "BoundingBox.hpp"
#include "Config.hpp"
#include "GCodeWriter.hpp"
#include "Polygon.hpp"
#include "PrintConfig.hpp"
#include "libslic3r.h"
@@ -23,10 +24,12 @@
#include "Time.hpp"
#include "GCode/ExtrusionProcessor.hpp"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstdlib>
#include <chrono>
#include <iostream>
#include <iterator>
#include <math.h>
#include <stdlib.h>
#include <string>
@@ -5687,12 +5690,12 @@ void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids)
void GCode::set_origin(const Vec2d &pointf)
{
// if origin increases (goes towards right), last_pos decreases because it goes towards left
const Point translate(
const Point3 translate(
scale_(m_origin(0) - pointf(0)),
scale_(m_origin(1) - pointf(1))
);
m_last_pos += translate;
m_wipe.path.translate(translate);
m_wipe.path.translate(translate.to_point());
m_origin = pointf;
}
@@ -5769,11 +5772,15 @@ static std::unique_ptr<EdgeGrid::Grid> calculate_layer_edge_grid(const Layer& la
return out;
}
std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters, const Point* start_point)
std::string GCode::extrude_loop(const ExtrusionLoop& loop_ref,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters,
const Point* start_point)
{
// get a copy; don't modify the orientation of the original loop object otherwise
// next copies (if any) would not detect the correct orientation
ExtrusionLoop loop = loop_ref;
bool is_hole = (loop.loop_role() & elrHole) == elrHole;
@@ -5848,13 +5855,14 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
const double nozzle_diam = nozzle_diameter;
// note: previous & next are inverted to extrude "in the opposite direction, and we are "rewinding"
Point previous_point = paths.front().polyline.points[1];
Point current_point = paths.front().polyline.points.front();
Point next_point = paths.back().polyline.points.back();
Point previous_point = Point(paths.front().polyline.points[1].x(), paths.front().polyline.points[1].y());
Point current_point = Point(paths.front().polyline.points.front().x(), paths.front().polyline.points.front().y());
Point next_point = Point(paths.back().polyline.points.back().x(), paths.back().polyline.points.back().y());
// can happen if seam_gap is null
if (next_point == current_point) {
next_point = paths.back().polyline.points[paths.back().polyline.points.size() - 2];
const Point3 &p3 = paths.back().polyline.points[paths.back().polyline.points.size() - 2];
next_point = Point(p3.x(), p3.y());
}
Point a = next_point; // second point
@@ -5901,7 +5909,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// inside the model
if(discoveredTouchingLines > 1){
// use extrude instead of travel_to_xy to trigger the unretract
ExtrusionPath fake_path_wipe(Polyline{pt, current_point}, paths.front());
ExtrusionPath fake_path_wipe(Polyline3(Points3{Point3(pt), Point3(current_point)}), paths.front());
fake_path_wipe.set_force_no_extrusion(true);
fake_path_wipe.mm3_per_mm = 0;
//fake_path_wipe.set_extrusion_role(erExternalPerimeter);
@@ -5937,7 +5945,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (!enable_seam_slope) {
for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) {
gcode += this->_extrude(*path, description, speed_for_path(*path));
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed
// Orca: Adaptive PA - dont adapt PA after the first multipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path
// in the first extrude move
// TODO: testing is needed with slope seams and adaptive PA.
@@ -5995,10 +6003,12 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
for (ExtrusionPath &path : paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
m_wipe.path.last_point() == path.first_point())
m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
else
m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
// Convert Points3 to Points
for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
m_wipe.path.append(Point(it->x(), it->y()));
} else
m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
}
@@ -6008,8 +6018,10 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// the side depends on the original winding order of the polygon (inwards for contours, outwards for holes)
//FIXME improve the algorithm in case the loop is tiny.
//FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query).
Point a = paths.front().polyline.points[1]; // second point
Point b = *(paths.back().polyline.points.end()-3); // second to last point
const Point3 &a3 = paths.front().polyline.points[1]; // second point
Point a = Point(a3.x(), a3.y());
const Point3 &b3 = *(paths.back().polyline.points.end()-3); // second to last point
Point b = Point(b3.x(), b3.y());
if (is_hole == loop.is_counter_clockwise()) {
// swap points
Point c = a; a = b; b = c;
@@ -6023,8 +6035,8 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
// create the destination point along the first segment and rotate it
// we make sure we don't exceed the segment length because we don't know
// the rotation of the second segment so we might cross the object boundary
Vec2d p1 = paths.front().polyline.points.front().cast<double>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>();
Vec2d p1 = paths.front().polyline.points.front().cast<double>().head<2>();
Vec2d p2 = paths.front().polyline.points[1].cast<double>().head<2>();
Vec2d v = p2 - p1;
double nd = scale_(EXTRUDER_CONFIG(nozzle_diameter));
double l2 = v.squaredNorm();
@@ -6036,19 +6048,20 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou
if (nd * nd < l2)
pt = (p1 + threshold * v * (nd / sqrt(l2))).cast<coord_t>();
//Point pt = ((nd * nd >= l2) ? (p1+v*0.4): (p1 + 0.2 * v * (nd / sqrt(l2)))).cast<coord_t>();
pt.rotate(angle, paths.front().polyline.points.front());
const Point3 &center3 = paths.front().polyline.points.front();
pt.rotate(angle, Point(center3.x(), center3.y()));
// generate the travel move
gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0,"move inwards before travel",true);
gcode += m_writer.extrude_to_xy(this->point_to_gcode(pt), 0, "move inwards before travel", true);
}
return gcode;
}
std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string description, double speed)
std::string GCode::extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description, double speed)
{
// extrude along the path
std::string gcode;
//Orca: calculate multipath average mm3_per_mm value over the length of the path.
//This is used for adaptive PA
m_multi_flow_segment_path_pa_set = false; // always emit PA on the first path of the multi-path
@@ -6065,8 +6078,8 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
if (total_multipath_length > 0.0)
m_multi_flow_segment_path_average_mm3_per_mm = weighted_sum_mm3_per_mm / total_multipath_length;
// Orca: end of multipath average mm3_per_mm value calculation
for (ExtrusionPath path : multipath.paths){
for (const ExtrusionPath &path : multipath.paths){
gcode += this->_extrude(path, description, speed);
// Orca: Adaptive PA - dont adapt PA after the first pultipath extrusion is completed
// as we have already set the PA value to the average flow over the totality of the path
@@ -6077,13 +6090,15 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
// BBS
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = Polyline();
for (ExtrusionPath &path : multipath.paths) {
for (const ExtrusionPath &path : multipath.paths) {
//BBS: Don't need to save duplicated point into wipe path
if (!m_wipe.path.empty() && !path.empty() &&
m_wipe.path.last_point() == path.first_point())
m_wipe.path.append(path.polyline.points.begin() + 1, path.polyline.points.end());
else
m_wipe.path.append(path.polyline); // TODO: don't limit wipe to last path
m_wipe.path.last_point() == Point(path.first_point().x(), path.first_point().y())) {
// Convert Points3 to Points
for (auto it = path.polyline.points.begin() + 1; it != path.polyline.points.end(); ++it)
m_wipe.path.append(Point(it->x(), it->y()));
} else
m_wipe.path.append(path.polyline.to_polyline()); // TODO: don't limit wipe to last path
}
m_wipe.path.reverse();
}
@@ -6091,7 +6106,10 @@ std::string GCode::extrude_multi_path(ExtrusionMultiPath multipath, std::string
return gcode;
}
std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string description, double speed, const ExtrusionEntitiesPtr& region_perimeters)
std::string GCode::extrude_entity(const ExtrusionEntity& entity,
const std::string& description,
double speed,
const ExtrusionEntitiesPtr& region_perimeters)
{
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(&entity))
return this->extrude_path(*path, description, speed);
@@ -6104,7 +6122,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return "";
}
std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)
std::string GCode::extrude_path(const ExtrusionPath& path, const std::string& description, double speed)
{
// Orca: Reset average multipath flow as this is a single line, single extrude volumetric speed path
m_multi_flow_segment_path_pa_set = false;
@@ -6112,17 +6130,17 @@ std::string GCode::extrude_path(ExtrusionPath path, std::string description, dou
// description += ExtrusionEntity::role_to_string(path.role());
std::string gcode = this->_extrude(path, description, speed);
if (m_wipe.enable && FILAMENT_CONFIG(wipe)) {
m_wipe.path = path.polyline;
m_wipe.path = path.polyline.to_polyline();
if (is_tree(this->config().support_type) && (path.role() == erSupportMaterial || path.role() == erSupportMaterialInterface || path.role() == erSupportTransition)) {
if ((m_wipe.path.first_point() - m_wipe.path.last_point()).cast<double>().norm() > scale_(0.2)) {
double min_dist = scale_(0.2);
int i = 0;
for (; i < path.polyline.points.size(); i++) {
double dist = (path.polyline.points[i] - path.last_point()).cast<double>().norm();
double dist = (path.polyline.points[i] - path.last_point3()).cast<double>().norm();
if (dist < min_dist) min_dist = dist;
if (min_dist < scale_(0.2) && dist > min_dist) break;
}
m_wipe.path = Polyline(Points(path.polyline.points.begin() + i - 1, path.polyline.points.end()));
m_wipe.path = Polyline3(Points3(path.polyline.points.begin() + i - 1, path.polyline.points.end())).to_polyline();
}
} else
m_wipe.path.reverse();
@@ -6165,11 +6183,11 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
extrusions.emplace_back(ee);
if (! extrusions.empty()) {
m_config.apply(print.get_print_region(&region - &by_region.front()).config());
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
for (const ExtrusionEntity *fill : extrusions) {
auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos.to_point()).entities)
gcode += this->extrude_entity(*ee, extrusion_name);
} else
gcode += this->extrude_entity(*fill, extrusion_name);
@@ -6200,7 +6218,7 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
if (extrusions.empty())
return gcode;
chain_and_reorder_extrusion_entities(extrusions, &m_last_pos);
chain_and_reorder_extrusion_entities(extrusions, m_last_pos.to_point());
const double support_speed = m_config.support_speed.value;
const double support_interface_speed = m_config.get_abs_value("support_interface_speed");
@@ -6366,14 +6384,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// Move to first point of extrusion path
// path is 2D. But in slope lift case, lift z is done in travel_to function.
// Add m_need_change_layer_lift_z when change_layer in case of no lift if m_last_pos is equal to path.first_point() by chance
if (!m_last_pos_defined || m_last_pos != path.first_point() || m_need_change_layer_lift_z || slope_need_z_travel) {
Point first_point = path.first_point();
if (!m_last_pos_defined || m_last_pos.to_point() != first_point || m_need_change_layer_lift_z || slope_need_z_travel) {
const bool _last_pos_undefined = !m_last_pos_defined;
gcode += this->travel_to(
path.first_point(),
path.role(),
"move to first " + description + " point",
sloped == nullptr ? DBL_MAX : get_sloped_z(sloped->slope_begin.z_ratio)
);
double z = DBL_MAX;
if (sloped != nullptr) {
z = get_sloped_z(sloped->slope_begin.z_ratio);
} else if (path.z_contoured && !path.polyline.lines().empty()) {
z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
}
gcode += this->travel_to(first_point, path.role(), "move to first " + description + " point", z);
m_need_change_layer_lift_z = false;
// Orca: ensure Z matches planned layer height
if (!slope_need_z_travel && (_last_pos_undefined || m_need_change_layer_lift_z)) {
const std::string z_sync_comment = _last_pos_undefined ?
@@ -6383,6 +6407,19 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
m_need_change_layer_lift_z = false;
}
if (path.z_contoured && !path.polyline.lines().empty()) {
double current_z = m_writer.get_position().z();
double first_z = unscale_(path.polyline.lines().begin()->a.z()) + m_nominal_z;
if (GCodeFormatter::quantize_xyzf(first_z) != GCodeFormatter::quantize_xyzf(current_z)) {
gcode += m_writer.travel_to_z(first_z, "set Z for contouring", true);
}
}
if (!path.z_contoured && sloped == nullptr) {
double current_z = m_writer.get_position().z();
if (GCodeFormatter::quantize_xyzf(current_z) != GCodeFormatter::quantize_xyzf(m_nominal_z)) {
gcode += this->writer().travel_to_z(m_nominal_z, "reset Z after contouring", true);
}
}
// if needed, write the gcode_label_objects_end then gcode_label_objects_start
// should be already done by travel_to, but just in case
@@ -6553,7 +6590,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
speed = is_perimeter(path.role()) ? m_config.get_abs_value("initial_layer_speed") :
m_config.get_abs_value("initial_layer_infill_speed");
}
} else if (m_config.slow_down_layers > 1 && !m_config.raft_layers > 0) {
} else if (m_config.slow_down_layers > 1 && m_config.raft_layers == 0) {
if (_layer > 0 && _layer < m_config.slow_down_layers) {
const auto first_layer_speed =
@@ -6969,10 +7006,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
}
// BBS: use G1 if not enable arc fitting or has no arc fitting result or in spiral_mode mode or we are doing sloped extrusion
// Attention: G2 and G3 is not supported in spiral_mode mode
if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr) {
if (!m_config.enable_arc_fitting || path.polyline.fitting_result.empty() || m_config.spiral_mode || sloped != nullptr || path.z_contoured) {
double path_length = 0.;
double total_length = sloped == nullptr ? 0. : path.polyline.length() * SCALING_FACTOR;
for (const Line& line : path.polyline.lines()) {
double saved_z = m_writer.get_position().z();
for (const Line3& line : path.polyline.lines()) {
std::string tempDescription = description;
const double line_length = line.length() * SCALING_FACTOR;
if (line_length < EPSILON)
@@ -6987,16 +7026,35 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
}
}
if (sloped == nullptr) {
if (path.z_contoured) {
// ZAA: Z anti-aliased extrusion with variable Z per point
Vec2d dest2d = this->point_to_gcode(line.b.to_point());
coordf_t z_diff = unscale_(line.b.z());
double extrusion_ratio = 1;
if (path.role() != erIroning) {
extrusion_ratio = (path.height + z_diff) / path.height;
}
double e = dE * extrusion_ratio;
double z = m_nominal_z + z_diff;
if (z < 0.1) {
throw RuntimeError("GCode: very low z");
}
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} else if (sloped == nullptr) {
// Normal extrusion
gcode += m_writer.extrude_to_xy(
this->point_to_gcode(line.b),
this->point_to_gcode(line.b.to_point()),
dE,
GCodeWriter::full_gcode_comment ? tempDescription : "", path.is_force_no_extrusion());
} else {
// Sloped extrusion
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
Vec2d dest2d = this->point_to_gcode(line.b);
Vec2d dest2d = this->point_to_gcode(line.b.to_point());
Vec3d dest3d(dest2d(0), dest2d(1), get_sloped_z(z_ratio));
gcode += m_writer.extrude_to_xyz(
dest3d,
@@ -7015,7 +7073,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
size_t end_index = fitting_result[fitting_index].end_point_index;
for (size_t point_index = start_index + 1; point_index < end_index + 1; point_index++) {
tempDescription = description;
const Line line = Line(path.polyline.points[point_index - 1], path.polyline.points[point_index]);
const Line line = Line(path.polyline.points[point_index - 1].to_point(), path.polyline.points[point_index].to_point());
const double line_length = line.length() * SCALING_FACTOR;
if (line_length < EPSILON)
continue;
@@ -7073,14 +7131,14 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double total_length = 0;
if (sloped != nullptr) {
// Calculate total extrusion length
Points p;
Points3 p;
p.reserve(new_points.size());
std::transform(new_points.begin(), new_points.end(), std::back_inserter(p), [](const ProcessedPoint& pp) { return pp.p; });
Polyline l(p);
Polyline3 l(p);
total_length = l.length() * SCALING_FACTOR;
}
gcode += m_writer.set_speed(last_set_speed, "", comment);
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
Vec3d prev = this->point_to_gcode_quantized(new_points[0].p);
bool pre_fan_enabled = false;
bool cur_fan_enabled = false;
if( m_enable_cooling_markers && enable_overhang_bridge_fan)
@@ -7094,7 +7152,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
std::string tempDescription = description;
const ProcessedPoint &processed_point = new_points[i];
const ProcessedPoint &pre_processed_point = new_points[i-1];
Vec2d p = this->point_to_gcode_quantized(processed_point.p);
Vec3d p = this->point_to_gcode_quantized(processed_point.p);
if (m_enable_cooling_markers) {
if (enable_overhang_bridge_fan) {
cur_fan_enabled = check_overhang_fan(processed_point.overlap, path.role());
@@ -7176,9 +7234,26 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
tempDescription += Slic3r::format(" | Old Flow Value: %0.5f Length: %0.5f",oldE, line_length);
}
}
if (sloped == nullptr) {
if (path.z_contoured) {
Vec2d dest2d = p.head<2>();
coordf_t z_diff = unscale_(processed_point.p.z());
double extrusion_ratio = 1;
if (path.role() != erIroning) {
extrusion_ratio = (path.height + z_diff) / path.height;
}
double e = dE * extrusion_ratio;
double z = m_nominal_z + z_diff;
if (z < 0.1) {
throw RuntimeError("GCode: very low z");
}
gcode += m_writer.extrude_to_xyz(Vec3d(dest2d.x(), dest2d.y(), z), e,
GCodeWriter::full_gcode_comment ? tempDescription : "");
} else if (sloped == nullptr) {
// Normal extrusion
gcode += m_writer.extrude_to_xy(p, dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
gcode += m_writer.extrude_to_xy(p.head<2>(), dE, GCodeWriter::full_gcode_comment ? tempDescription : "");
} else {
// Sloped extrusion
const auto [z_ratio, e_ratio] = sloped->interpolate(path_length / total_length);
@@ -8080,6 +8155,13 @@ Vec2d GCode::point_to_gcode(const Point &point) const
return unscale(point) + m_origin - extruder_offset;
}
Vec3d GCode::point_to_gcode(const Point3& point) const
{
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
Vec2d xy = unscale(point.to_point()) + m_origin - extruder_offset;
return Vec3d(xy.x(), xy.y(), unscale_(point.z()));
}
// convert a model-space scaled point into G-code coordinates
Point GCode::gcode_to_point(const Vec2d &point) const
{
@@ -8097,6 +8179,11 @@ Vec2d GCode::point_to_gcode_quantized(const Point& point) const
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
Vec3d GCode::point_to_gcode_quantized(const Point3& point) const
{
Vec3d p = this->point_to_gcode(point);
return {GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()), GCodeFormatter::quantize_xyzf(p.z())};
}
// Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed
// during infill/perimeter wiping, or normally (depends on wiping_entities parameter)
+18 -8
View File
@@ -221,10 +221,12 @@ public:
const Vec2d& origin() const { return m_origin; }
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
Point last_pos() const { return m_last_pos.to_point(); }
Vec2d point_to_gcode(const Point &point) const;
Vec3d point_to_gcode(const Point3& point) const;
Point gcode_to_point(const Vec2d &point) const;
Vec2d point_to_gcode_quantized(const Point& point) const;
Vec3d point_to_gcode_quantized(const Point3& point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
@@ -383,7 +385,8 @@ private:
void check_placeholder_parser_failed();
size_t get_extruder_id(unsigned int filament_id) const;
void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; }
void set_last_pos(const Point &pos) { m_last_pos = Point3(pos, 0); m_last_pos_defined = true; }
void set_last_pos(const Point3 &pos) { m_last_pos = pos; m_last_pos_defined = true; }
bool last_pos_defined() const { return m_last_pos_defined; }
void set_extruders(const std::vector<unsigned int> &extruder_ids);
std::string preamble();
@@ -391,13 +394,20 @@ private:
std::string change_layer(coordf_t print_z);
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
// should be executed
std::string extrude_entity(const ExtrusionEntity &entity, std::string description = "", double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr());
std::string extrude_entity(const ExtrusionEntity& entity,
const std::string& description = "",
double speed = -1.,
const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr());
// Orca: pass the complete collection of region perimeters to the extrude loop to check whether the wipe before external loop
// should be executed
std::string extrude_loop(ExtrusionLoop loop, std::string description, double speed = -1., const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(), const Point* start_point = nullptr);
std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.);
std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.);
std::string extrude_loop(const ExtrusionLoop& loop,
const std::string& description,
double speed = -1.,
const ExtrusionEntitiesPtr& region_perimeters = ExtrusionEntitiesPtr(),
const Point* start_point = nullptr);
std::string extrude_multi_path(const ExtrusionMultiPath& multipath, const std::string& description = "", double speed = -1.);
std::string extrude_path(const ExtrusionPath& path, const std::string& description = "", double speed = -1.);
// Orca: Adaptive PA variables
// Used for adaptive PA when extruding paths with multiple, varying flow segments.
// This contains the sum of the mm3_per_mm values weighted by the length of each path segment.
@@ -584,7 +594,7 @@ private:
std::map<std::string, std::vector<std::string>> m_placeholder_error_messages;
#endif
Point m_last_pos;
Point3 m_last_pos;
bool m_last_pos_defined;
std::unique_ptr<CoolingBuffer> m_cooling_buffer;
+1 -1
View File
@@ -87,7 +87,7 @@ public:
for (int i = b; i < e; ++i) {
for (const ExtrusionPath &path : _piles[i].paths) {
if (path.is_force_no_extrusion() == false) {
Polyline check_polyline = path.polyline;
Polyline check_polyline = path.polyline.to_polyline();
check_polyline.translate(_offset);
Lines tmpLines = check_polyline.lines();
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
+82 -81
View File
@@ -27,19 +27,19 @@
namespace Slic3r {
struct ExtendedPoint
template<int Dim> struct ExtendedPoint
{
Vec2d position;
Eigen::Matrix<double, Dim, 1, Eigen::DontAlign> position;
float distance;
float curvature;
};
template<bool SCALED_INPUT, bool ADD_INTERSECTIONS, bool PREV_LAYER_BOUNDARY_OFFSET, bool SIGNED_DISTANCE, typename POINTS, typename L>
std::vector<ExtendedPoint> estimate_points_properties(const POINTS &input_points,
const AABBTreeLines::LinesDistancer<L> &unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -1.0f)
std::vector<ExtendedPoint<L::Dim>> estimate_points_properties(const POINTS& input_points,
const AABBTreeLines::LinesDistancer<L>& unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
float min_distance = -1.0f)
{
bool looped = input_points.front() == input_points.back();
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
@@ -78,35 +78,36 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
double min_spacing = flow_width*0.25;
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
using Vec = Eigen::Matrix<double, L::Dim, 1, Eigen::DontAlign>;
if (input_points.empty())
return {};
float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f;
auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
auto maybe_unscale = [](const P& p) -> Vec { return SCALED_INPUT ? unscaled(p) : p.template cast<double>(); };
std::vector<ExtendedPoint> points;
std::vector<ExtendedPoint<L::Dim>> points;
points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1));
{
ExtendedPoint start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
ExtendedPoint<L::Dim> start_point{maybe_unscale(input_points.front())};
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
start_point.position.template cast<AABBScalar>());
start_point.distance = distance + boundary_offset;
points.push_back(start_point);
}
for (size_t i = 1; i < input_points.size(); i++) {
ExtendedPoint next_point{maybe_unscale(input_points[i])};
ExtendedPoint<L::Dim> next_point{maybe_unscale(input_points[i])};
auto [distance, nearest_line,
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.template cast<AABBScalar>());
next_point.distance = distance + boundary_offset;
// Intersection handling
if (ADD_INTERSECTIONS &&
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
const ExtendedPoint &prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
const ExtendedPoint<L::Dim>& prev_point = points.back();
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
L{prev_point.position.template cast<AABBScalar>(), next_point.position.template cast<AABBScalar>()});
for (const auto &intersection : intersections) {
ExtendedPoint p{};
ExtendedPoint<L::Dim> p{};
p.position = intersection.first.template cast<double>();
p.distance = boundary_offset;
// ORCA: Filter out points that are introduced at intersections if their distance from the previous or next point is not meaningful
@@ -121,12 +122,12 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
// Segmentation handling
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector<ExtendedPoint> new_points;
std::vector<ExtendedPoint<L::Dim>> new_points;
new_points.reserve(points.size() * 2);
new_points.push_back(points.front());
for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) {
const ExtendedPoint &curr = points[point_idx];
const ExtendedPoint &next = points[point_idx + 1];
const ExtendedPoint<L::Dim>& curr = points[point_idx];
const ExtendedPoint<L::Dim>& next = points[point_idx + 1];
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
@@ -144,10 +145,10 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
double t1 = std::max(a0, a1);
if (t0 < 1.0) {
Vec2d p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l,
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
ExtendedPoint new_p{};
Vec p0 = curr.position + t0 * (next.position - curr.position);
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
p0.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = p0;
new_p.distance = float(p0_dist + boundary_offset);
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
@@ -161,10 +162,10 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
if (t1 > 0.0) {
Vec2d p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l,
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
ExtendedPoint new_p{};
Vec p1 = curr.position + t1 * (next.position - curr.position);
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(
p1.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = p1;
new_p.distance = float(p1_dist + boundary_offset);
// ORCA: only create a new point in the path if the new point overhang distance will be used to generate a speed change
@@ -186,21 +187,21 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
// Maximum line length handling
if (max_line_length > 0) {
std::vector<ExtendedPoint> new_points;
std::vector<ExtendedPoint<L::Dim>> new_points;
new_points.reserve(points.size() * 2);
{
for (size_t i = 0; i + 1 < points.size(); i++) {
const ExtendedPoint &curr = points[i];
const ExtendedPoint &next = points[i + 1];
const ExtendedPoint<L::Dim>& curr = points[i];
const ExtendedPoint<L::Dim>& next = points[i + 1];
new_points.push_back(curr);
double len = (next.position - curr.position).squaredNorm();
double t = sqrt((max_line_length * max_line_length) / len);
size_t new_point_count = 1.0 / t;
for (size_t j = 1; j < new_point_count + 1; j++) {
Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t);
Vec pos = curr.position * (1.0 - j * t) + next.position * (j * t);
auto [p_dist, p_near_l,
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.cast<AABBScalar>());
ExtendedPoint new_p{};
p_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(pos.template cast<AABBScalar>());
ExtendedPoint<L::Dim> new_p{};
new_p.position = pos;
new_p.distance = float(p_dist + boundary_offset);
@@ -219,8 +220,8 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
float accumulated_distance = 0;
std::vector<float> distances_for_curvature(points.size());
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
const ExtendedPoint &a = points[point_idx];
const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())];
const ExtendedPoint<L::Dim>& a = points[point_idx];
const ExtendedPoint<L::Dim>& b = points[get_prev_index(point_idx, points.size())];
distances_for_curvature[point_idx] = (b.position - a.position).norm();
accumulated_distance += distances_for_curvature[point_idx];
@@ -229,9 +230,9 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
if (accumulated_distance > EPSILON)
for (float window_size : {3.0f, 9.0f, 16.0f}) {
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
ExtendedPoint &current = points[point_idx];
ExtendedPoint<L::Dim>& current = points[point_idx];
Vec2d back_position = current.position;
Vec back_position = current.position;
{
size_t back_point_index = point_idx;
float dist_backwards = 0;
@@ -251,7 +252,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
Vec2d front_position = current.position;
Vec front_position = current.position;
{
size_t front_point_index = point_idx;
float dist_forwards = 0;
@@ -271,7 +272,9 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
}
}
float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size;
float new_curvature = angle((current.position - back_position).template head<2>(),
(front_position - current.position).template head<2>()) /
window_size;
if (abs(current.curvature) < abs(new_curvature)) {
current.curvature = new_curvature;
}
@@ -283,15 +286,15 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
struct ProcessedPoint
{
Point p;
Point3 p;
float speed = 1.0f;
float overlap = 1.0f;
};
class ExtrusionQualityEstimator
{
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> prev_layer_boundaries;
std::unordered_map<const PrintObject*, AABBTreeLines::LinesDistancer<Linef3>> next_layer_boundaries;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
const PrintObject *current_object;
@@ -304,7 +307,7 @@ public:
if (layer == nullptr) return;
const PrintObject *object = obj;
prev_layer_boundaries[object] = next_layer_boundaries[object];
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef3>{to_unscaled_linesf3(layer->lslices)};
prev_curled_extrusions[object] = next_curled_extrusions[object];
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
}
@@ -359,56 +362,54 @@ public:
smallest_distance_with_lower_speed=-1.f;
// Orca: Pass to the point properties estimator the smallest ovehang distance that triggers a slowdown (smallest_distance_with_lower_speed)
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>
(path.polyline.points,
prev_layer_boundaries[current_object],
path.width,
-1,
smallest_distance_with_lower_speed);
std::vector<ExtendedPoint<3>> extended_points =
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
smallest_distance_with_lower_speed);
const auto width_inv = 1.0f / path.width;
std::vector<ProcessedPoint> processed_points;
processed_points.reserve(extended_points.size());
for (size_t i = 0; i < extended_points.size(); i++) {
const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
const ExtendedPoint<3>& curr = extended_points[i];
const ExtendedPoint<3>& next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
float artificial_distance_to_curled_lines = 0.0;
if(slowdown_for_curled_edges) {
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
const double dist_limit = 10.0 * path.width;
{
Vec2d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
Vec3d middle = 0.5 * (curr.position + next.position);
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle),
scale_(dist_limit));
if (!line_indices.empty()) {
double len = (next.position - curr.position).norm();
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
if (len > 2) {
Vec2d dir = Vec2d(next.position - curr.position) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Vec2d dir = Vec2d(next.position.head<2>() - curr.position.head<2>()) / len;
Vec2d right = Vec2d(-dir.y(), dir.x());
Polygon box_of_influence = {
scaled(Vec2d(curr.position + right * dist_limit)),
scaled(Vec2d(next.position + right * dist_limit)),
scaled(Vec2d(next.position - right * dist_limit)),
scaled(Vec2d(curr.position - right * dist_limit)),
};
Polygon box_of_influence = {
scaled(Vec2d(curr.position.head<2>() + right * dist_limit)),
scaled(Vec2d(next.position.head<2>() + right * dist_limit)),
scaled(Vec2d(next.position.head<2>() - right * dist_limit)),
scaled(Vec2d(curr.position.head<2>() - right * dist_limit)),
};
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
double projected_lengths_sum = 0;
for (size_t idx : line_indices) {
const CurledLine& line = prev_curled_extrusions[current_object].get_line(idx);
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
if (inside.empty())
continue;
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
projected_lengths_sum += projected_length;
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
}
if (projected_lengths_sum < 0.4 * len) {
line_indices.clear();
}
}
for (size_t idx : line_indices) {
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
@@ -418,9 +419,9 @@ public:
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
}
}
}
}
}
}
}
auto calculate_speed = [&speed_sections, &original_speed](float distance) {
float final_speed;
@@ -452,8 +453,8 @@ public:
}
float overlap = std::min(1 - (curr.distance+artificial_distance_to_curled_lines) * width_inv, 1 - (next.distance+artificial_distance_to_curled_lines) * width_inv);
processed_points.push_back({ scaled(curr.position), extrusion_speed, overlap });
processed_points.push_back({Point3(scaled(curr.position)), extrusion_speed, overlap});
}
return processed_points;
}
+3 -3
View File
@@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
{
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width)));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
@@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
{
BoundingBox bbox;
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline.to_polyline(), coord_t(scale_(0.5 * extrusion_path.width))));
BoundingBoxf bboxf;
if (! empty(bbox)) {
bboxf.min = unscale(bbox.min);
+2 -2
View File
@@ -1514,7 +1514,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop,
current.path_idx = next_idx_modulo(current.path_idx, loop.paths.size());
current.segment_idx = 0;
}
current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx];
current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx].to_point();
return current;
};
@@ -1527,7 +1527,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop,
size_t closest_perimeter_point_index = 0;
{ // local space for the closest_perimeter_point_index
Perimeter *closest_perimeter = nullptr;
ExtrusionLoop::ClosestPathPoint closest_point{0,0,loop.paths[0].polyline.points[0]};
ExtrusionLoop::ClosestPathPoint closest_point{0, 0, loop.paths[0].polyline.points[0].to_point()};
size_t points_count = std::accumulate(loop.paths.begin(), loop.paths.end(), 0, [](size_t acc,const ExtrusionPath& p) {
return acc + p.polyline.points.size();
});
+9 -1
View File
@@ -946,6 +946,11 @@ std::string GCodeWriter::extrude_arc_to_xy(const Vec2d& point, const Vec2d& cent
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment, bool force_no_extrusion)
{
// Check if Z actually changes (at export precision) before emitting it.
// ZAA sloped extrusions call this for every segment, but many consecutive
// segments share the same quantized Z — emitting it every time is redundant.
bool z_changed = (GCodeG1Formatter::quantize_xyzf(point(2)) != GCodeG1Formatter::quantize_xyzf(m_pos(2)));
m_pos = point;
m_lifted = 0;
if (!force_no_extrusion)
@@ -955,7 +960,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
Vec3d point_on_plate = { point(0) - m_x_offset, point(1) - m_y_offset, point(2) };
GCodeG1Formatter w;
w.emit_xyz(point_on_plate);
if (z_changed)
w.emit_xyz(point_on_plate);
else
w.emit_xy(Vec2d(point_on_plate.x(), point_on_plate.y()));
if (!force_no_extrusion)
w.emit_e(filament()->E());
//BBS
+5
View File
@@ -26,6 +26,10 @@ namespace FillLightning {
class Generator;
};
namespace sla {
class IndexedMesh;
};
class LayerRegion
{
public:
@@ -191,6 +195,7 @@ public:
FillAdaptive::Octree *support_fill_octree,
FillLightning::Generator* lightning_generator) const;
void make_ironing();
void make_contour_z(const sla::IndexedMesh &mesh);
void export_region_slices_to_svg(const char *path) const;
void export_region_fill_surfaces_to_svg(const char *path) const;
+1
View File
@@ -3,6 +3,7 @@
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "PerimeterGenerator.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "Surface.hpp"
#include "BoundingBox.hpp"
+50 -6
View File
@@ -224,17 +224,28 @@ using CurledLines = std::vector<CurledLine>;
class Line3
{
public:
Line3() : a(Vec3crd::Zero()), b(Vec3crd::Zero()) {}
Line3(const Vec3crd& _a, const Vec3crd& _b) : a(_a), b(_b) {}
Line3() : a(Point3()), b(Point3()) {}
Line3(const Point3& _a, const Point3& _b) : a(_a), b(_b) {}
// Backward compatibility with Vec3crd
Line3(const Vec3crd& _a, const Vec3crd& _b) : a(Point3(_a)), b(Point3(_b)) {}
double length() const { return (this->a - this->b).cast<double>().norm(); }
Vec3crd vector() const { return this->b - this->a; }
Point3 vector() const { Vec3crd v = this->b - this->a; return Point3(v.x(), v.y(), v.z()); }
Point3 midpoint() const { return Point3((this->a.x() + this->b.x()) / 2, (this->a.y() + this->b.y()) / 2, (this->a.z() + this->b.z()) / 2); }
Vec3crd a;
Vec3crd b;
// Convert to 2D line by dropping Z coordinate
Line to_line() const { return Line(this->a.to_point(), this->b.to_point()); }
static inline double distance_to_squared(const Point3& point, const Point3& a, const Point3& b)
{
return line_alg::distance_to_squared(Line3{a, b}, Vec<3, coord_t>{point});
}
Point3 a;
Point3 b;
static const constexpr int Dim = 3;
using Scalar = Vec3crd::Scalar;
using Scalar = coord_t;
};
class Linef
@@ -243,6 +254,10 @@ public:
Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {}
Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {}
Vec2d vector() const { return this->b - this->a; }
Vec2d unit_vector() const { return (length() == 0.0) ? Vec2d::Zero() : vector().normalized(); }
double length() const { return vector().norm(); }
Vec2d a;
Vec2d b;
@@ -256,6 +271,7 @@ class Linef3
public:
Linef3() : a(Vec3d::Zero()), b(Vec3d::Zero()) {}
Linef3(const Vec3d& _a, const Vec3d& _b) : a(_a), b(_b) {}
Linef3(const Vec2d& _a, const Vec2d& _b, double z) : a(Vec3d(_a.x(), _a.y(), z)), b(Vec3d(_b.x(), _b.y(), z)) {}
Vec3d intersect_plane(double z) const;
void scale(double factor) { this->a *= factor; this->b *= factor; }
@@ -263,6 +279,32 @@ public:
Vec3d unit_vector() const { return (length() == 0.0) ? Vec3d::Zero() : vector().normalized(); }
double length() const { return vector().norm(); }
double distance_to_infinite_squared(const Vec3d &point, Vec3d *closest_point) const {
const Vec3d v = this->b - this->a;
const Vec3d va = point - this->a;
const double l2 = v.squaredNorm();
if (l2 == 0.) {
// a == b case
*closest_point = this->a;
return va.squaredNorm();
}
// Consider the line extending the segment, parameterized as a + t (b - a).
// Find parameter value t of the projection of point onto the line.
const double t = va.dot(v) / l2;
*closest_point = this->a + t * v;
return (point - *closest_point).squaredNorm();
}
double distance_to_infinite_squared(const Vec3d &point) const {
Vec3d nearest_point;
return distance_to_infinite_squared(point, &nearest_point);
}
static inline double distance_to_infinite_squared(const Vec3d &point, const Vec3d &a, const Vec3d &b) {
Linef3 line{a, b};
return line.distance_to_infinite_squared(point);
}
Vec3d a;
Vec3d b;
@@ -270,6 +312,8 @@ public:
using Scalar = Vec3d::Scalar;
};
using Linesf3 = std::vector<Linef3>;
BoundingBox get_extents(const Lines &lines);
} // namespace Slic3r
+152 -18
View File
@@ -422,32 +422,23 @@ Points MultiPoint::concave_hull_2d(const Points& pts, const double tolerence)
return min_distance;
}
void MultiPoint3::translate(double x, double y)
void MultiPoint3::translate(const Point3& v)
{
for (Vec3crd &p : points) {
p(0) += coord_t(x);
p(1) += coord_t(y);
}
}
void MultiPoint3::translate(const Point& vector)
{
this->translate(vector(0), vector(1));
for (Point3& pt : points)
pt += v;
}
double MultiPoint3::length() const
{
double len = 0.0;
for (const Line3& line : this->lines())
len += line.length();
const Lines3& lines = this->lines();
double len = 0;
for (auto it = lines.cbegin(); it != lines.cend(); ++it) {
len += it->length();
}
return len;
}
BoundingBox3 MultiPoint3::bounding_box() const
{
return BoundingBox3(points);
}
BoundingBox3 MultiPoint3::bounding_box() const { return BoundingBox3(this->points); }
bool MultiPoint3::remove_duplicate_points()
{
@@ -471,6 +462,75 @@ bool MultiPoint3::remove_duplicate_points()
return false;
}
// Douglas-Peucker simplification for 3D points
Points3 MultiPoint3::_douglas_peucker(const Points3& pts, double tolerance)
{
Points3 result_pts;
double tolerance_sq = tolerance * tolerance;
if (!pts.empty()) {
const Point3* anchor = &pts.front();
size_t anchor_idx = 0;
const Point3* floater = &pts.back();
size_t floater_idx = pts.size() - 1;
result_pts.reserve(pts.size());
result_pts.emplace_back(*anchor);
if (anchor_idx != floater_idx) {
assert(pts.size() > 1);
std::vector<size_t> dpStack;
dpStack.reserve(pts.size());
dpStack.emplace_back(floater_idx);
for (;;) {
double max_dist_sq = 0.0;
size_t furthest_idx = anchor_idx;
// find point furthest from line seg created by (anchor, floater) and note it
for (size_t i = anchor_idx + 1; i < floater_idx; ++i) {
double dist_sq = Line3::distance_to_squared(pts[i], *anchor, *floater);
if (dist_sq > max_dist_sq) {
max_dist_sq = dist_sq;
furthest_idx = i;
}
}
// remove point if less than tolerance
if (max_dist_sq <= tolerance_sq) {
result_pts.emplace_back(*floater);
anchor_idx = floater_idx;
anchor = floater;
assert(dpStack.back() == floater_idx);
dpStack.pop_back();
if (dpStack.empty())
break;
floater_idx = dpStack.back();
} else {
floater_idx = furthest_idx;
dpStack.emplace_back(floater_idx);
}
floater = &pts[floater_idx];
}
}
assert(result_pts.front() == pts.front());
assert(result_pts.back() == pts.back());
#if 0
{
static int iRun = 0;
BoundingBox bbox(pts);
BoundingBox bbox2(result_pts);
bbox.merge(bbox2);
SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
if (pts.front() == pts.back())
svg.draw(Polygon(pts), "black");
else
svg.draw(Polyline(pts), "black");
if (result_pts.front() == result_pts.back())
svg.draw(Polygon(result_pts), "green", scale_(0.1));
else
svg.draw(Polyline(result_pts), "green", scale_(0.1));
}
#endif
}
return result_pts;
}
BoundingBox get_extents(const MultiPoint &mp)
{
return BoundingBox(mp.points);
@@ -514,4 +574,78 @@ void MultiPoint::symmetric_y(const coord_t &x_axis)
}
}
// MultiPoint3 implementations
void MultiPoint3::rotate(double cos_angle, double sin_angle)
{
for (Point3 &pt : this->points) {
double cur_x = double(pt(0));
double cur_y = double(pt(1));
pt(0) = coord_t(round(cos_angle * cur_x - sin_angle * cur_y));
pt(1) = coord_t(round(cos_angle * cur_y + sin_angle * cur_x));
// Keep Z unchanged
}
}
void MultiPoint3::rotate(double angle, const Point3 &center)
{
double s = sin(angle);
double c = cos(angle);
for (Point3 &pt : points) {
Vec3crd v(pt - center);
pt(0) = (coord_t)round(double(center(0)) + c * v[0] - s * v[1]);
pt(1) = (coord_t)round(double(center(1)) + c * v[1] + s * v[0]);
// Keep Z unchanged from original point
}
}
int MultiPoint3::find_point(const Point &point) const
{
for (const Point3 &pt : this->points)
if (pt.to_point() == point)
return int(&pt - &this->points.front());
return -1; // not found
}
int MultiPoint3::find_point(const Point &point, double scaled_epsilon) const
{
if (scaled_epsilon == 0) return this->find_point(point);
auto dist2_min = std::numeric_limits<double>::max();
auto eps2 = scaled_epsilon * scaled_epsilon;
int idx_min = -1;
for (const Point3 &pt : this->points) {
double d2 = (pt.to_point() - point).cast<double>().squaredNorm();
if (d2 < dist2_min) {
idx_min = int(&pt - &this->points.front());
dist2_min = d2;
}
}
return (dist2_min < eps2) ? idx_min : -1;
}
int MultiPoint3::find_point(const Point3 &point) const
{
for (const Point3 &pt : this->points)
if (pt == point)
return int(&pt - &this->points.front());
return -1; // not found
}
int MultiPoint3::find_point(const Point3 &point, double scaled_epsilon) const
{
if (scaled_epsilon == 0) return this->find_point(point);
auto dist2_min = std::numeric_limits<double>::max();
auto eps2 = scaled_epsilon * scaled_epsilon;
int idx_min = -1;
for (const Point3 &pt : this->points) {
double d2 = (pt - point).cast<double>().squaredNorm();
if (d2 < dist2_min) {
idx_min = int(&pt - &this->points.front());
dist2_min = d2;
}
}
return (dist2_min < eps2) ? idx_min : -1;
}
}
+35 -3
View File
@@ -119,18 +119,50 @@ class MultiPoint3
public:
Points3 points;
void append(const Vec3crd& point) { this->points.push_back(point); }
void append(const Point3& point) { this->points.push_back(point); }
void append(const Vec3crd& point) { this->points.push_back(Point3(point)); }
void append(const Points3::const_iterator& begin, const Points3::const_iterator& end)
{
this->points.insert(this->points.end(), begin, end);
}
void translate(double x, double y, double z = 0) { this->translate(Point3(coord_t(x), coord_t(y), coord_t(z))); }
void translate(const Point3& vector);
void reverse() { std::reverse(this->points.begin(), this->points.end()); }
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle);
void rotate(double angle, const Point3 &center);
Point3& first_point() { return this->points.front(); }
Point3& last_point() { return this->points.back(); }
const Point3& first_point() const { return this->points.front(); }
const Point3& last_point() const { return this->points.back(); }
size_t size() const { return this->points.size(); }
bool empty() const { return this->points.empty(); }
void clear() { this->points.clear(); }
auto begin() { return this->points.begin(); }
auto end() { return this->points.end(); }
auto begin() const { return this->points.begin(); }
auto end() const { return this->points.end(); }
void translate(double x, double y);
void translate(const Point& vector);
virtual Lines3 lines() const = 0;
double length() const;
bool is_valid() const { return this->points.size() >= 2; }
BoundingBox3 bounding_box() const;
// Find a point in the points array
int find_point(const Point &point) const;
int find_point(const Point &point, const double scaled_epsilon) const;
int find_point(const Point3 &point) const;
int find_point(const Point3 &point, const double scaled_epsilon) const;
// Remove exact duplicates, return true if any duplicate has been removed.
bool remove_duplicate_points();
// Douglas-Peucker simplification
static Points3 _douglas_peucker(const Points3 &points, double tolerance);
};
extern BoundingBox get_extents(const MultiPoint &mp);
+20 -11
View File
@@ -206,7 +206,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
if(paths.empty()) continue;
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
Point start_pt = Point(paths.front().first_point().x(), paths.front().first_point().y());
chain_and_reorder_extrusion_paths(paths, &start_pt);
} else {
if (overhangs_reverse && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
// Always reverse if detect overhang wall is not enabled
@@ -216,7 +217,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
ExtrusionPath path(role);
//BBS.
path.polyline = polygon.split_at_first_point();
path.polyline = Polyline3(polygon.split_at_first_point());
path.mm3_per_mm = extrusion_mm3_per_mm;
path.width = extrusion_width;
path.height = (float)perimeter_generator.layer_height;
@@ -441,7 +442,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
Polylines be_clipped;
for (const ExtrusionPath &p : it.second) {
be_clipped.emplace_back(std::move(p.polyline));
be_clipped.emplace_back(p.polyline.to_polyline());
}
BoundingBox extrusion_bboxs = get_extents(be_clipped);
@@ -475,11 +476,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
};
std::unordered_map<Point, PointInfo, PointHash> point_occurrence;
for (const ExtrusionPath& path : paths) {
++point_occurrence[path.polyline.first_point()].occurrence;
++point_occurrence[path.polyline.last_point()].occurrence;
Point first_p = path.polyline.first_point().to_point();
Point last_p = path.polyline.last_point().to_point();
++point_occurrence[first_p].occurrence;
++point_occurrence[last_p].occurrence;
if (path.role() == erOverhangPerimeter) {
point_occurrence[path.polyline.first_point()].is_overhang = true;
point_occurrence[path.polyline.last_point()].is_overhang = true;
point_occurrence[first_p].is_overhang = true;
point_occurrence[last_p].is_overhang = true;
}
}
@@ -674,11 +677,17 @@ bool paths_touch(const ExtrusionPath &path_one, const ExtrusionPath &path_two, d
{
AABBTreeLines::LinesDistancer<Line> lines_two{path_two.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_one.polyline.size(); pt_idx++) {
if (lines_two.distance_from_lines<false>(path_one.polyline.points[pt_idx]) < limit_distance) { return true; }
Point pt = path_one.polyline.points[pt_idx].to_point();
if (lines_two.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
}
AABBTreeLines::LinesDistancer<Line> lines_one{path_one.as_polyline().lines()};
for (size_t pt_idx = 0; pt_idx < path_two.polyline.size(); pt_idx++) {
if (lines_one.distance_from_lines<false>(path_two.polyline.points[pt_idx]) < limit_distance) { return true; }
Point pt = path_two.polyline.points[pt_idx].to_point();
if (lines_one.distance_from_lines<false>(pt) < limit_distance) {
return true;
}
}
return false;
}
@@ -1032,7 +1041,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
// polyline)
bool first_overhang_is_closed_and_anchored =
(overhang_region.front().first_point() == overhang_region.front().last_point() &&
!intersection_pl(overhang_region.front().polyline, optimized_lower_slices).empty());
!intersection_pl(overhang_region.front().polyline.to_polyline(), optimized_lower_slices).empty());
auto is_anchored = [&lower_layer_aabb_tree](const ExtrusionPath &path) {
return lower_layer_aabb_tree.distance_from_lines<true>(path.first_point()) <= 0 ||
@@ -1044,7 +1053,7 @@ std::tuple<std::vector<ExtrusionPaths>, Polygons> generate_extra_perimeters_over
size_t min_dist_idx = 0;
double min_dist = std::numeric_limits<double>::max();
for (size_t i = 0; i < overhang_region.front().polyline.size(); i++) {
Point p = overhang_region.front().polyline[i];
Point p = overhang_region.front().polyline.points[i].to_point();
if (double d = lower_layer_aabb_tree.distance_from_lines<true>(p) < min_dist) {
min_dist = d;
min_dist_idx = i;
+89
View File
@@ -1,6 +1,8 @@
#include "Point.hpp"
#include "Exception.hpp"
#include "Line.hpp"
#include "MultiPoint.hpp"
#include "Polyline.hpp"
#include "Int128.hpp"
#include "BoundingBox.hpp"
#include <algorithm>
@@ -257,4 +259,91 @@ int cross(const Vec2crd &v1, const Vec2crd &v2)
}
// Point3 utility functions for ZAA (Z Anti-Aliasing)
Polyline to_polyline(const Points &points) { return Polyline(points); }
Polyline3 to_polyline(const Points3 &points) { return Polyline3(points); }
Points to_points(const Points3 &points3) {
Points points2;
points2.reserve(points3.size());
for (const Point3 &pt : points3) {
points2.emplace_back(pt.to_point());
}
return points2;
}
Points3 to_points3(const Points& points)
{
Points3 points3;
points3.reserve(points.size());
for (const Point& pt : points) {
points3.emplace_back(pt);
}
return points3;
}
// Point3 method implementations
void Point3::rotate(double angle, const Point3 &center) {
Vec3crd diff = *this - center;
Point3 temp(diff.x(), diff.y(), diff.z());
temp.rotate(angle);
Vec3crd sum = temp + center;
*this = Point3(sum.x(), sum.y(), sum.z());
}
int Point3::nearest_point_index(const Points &points) const {
return this->to_point().nearest_point_index(points);
}
bool Point3::nearest_point(const Points &points, Point3* point) const {
Point pt2d;
bool result = this->to_point().nearest_point(points, &pt2d);
if (result && point) {
*point = Point3(pt2d, this->z());
}
return result;
}
double Point3::ccw(const Point3 &p1, const Point3 &p2) const {
return this->to_point().ccw(p1.to_point(), p2.to_point());
}
double Point3::ccw(const Line3 &line) const {
// Convert to 2D and use existing Point ccw implementation
Point a2d(line.a.x(), line.a.y());
Point b2d(line.b.x(), line.b.y());
return this->to_point().ccw(Line(a2d, b2d));
}
double Point3::ccw_angle(const Point3 &p1, const Point3 &p2) const {
return this->to_point().ccw_angle(p1.to_point(), p2.to_point());
}
Point3 Point3::projection_onto(const MultiPoint3 &poly) const {
// TODO: Implement proper 3D projection when MultiPoint3 conversion methods are ready
// For now, stub implementation
throw RuntimeError("Point3::projection_onto(MultiPoint3) not implemented yet");
return *this;
}
Point3 Point3::projection_onto(const Line3 &line) const {
// Project in 2D plane and interpolate Z
Point pt2d = this->to_point();
Point line_a(line.a.x(), line.a.y());
Point line_b(line.b.x(), line.b.y());
Line line2d(line_a, line_b);
Point proj2d = pt2d.projection_onto(line2d);
// Interpolate Z coordinate
double line_len = line.length();
if (line_len < EPSILON) {
return Point3(proj2d, line.a.z());
}
double dist_from_a = (proj2d - line_a).cast<double>().norm();
double t = dist_from_a / line_len;
t = std::clamp(t, 0.0, 1.0);
coord_t z = coord_t(line.a.z() + t * (line.b.z() - line.a.z()));
return Point3(proj2d, z);
}
}
+130 -1
View File
@@ -21,8 +21,13 @@ namespace Slic3r {
class BoundingBox;
class BoundingBoxf;
class Line;
class Line3;
class MultiPoint;
class MultiPoint3;
class Point;
class Point3;
class Polyline;
class Polyline3;
using Vector = Point;
// Base template for eigen derived vectors
@@ -57,7 +62,7 @@ using PointsAllocator = tbb::scalable_allocator<BaseType>;
using Points = std::vector<Point, PointsAllocator<Point>>;
using PointPtrs = std::vector<Point*>;
using PointConstPtrs = std::vector<const Point*>;
using Points3 = std::vector<Vec3crd>;
using Points3 = std::vector<Point3, PointsAllocator<Point3>>;
using Pointfs = std::vector<Vec2d>;
using Vec2ds = std::vector<Vec2d>;
using Pointf3s = std::vector<Vec3d>;
@@ -79,6 +84,12 @@ using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAli
using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
// Utility functions for Point/Polyline conversion
Polyline to_polyline(const Points &points);
Polyline3 to_polyline(const Points3 &points);
Points to_points(const Points3 &points);
Points3 to_points3(const Points& points);
// using ColorRGBA = std::array<float, 4>;
// I don't know why Eigen::Transform::Identity() return a const object...
template<int N, class T> Transform<N, T> identity() { return Transform<N, T>::Identity(); }
@@ -258,6 +269,118 @@ inline Point operator* (const Point& l, const double& r)
return { coord_t(l.x() * r), coord_t(l.y() * r) };
}
// Point3 class - 3D point with Z coordinate for non-planar printing (ZAA)
class Point3 : public Vec3crd {
public:
using coord_type = coord_t;
Point3() : Vec3crd(0, 0, 0) {}
Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(std::round(x)), coord_t(std::round(y)), coord_t(std::round(z))) {}
Point3(const Point3 &rhs) { *this = rhs; }
explicit Point3(const Vec2crd& vec2crd, coord_t z = 0) : Vec3crd(vec2crd.x(), vec2crd.y(), z) {}
explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {}
// This constructor allows you to construct Point from Eigen expressions
template<typename OtherDerived>
explicit Point3(const Eigen::MatrixBase<OtherDerived> &other) : Vec3crd(other) {}
static Point3 new_scale(coordf_t x, coordf_t y, coordf_t z) {
return Point3(coord_t(scale_(x)), coord_t(scale_(y)), coord_t(scale_(z)));
}
static Point3 new_scale(const Vec3d &v) {
return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z())));
}
static Point3 new_scale(const Vec3f &v) {
return Point3(coord_t(scale_(v.x())), coord_t(scale_(v.y())), coord_t(scale_(v.z())));
}
// Assignment operator for Eigen expressions
template<typename OtherDerived>
Point3& operator=(const Eigen::MatrixBase<OtherDerived> &other)
{
this->Vec3crd::operator=(other);
return *this;
}
Point3& operator+=(const Point3& rhs) { this->x() += rhs.x(); this->y() += rhs.y(); this->z() += rhs.z(); return *this; }
Point3& operator-=(const Point3& rhs) { this->x() -= rhs.x(); this->y() -= rhs.y(); this->z() -= rhs.z(); return *this; }
Point3& operator*=(const double &rhs) {
this->x() = coord_t(this->x() * rhs);
this->y() = coord_t(this->y() * rhs);
this->z() = coord_t(this->z() * rhs);
return *this;
}
Point3 operator*(const double &rhs) const { return Point3(this->x() * rhs, this->y() * rhs, this->z() * rhs); }
bool both_comp(const Point3 &rhs, const std::string& op) {
if (op == ">")
return this->x() > rhs.x() && this->y() > rhs.y();
else if (op == "<")
return this->x() < rhs.x() && this->y() < rhs.y();
return false;
}
bool any_comp(const Point3 &rhs, const std::string &op)
{
if (op == ">")
return this->x() > rhs.x() || this->y() > rhs.y();
else if (op == "<")
return this->x() < rhs.x() || this->y() < rhs.y();
return false;
}
bool any_comp(const coord_t val, const std::string &op)
{
if (op == ">")
return this->x() > val || this->y() > val;
else if (op == "<")
return this->x() < val || this->y() < val;
return false;
}
void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); }
void rotate(double cos_a, double sin_a) {
double cur_x = (double)this->x();
double cur_y = (double)this->y();
this->x() = (coord_t)round(cos_a * cur_x - sin_a * cur_y);
this->y() = (coord_t)round(cos_a * cur_y + sin_a * cur_x);
}
void rotate(double angle, const Point3 &center);
Point3 rotated(double angle) const { Point3 res(*this); res.rotate(angle); return res; }
Point3 rotated(double cos_a, double sin_a) const { Point3 res(*this); res.rotate(cos_a, sin_a); return res; }
Point3 rotated(double angle, const Point3 &center) const { Point3 res(*this); res.rotate(angle, center); return res; }
Point3 rotate_90_degree_ccw() const { return Point3(-this->y(), this->x(), this->z()); }
int nearest_point_index(const Points &points) const;
bool nearest_point(const Points &points, Point3* point) const;
double ccw(const Point3 &p1, const Point3 &p2) const;
double ccw(const Line3 &line) const;
double ccw_angle(const Point3 &p1, const Point3 &p2) const;
Point3 projection_onto(const MultiPoint3 &poly) const;
Point3 projection_onto(const Line3 &line) const;
// Convert to 2D Point by dropping Z coordinate
Point to_point() const {
return Point(this->x(), this->y());
}
double distance_to(const Point3 &point) const { return (point - *this).cast<double>().norm(); }
};
// Utility function to convert Points3 to Points
inline void append_points(Points &dst, const Points3 &src) {
std::transform(src.begin(), src.end(),
std::back_inserter(dst),
[](const Point3 &pt) {
return pt.to_point();
});
}
inline Point3 operator* (const Point3& l, const double& r)
{
return { coord_t(l.x() * r), coord_t(l.y() * r), coord_t(l.z() * r) };
}
inline std::ostream &operator<<(std::ostream &os, const Point &pt)
{
os << unscale_(pt.x()) << "," << unscale_(pt.y());
@@ -300,6 +423,12 @@ inline Point lerp(const Point &a, const Point &b, double t)
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
}
inline Point3 lerp(const Point3& a, const Point3& b, double t)
{
assert((t >= -EPSILON) && (t <= 1. + EPSILON));
return Point3(((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>());
}
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
+395 -5
View File
@@ -612,6 +612,28 @@ std::pair<int, Point> foot_pt(const Points &polyline, const Point &pt)
return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min);
}
std::pair<int, Point3> foot_pt(const Points3 &polyline, const Point3 &pt)
{
if (polyline.size() < 2) return std::make_pair(-1, Point3(0, 0, 0));
auto d2_min = std::numeric_limits<double>::max();
Point3 foot_pt_min;
Point3 prev = polyline.front();
auto it = polyline.begin();
auto it_proj = polyline.begin();
for (++it; it != polyline.end(); ++it) {
Point3 foot_pt = pt.projection_onto(Line3(prev, *it));
double d2 = (foot_pt - pt).cast<double>().squaredNorm();
if (d2 < d2_min) {
d2_min = d2;
foot_pt_min = foot_pt;
it_proj = it;
}
prev = *it;
}
return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min);
}
ThickLines ThickPolyline::thicklines() const
{
ThickLines lines;
@@ -639,15 +661,383 @@ void ThickPolyline::start_at_index(int index)
Lines3 Polyline3::lines() const
{
Lines3 lines;
if (points.size() >= 2)
{
lines.reserve(points.size() - 1);
for (Points3::const_iterator it = points.begin(); it != points.end() - 1; ++it)
{
if (this->points.size() >= 2) {
lines.reserve(this->points.size() - 1);
for (Points3::const_iterator it = this->points.begin(); it != this->points.end() - 1; ++it) {
lines.emplace_back(*it, *(it + 1));
}
}
return lines;
}
Polyline Polyline3::to_polyline() const
{
Polyline out;
out.points.reserve(this->points.size());
for (const Point3 &point : this->points) {
out.points.emplace_back(point.x(), point.y());
}
return out;
}
void Polyline3::clip_end(double distance)
{
bool last_point_inserted = false;
size_t remove_after_index = MultiPoint3::size();
while (distance > 0) {
Vec3d last_point = this->last_point().cast<double>();
this->points.pop_back();
remove_after_index--;
if (this->points.empty()) {
this->fitting_result.clear();
return;
}
Vec3d v = this->last_point().cast<double>() - last_point;
double lsqr = v.squaredNorm();
if (lsqr > distance * distance) {
this->points.emplace_back((last_point + v * (distance / sqrt(lsqr))).cast<coord_t>());
last_point_inserted = true;
break;
}
distance -= sqrt(lsqr);
}
// BBS: don't need to clip fitting result if it's empty
if (fitting_result.empty())
return;
while (!fitting_result.empty() && fitting_result.back().start_point_index >= remove_after_index)
fitting_result.pop_back();
if (!fitting_result.empty()) {
// BBS: last remaining segment is arc move, then clip the arc at last point
if (fitting_result.back().path_type == EMovePathType::Arc_move_ccw ||
fitting_result.back().path_type == EMovePathType::Arc_move_cw) {
if (fitting_result.back().arc_data.clip_end(this->last_point().to_point()))
// BBS: succeed to clip arc, then update the last point
// TODO: fix z parameter
this->points.back() = Point3(fitting_result.back().arc_data.end_point, this->points.back().z());
else
// BBS: Failed to clip arc, then back to linear move
fitting_result.back().path_type = EMovePathType::Linear_move;
}
fitting_result.back().end_point_index = this->points.size() - 1;
}
}
void Polyline3::simplify(double tolerance)
{
this->points = MultiPoint3::_douglas_peucker(this->points, tolerance);
this->fitting_result.clear();
}
void Polyline3::simplify_by_fitting_arc(double tolerance)
{
// BBS: do arc fit first, then use DP simplify to handle the straight part to reduce point.
Points points_2d = to_points(this->points);
ArcFitter::do_arc_fitting_and_simplify(points_2d, this->fitting_result, tolerance);
this->points = to_points3(points_2d);
}
void Polyline3::reverse()
{
// BBS: reverse points
MultiPoint3::reverse();
// BBS: reverse the fitting_result
if (!this->fitting_result.empty()) {
for (size_t i = 0; i < this->fitting_result.size(); i++) {
std::swap(fitting_result[i].start_point_index, fitting_result[i].end_point_index);
fitting_result[i].start_point_index = MultiPoint3::size() - 1 - fitting_result[i].start_point_index;
fitting_result[i].end_point_index = MultiPoint3::size() - 1 - fitting_result[i].end_point_index;
if (fitting_result[i].is_arc_move())
fitting_result[i].reverse_arc_path();
}
std::reverse(this->fitting_result.begin(), this->fitting_result.end());
}
}
bool Polyline3::split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const
{
if (index > this->size() - 1)
return false;
if (index == 0) {
p1->clear();
p1->append(this->first_point());
*p2 = *this;
} else if (index == this->size() - 1) {
p2->clear();
p2->append(this->last_point());
*p1 = *this;
} else {
// BBS: spilit first part
p1->clear();
p1->points.reserve(index + 1);
p1->points.insert(p1->begin(), this->begin(), this->begin() + index + 1);
Point3 new_endpoint;
if (this->split_fitting_result_before_index(index, new_endpoint, p1->fitting_result))
p1->points.back() = new_endpoint;
p2->clear();
p2->points.reserve(this->size() - index);
p2->points.insert(p2->begin(), this->begin() + index, this->end());
Point3 new_startpoint;
if (this->split_fitting_result_after_index(index, new_startpoint, p2->fitting_result))
p2->points.front() = new_startpoint;
}
return true;
}
void Polyline3::append(const Point3& point)
{
// // Don't append if same as last point
// if (!this->empty() && this->last_point() == point)
// return;
// this->points.push_back(point);
// append_fitting_result_after_append_points();
// BBS: don't need to append same point
if (!this->empty() && this->last_point() == point)
return;
MultiPoint3::append(point);
append_fitting_result_after_append_points();
}
void Polyline3::append(const Polyline3& src)
{
if (!src.is_valid()) return;
if (this->points.empty()) {
this->points = src.points;
this->fitting_result = src.fitting_result;
} else {
// BBS: append the first point to create connection first, update the fitting date as well
this->append(src.points[0]);
// BBS: append a polyline which has fitting data to a polyline without fitting data.
// Then create a fake fitting data first, so that we can keep the fitting data in last polyline
if (this->fitting_result.empty() && !src.fitting_result.empty()) {
this->fitting_result.emplace_back(PathFittingData{0, this->points.size() - 1, EMovePathType::Linear_move, ArcSegment()});
}
// BBS: then append the remain points
MultiPoint3::append(src.points.begin() + 1, src.points.end());
// BBS: finally append the fitting data
append_fitting_result_after_append_polyline(src);
}
}
void Polyline3::append_before(const Point3& point)
{
// BBS: don't need to append same point
if (!this->empty() && this->first_point() == point)
return;
if (this->size() == 1) {
this->fitting_result.clear();
MultiPoint3::append(point);
MultiPoint3::reverse();
} else {
this->reverse();
this->append(point);
this->reverse();
}
}
void Polyline3::split_at(Point& point, Polyline3* p1, Polyline3* p2) const
{
if (this->points.empty()) return;
// 0 judge whether the point is on the polyline
int index = this->find_point(point);
if (index != -1) {
// BBS: the spilit point is on the polyline, then easy
split_at_index(index, p1, p2);
point = p1->is_valid() ? p1->last_point().to_point() : p2->first_point().to_point();
return;
}
// 1 find the line to split at
size_t line_idx = 0;
Point p = this->first_point().to_point();
double min = (p - point).cast<double>().norm();
Lines3 lines = this->lines();
for (Lines3::const_iterator line = lines.begin(); line != lines.end(); ++line) {
Point p_tmp = point.projection_onto(line->to_line());
if ((p_tmp - point).cast<double>().norm() < min) {
p = p_tmp;
min = (p - point).cast<double>().norm();
line_idx = line - lines.begin();
}
}
// 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
index = this->find_point(p);
if (index != -1) {
this->split_at_index(index, p1, p2);
p1->append(Point3(point, p1->last_point().z()));
p2->append_before(Point3(point, p2->first_point().z()));
} else {
Polyline3 temp;
this->split_at_index(line_idx, p1, &temp);
p1->append(Point3(point, p1->last_point().z()));
this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(Point3(point, p2->first_point().z()));
}
}
void Polyline3::split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const {
Point p = point.to_point();
this->split_at(p, p1, p2);
point = Point3(p, point.z());
}
bool Polyline3::split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const {
if (this->points.empty()) return false;
if (length < 0 || length > this->length()) {
return false;
}
if (length < SCALED_EPSILON) {
p1->clear();
p1->append(this->first_point());
*p2 = *this;
} else if (is_approx(length, this->length(), SCALED_EPSILON)) {
p2->clear();
p2->append(this->last_point());
*p1 = *this;
} else {
// 1 find the line to split at
size_t line_idx = 0;
double acc_length = 0;
Point3 p = this->first_point();
for (const auto& l : this->lines()) {
p = l.b;
const double current_length = l.length();
if (acc_length + current_length >= length) {
p = lerp(l.a, l.b, (length - acc_length) / current_length);
break;
}
acc_length += current_length;
line_idx++;
}
// 2 judge whether the cloest point is one vertex of polyline.
// and spilit the polyline at different index
int index = this->find_point(p);
if (index != -1) {
this->split_at_index(index, p1, p2);
} else {
Polyline3 temp;
this->split_at_index(line_idx, p1, &temp);
p1->append(p);
this->split_at_index(line_idx + 1, &temp, p2);
p2->append_before(p);
}
}
return true;
}
bool Polyline3::split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_endpoint = this->points[index];
if (!this->fitting_result.empty()) {
// BBS: max size
data.reserve(this->fitting_result.size());
// BBS: save fitting result before index
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].start_point_index < index)
data.push_back(this->fitting_result[i]);
else
break;
}
if (!data.empty()) {
// BBS: need to clip the arc and generate new end point
if (data.back().is_arc_move() && data.back().end_point_index > index) {
if (!data.back().arc_data.clip_end(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.back().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new end point
new_endpoint = Point3(data.back().arc_data.end_point, 0);
}
data.back().end_point_index = index;
}
data.shrink_to_fit();
return true;
}
return false;
}
bool Polyline3::split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& data) const
{
data.clear();
new_startpoint = this->points[index];
if (!this->fitting_result.empty()) {
data.reserve(this->fitting_result.size());
for (size_t i = 0; i < this->fitting_result.size(); i++) {
if (this->fitting_result[i].end_point_index > index)
data.push_back(this->fitting_result[i]);
}
if (!data.empty()) {
for (size_t i = 0; i < data.size(); i++) {
if (i != 0) {
data[i].start_point_index -= index;
data[i].end_point_index -= index;
} else {
data[i].end_point_index -= index;
// BBS: need to clip the arc and generate new start point
if (data.front().is_arc_move() && data.front().start_point_index < index) {
if (!data.front().arc_data.clip_start(this->points[index].to_point()))
// BBS: failed to clip arc, then return to be linear move
data.front().path_type = EMovePathType::Linear_move;
else
// BBS: succeed to clip arc, then update and return the new start point
new_startpoint = Point3(data.front().arc_data.start_point, 0);
}
data[i].start_point_index = 0;
}
}
}
data.shrink_to_fit();
return true;
}
return false;
}
void Polyline3::append_fitting_result_after_append_points()
{
if (!fitting_result.empty()) {
if (fitting_result.back().is_linear_move()) {
fitting_result.back().end_point_index = this->points.size() - 1;
} else {
size_t new_start = fitting_result.back().end_point_index;
size_t new_end = this->points.size() - 1;
if (new_start != new_end)
fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
void Polyline3::append_fitting_result_after_append_polyline(const Polyline3& src)
{
if (!this->fitting_result.empty()) {
// BBS: offset and save the fitting_result from src polyline
if (!src.fitting_result.empty()) {
size_t old_size = this->fitting_result.size();
size_t index_offset = this->fitting_result.back().end_point_index;
this->fitting_result.insert(this->fitting_result.end(), src.fitting_result.begin(), src.fitting_result.end());
for (size_t i = old_size; i < this->fitting_result.size(); i++) {
this->fitting_result[i].start_point_index += index_offset;
this->fitting_result[i].end_point_index += index_offset;
}
} else {
// BBS: the append polyline has no fitting data, then append as linear move directly
size_t new_start = this->fitting_result.back().end_point_index;
size_t new_end = this->size() - 1;
if (new_start != new_end)
this->fitting_result.emplace_back(PathFittingData{new_start, new_end, EMovePathType::Linear_move, ArcSegment()});
}
}
}
}
+57
View File
@@ -251,6 +251,7 @@ bool remove_degenerate(Polylines &polylines);
// Returns index of a segment of a polyline and foot point of pt on polyline.
std::pair<int, Point> foot_pt(const Points &polyline, const Point &pt);
std::pair<int, Point3> foot_pt(const Points3 &polyline, const Point3 &pt);
class ThickPolyline : public Polyline {
public:
@@ -290,7 +291,63 @@ inline ThickPolylines to_thick_polylines(Polylines&& polylines, const coordf_t w
class Polyline3 : public MultiPoint3
{
public:
Polyline3() {}
explicit Polyline3(const Points3 &points) { this->points = points; }
explicit Polyline3(const Polyline &poly, coord_t z = 0) {
this->points.reserve(poly.points.size());
for (const Point &pt : poly.points) {
this->points.emplace_back(pt.x(), pt.y(), z);
}
}
virtual Lines3 lines() const;
// Convert to 2D Polyline by dropping Z coordinates
Polyline to_polyline() const;
// Clip the end of the polyline by a distance
void clip_end(double distance);
// Simplify polyline using Douglas-Peucker algorithm
void simplify(double tolerance);
// Simplify by arc fitting (for ZAA arc fitting support)
void simplify_by_fitting_arc(double tolerance);
// Reverse the polyline
void reverse();
// Split polyline at given index
bool split_at_index(const size_t index, Polyline3 *p1, Polyline3 *p2) const;
// Split polyline at a given point (2D)
void split_at(Point &point, Polyline3* p1, Polyline3* p2) const;
// Split polyline at a given point (3D)
void split_at(Point3 &point, Polyline3* p1, Polyline3* p2) const;
// Split polyline at a given length
bool split_at_length(const double length, Polyline3 *p1, Polyline3 *p2) const;
// Append a single point
void append(const Point3& point);
// Append another Polyline3
void append(const Polyline3& src);
// Append before (prepend)
void append_before(const Point3& point);
// Arc fitting support - fitting_result stores arc path data
// This is populated by simplify_by_fitting_arc()
// Uses the global PathFittingData from ArcFitter.hpp
std::vector<PathFittingData> fitting_result;
private:
void append_fitting_result_after_append_points();
void append_fitting_result_after_append_polyline(const Polyline3& src);
bool split_fitting_result_before_index(size_t index, Point3& new_endpoint, std::vector<PathFittingData>& result) const;
bool split_fitting_result_after_index(size_t index, Point3& new_startpoint, std::vector<PathFittingData>& result) const;
};
typedef std::vector<Polyline3> Polylines3;
+520 -139
View File
File diff suppressed because it is too large Load Diff
+48 -7
View File
@@ -29,6 +29,11 @@
#define PRESET_TEMPLATE_DIR "Template"
#define PRESET_CUSTOM_VENDOR "Custom"
// Orca: bundle import directories
#define PRESET_LOCAL_DIR "_local"
#define PRESET_SUBSCRIBED_DIR "_subscribed"
#define PRESET_BUNDLE_METADATA "bundle_metadata.json"
//BBS: iot preset type strings
#define PRESET_IOT_PRINTER_TYPE "printer"
#define PRESET_IOT_FILAMENT_TYPE "filament"
@@ -228,7 +233,8 @@ public:
//BBS: add type for project-embedded
bool is_project_embedded = false;
ConfigSubstitutions *loading_substitutions{nullptr};
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded; }
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded && ! this->is_from_bundle(); }
bool can_overwrite() const { return ! this->is_default && ! this->is_system && ! this->is_from_bundle(); }
//bool is_user() const { return ! this->is_default && ! this->is_system; }
// Name of the preset, usually derived form the file name.
@@ -261,6 +267,11 @@ public:
// Orca: flag to indicate if this preset is from Orca Filament Library
bool m_from_orca_filament_lib = false;
// Orca: bundle tracking - imported preset bundles. Bundle ID: UUID (OrcaCloud) or name+timestamp (external).
// Presence of bundle_id is the source of truth for "came from a bundle".
std::string bundle_id;
bool is_from_bundle() const { return ! bundle_id.empty(); }
//BBS
Semver version; // version of preset
std::string ini_str; // ini string of preset
@@ -279,7 +290,7 @@ public:
static Preset::Type get_type_from_string(std::string type_str);
void load_info(const std::string& file);
void save_info(std::string file = "");
void remove_files();
void remove_files(bool cloud_already_deleted = false);
//BBS: add logic for only difference save
//if parent_config is null, save all keys, otherwise, only save difference
@@ -395,6 +406,28 @@ bool is_compatible_with_print (const PresetWithVendorProfile &preset, const Pre
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
// Where a preset is being loaded from. `Auto` lets load_presets() infer from the directory path.
struct PresetOrigin {
enum class Kind { Auto, User, LocalBundle, SubscribedBundle };
Kind kind { Kind::Auto };
std::string bundle_id;
PresetOrigin() = default;
PresetOrigin(Kind kind, std::string bundle_id = {}) : kind(kind), bundle_id(std::move(bundle_id)) {}
bool is_bundle() const { return kind == Kind::LocalBundle || kind == Kind::SubscribedBundle; }
};
// Prepend the bundle folder to `preset_bare_name` based on `origin`. No-op for non-bundle origins.
std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin);
// Tail segment of a canonical name — what's written to the bundle's .json filename and JSON "name" field.
std::string get_preset_bare_name(const std::string &canonical_name);
// Resolve an origin from a directory path when the caller passes Kind::Auto.
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin = PresetOrigin());
enum class PresetSelectCompatibleType {
// Never select a compatible preset if the newly selected profile is not compatible.
Never,
@@ -471,12 +504,12 @@ public:
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, std::function<void(Preset&)> preset_loaded_fn = nullptr, const PresetOrigin &load_origin = PresetOrigin());
//BBS: update user presets directory
void update_user_presets_directory(const std::string& dir_path, const std::string& type);
void save_user_presets(const std::string& dir_path, const std::string& type, std::vector<std::string>& need_to_delete_list);
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list);
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin = PresetOrigin(PresetOrigin::Kind::User));
void update_after_user_presets_loaded();
//BBS: get user presets
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
@@ -550,7 +583,11 @@ public:
bool delete_current_preset();
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
bool delete_preset(const std::string& name);
// When force=true, bypasses the can_overwrite() check (used for bundle preset cleanup).
bool delete_preset(const std::string& name, bool force = false);
// Verify and correct the sync metadata for the preset to ensure proper cloud synchronization.
void check_and_fix_syncinfo(Preset& preset, const std::string& user_id);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
@@ -637,6 +674,7 @@ public:
{
return const_cast<PresetCollection*>(this)->find_preset2(name, auto_match);
}
size_t first_visible_idx() const;
// Return the index of the first visible, compatible, system base preset
// matching the given filament_type. Falls back to base type, then any visible.
@@ -774,6 +812,8 @@ protected:
void set_custom_preset_alias(Preset &preset);
private:
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
static bool filament_preset_less(const Preset &a, const Preset &b) {
bool a_generic = boost::starts_with(a.name, GENERIC_PREFIX);
@@ -795,6 +835,7 @@ private:
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
// `name` must already be canonical — callers canonicalize via find_preset / canonical_preset_name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name, bool from_orca_lib_only = false)
{
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(),
@@ -864,7 +905,7 @@ private:
friend class PresetBundle;
//BBS: mutex
std::mutex m_mutex;
std::recursive_mutex m_mutex;
// Orca: used for validation only
int m_errors = 0;
File diff suppressed because it is too large Load Diff
+133 -9
View File
@@ -6,10 +6,12 @@
#include "enum_bitmask.hpp"
#include <memory>
#include <shared_mutex>
#include <unordered_map>
#include <optional>
#include <array>
#include <boost/filesystem/path.hpp>
#include <unordered_set>
#define DEFAULT_USER_FOLDER_NAME "default"
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
@@ -72,6 +74,79 @@ struct FilamentBaseInfo
int filament_printable = 3;
};
enum BundleType{
Default = 0,
Local,
Subscribed,
};
// Orca: Bundle metadata structure for imported preset bundles
struct BundleMetadata
{
std::string id; // Bundle ID: UUID (OrcaCloud) or name+timestamp (external)
std::string name; // Display name
std::string version; // Bundle version
std::string description;
std::string author;
long long imported_time{0};
long long updated_time{0};
BundleType bundle_type{Default};
std::string path;
// Cached preset names by type (populated on load)
std::vector<std::string> print_presets;
std::vector<std::string> filament_presets;
std::vector<std::string> printer_presets;
// Runtime-only flags
bool is_subscribed{false};
bool update_available{false};
bool not_found{false};
bool unauthorized{false};
bool load_from_json(const std::string& path);
bool save_to_json(const std::string& path) const;
};
struct PresetBundleMetadata
{
// To make sure write locks take precedent, pausereads needs to be true for when Orca needs to read or manipulate the container
// We only need to explicitly pause reads when entering a region in Orca which we deem necessary to quickly acquire write locks.
std::unordered_map<std::string, BundleMetadata> m_bundles;
std::shared_mutex RWMtx;
std::atomic<bool> pauseReads{false};
void PauseRead()
{
pauseReads.store(true);
}
void UnpauseRead()
{
pauseReads.store(false);
}
void ReadLock()
{
RWMtx.lock_shared();
}
void ReadUnlock()
{
RWMtx.unlock_shared();
}
void WriteLock()
{
RWMtx.lock();
}
void WriteUnlock()
{
RWMtx.unlock();
}
};
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
@@ -82,6 +157,10 @@ public:
std::vector<Preset> &in_filament_presets,
bool apply_extruder,
std::optional<std::vector<int>> filament_maps_new);
// ORCA: utility function to find the vendor for a given preset name
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
PresetBundle();
PresetBundle(const PresetBundle &rhs);
PresetBundle& operator=(const PresetBundle &rhs);
@@ -114,19 +193,53 @@ public:
// BBS Load user presets
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions import_presets(std::vector<std::string> &files, std::function<int(std::string const &)> override_confirm, ForwardCompatibilitySubstitutionRule rule);
bool import_json_presets(PresetsConfigSubstitutions & substitutions,
std::string & file,
std::function<int(std::string const &)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
int & overwrite,
std::vector<std::string> & result);
void save_user_presets(AppConfig& config, std::vector<std::string>& need_to_delete_list);
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
const BundleMetadata& remote_metadata,
ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions import_presets(std::vector<std::string>& files,
std::function<int(std::string const&)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
AppConfig& config);
bool import_json_presets(PresetsConfigSubstitutions& substitutions,
std::string& file,
std::function<int(std::string const&)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
int& overwrite,
std::vector<std::string>& result,
const std::string& bundle_dir = "");
void save_user_presets(AppConfig& config, std::map<std::string, std::string>& need_to_delete_list);
void check_and_fix_user_presets_syncinfo(const std::string& user_id);
void remove_users_preset(AppConfig &config, std::map<std::string, std::map<std::string, std::string>> * my_presets = nullptr);
void update_user_presets_directory(const std::string preset_folder);
void remove_user_presets_directory(const std::string preset_folder);
void update_system_preset_setting_ids(std::map<std::string, std::map<std::string, std::string>>& system_presets);
// Apply vendor configuration changes (Core library version, no GUI dependencies)
// This function installs vendors from resources and loads them into the preset bundle
//
// Parameters:
// new_vendors: Map of vendor names to their enabled printer models and variants
// new_filaments: Map of filament names to their settings
// app_config: Pointer to AppConfig to update with new vendor/filament selections
// preferred_printer_model: Optional preferred printer model to select
// preferred_printer_variant: Optional preferred printer variant to select
// preferred_filament: Optional preferred filament to select
//
// Returns: true if successful, false otherwise
bool apply_vendor_config(
const std::map<std::string, std::map<std::string, std::set<std::string>>>& new_vendors,
const std::map<std::string, std::string>& new_filaments,
AppConfig* app_config,
bool overwrite = true,
const std::string& preferred_printer_model = std::string(),
const std::string& preferred_printer_variant = std::string(),
const std::string& preferred_filament = std::string());
//BBS: add API to get previous machine
int validate_presets(const std::string &file_name, DynamicPrintConfig& config, std::set<std::string>& different_gcodes);
@@ -232,6 +345,12 @@ public:
std::map<std::string, DynamicPrintConfig> m_config_maps;
std::map<std::string, std::string> m_filament_id_maps;
// Orca: Bundle metadata and cached preset names
// std::map<std::string, BundleMetadata> m_bundles;
fs::path dir_user_presets_local;
fs::path dir_user_presets_subscribed;
PresetBundleMetadata bundles;
struct ObsoletePresets
{
std::vector<std::string> prints;
@@ -385,9 +504,14 @@ private:
// Orca: used for validation only
bool validation_mode = false;
std::string vendor_to_validate = "";
std::string vendor_to_validate = "";
int m_errors = 0;
// Helper function: save preset to bundle directory with common logic
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
const std::string& bundle_id, const std::string& type_subdir,
const std::string& bundle_base_dir);
};
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
+20 -5
View File
@@ -2246,6 +2246,17 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
}
}
// Z-Contouring
for (PrintObject *obj : m_objects) {
bool need_contouring = need_slicing_objects.count(obj) != 0 && obj->need_z_contouring();
if (need_contouring) {
obj->contour_z();
} else {
if (obj->set_started(posContouring))
obj->set_done(posContouring);
}
}
tbb::parallel_for(tbb::blocked_range<int>(0, int(m_objects.size())),
[this, need_slicing_objects](const tbb::blocked_range<int>& range) {
for (int i = range.begin(); i < range.end(); i++) {
@@ -2284,6 +2295,8 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
obj->set_done(posInfill);
if (obj->set_started(posIroning))
obj->set_done(posIroning);
if (obj->set_started(posContouring))
obj->set_done(posContouring);
if (obj->set_started(posSupportMaterial))
obj->set_done(posSupportMaterial);
if (obj->set_started(posDetectOverhangsForLift))
@@ -2663,7 +2676,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
m_skirt.append(eloop);
if (m_config.min_skirt_length.value > 0) {
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
@@ -2721,7 +2734,7 @@ void Print::_make_skirt()
flow.width(),
(float)initial_layer_print_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();
eloop.paths.back().polyline = Polyline3(loop.split_at_first_point());
object->m_skirt.append(std::move(eloop));
if (m_config.min_skirt_length.value > 0) {
// The skirt length is limited. Sum the total amount of filament length extruded, in mm.
@@ -4126,7 +4139,8 @@ static void from_json(const json& j, Polyline& poly_line) {
}
static void from_json(const json& j, ExtrusionPath& extrusion_path) {
extrusion_path.polyline = j[JSON_EXTRUSION_POLYLINE];
Polyline temp_polyline = j[JSON_EXTRUSION_POLYLINE];
extrusion_path.polyline = Polyline3(temp_polyline);
extrusion_path.mm3_per_mm = j[JSON_EXTRUSION_MM3_PER_MM];
extrusion_path.width = j[JSON_EXTRUSION_WIDTH];
extrusion_path.height = j[JSON_EXTRUSION_HEIGHT];
@@ -4979,8 +4993,9 @@ ExtrusionLayers FakeWipeTower::getTrueExtrusionLayersFromWipeTower() const
paths.reserve(it->second.size());
for (auto &polyline : it->second) {
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_heights[index]);
path.polyline = polyline;
for (auto &p : path.polyline.points) p += trans;
path.polyline = Polyline3(polyline);
Point3 trans3(trans, 0);
for (auto &p : path.polyline.points) p += trans3;
paths.push_back(path);
}
el.paths = std::move(paths);
+7 -5
View File
@@ -92,7 +92,7 @@ enum PrintStep {
enum PrintObjectStep {
posSlice, posPerimeters,posEstimateCurledExtrusions, posPrepareInfill,
posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifySupportPath,
posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifySupportPath,
// BBS
posDetectOverhangsForLift,
posSimplifyWall, posSimplifyInfill,
@@ -496,6 +496,8 @@ private:
void prepare_infill();
void infill();
void ironing();
bool need_z_contouring() const;
void contour_z();
void generate_support_material();
void estimate_curled_extrusions();
void simplify_extrusion_path();
@@ -642,14 +644,14 @@ struct FakeWipeTower
std::vector<ExtrusionPaths> paths;
for (float h = 0.f; h < height; h += layer_height) {
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, layer_height);
path.polyline = {minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner};
path.polyline = Polyline3(Polyline{{minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner}});
paths.push_back({path});
if (h == 0.f) { // add brim
ExtrusionPath fakeBrim(ExtrusionRole::erBrim, 0.0, 0.0, layer_height);
Point wtbminCorner = {minCorner - Point{bd, bd}};
Point wtbmaxCorner = {maxCorner + Point{bd, bd}};
fakeBrim.polyline = {wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner};
fakeBrim.polyline = Polyline3(Polyline{{wtbminCorner, {wtbmaxCorner.x(), wtbminCorner.y()}, wtbmaxCorner, {wtbminCorner.x(), wtbmaxCorner.y()}, wtbminCorner}});
paths.back().push_back(fakeBrim);
}
}
@@ -686,13 +688,13 @@ struct FakeWipeTower
ExtrusionPath path(ExtrusionRole::erWipeTower, 0.0, 0.0, lh);
path.polyline = { minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner };
path.polyline = Polyline3(Polyline{{ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner }});
paths.push_back({ path });
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
// For now, simply use fixed spacing of 3mm.
for (coord_t y=minCorner.y()+scale_(3.); y<maxCorner.y(); y+=scale_(3.)) {
path.polyline = { {minCorner.x(), y}, {maxCorner.x(), y} };
path.polyline = Polyline3(Polyline{{ {minCorner.x(), y}, {maxCorner.x(), y} }});
paths.back().emplace_back(path);
}
+46 -1
View File
@@ -3658,7 +3658,7 @@ void PrintConfigDef::init_fff_params()
def =this->add("support_chamber_temp_control",coBool);
def->label=L("Support control chamber temperature");
def->tooltip=L("This option is enabled if machine support controlling chamber temperature\nG-code command: M141 S(0-255)");
def->mode=comDevelop;
def->mode=comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def->readonly=false;
@@ -4130,6 +4130,51 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("ironing_expansion", coFloat);
def->label = L("Ironing expansion");
def->category = L("Quality");
def->tooltip = L("Expand or contract the ironing area.");
def->sidetext = L("mm");
def->min = -100;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("zaa_enabled", coBool);
def->label = L("Z contouring enabled");
def->category = L("Quality");
def->tooltip = L("Enable Z-layer contouring (aka Z-layer anti-aliasing)");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("zaa_minimize_perimeter_height", coFloat);
def->label = L("Minimize wall height angle");
def->category = L("Quality");
def->tooltip = L("Reduce height of top surface perimeters to match height of model edge. "
"Effects perimeters whose slope is less than this angle in degrees. Reasonable value is 35. Set 0 to disable.");
def->sidetext = L("°");
def->min = 0;
def->max = 90;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("zaa_dont_alternate_fill_direction", coBool);
def->label = L("Don't alternate fill direction");
def->category = L("Quality");
def->tooltip = L("Disable alternating fill direction when using Z contouring");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("zaa_min_z", coFloat);
def->label = L("Minimum z height");
def->category = L("Quality");
def->tooltip = L("Minimum z layer height. Also controls slicing plane");
def->sidetext = L("mm");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.05));
def = this->add("layer_change_gcode", coString);
def->label = L("Layer change G-code");
def->tooltip = L("This G-code is inserted at every layer change after the Z lift.");
+7 -1
View File
@@ -1225,7 +1225,13 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloatOrPercent, scarf_joint_speed))
((ConfigOptionFloat, scarf_joint_flow_ratio))
((ConfigOptionPercent, scarf_overhang_threshold))
)
// Orca: Z Anti-Aliasing (aka Z Contouring)
((ConfigOptionBool, zaa_enabled))
((ConfigOptionBool, zaa_dont_alternate_fill_direction))
((ConfigOptionFloat, zaa_min_z))
((ConfigOptionFloat, zaa_minimize_perimeter_height))
)
PRINT_CONFIG_CLASS_DEFINE(
MachineEnvelopeConfig,
+70 -4
View File
@@ -1,4 +1,6 @@
#include "Exception.hpp"
#include "Model.hpp"
#include "Point.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@@ -8,6 +10,7 @@
#include "Layer.hpp"
#include "MutablePolygon.hpp"
#include "PrintConfig.hpp"
#include "SLA/IndexedMesh.hpp"
#include "Support/SupportMaterial.hpp"
#include "Support/SupportSpotsGenerator.hpp"
#include "Support/TreeSupport.hpp"
@@ -22,7 +25,11 @@
#include "format.hpp"
#include "AABBTreeLines.hpp"
#include <cstddef>
#include <float.h>
#include <iterator>
#include <mutex>
#include <string>
#include <oneapi/tbb/blocked_range.h>
#include <oneapi/tbb/concurrent_vector.h>
#include <oneapi/tbb/parallel_for.h>
@@ -709,6 +716,65 @@ void PrintObject::ironing()
}
}
bool PrintObject::need_z_contouring() const
{
size_t num_regions = this->num_printing_regions();
for (size_t region_id = 0; region_id < num_regions; region_id++) {
if (this->printing_region(region_id).config().zaa_enabled)
return true;
}
return false;
}
void PrintObject::contour_z()
{
if (!this->set_started(posContouring)) {
return;
}
m_print->set_status(40, L("Z contouring"));
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - start";
TriangleMesh mesh = this->m_model_object->raw_mesh();
if (m_model_object->instances.size() != 1) {
throw RuntimeError("ContourZ: unexpected number of instances");
}
ModelInstance *inst = m_model_object->instances.front();
Point center_offset = this->center_offset();
Geometry::Transformation trans = inst->get_transformation();
double z = this->m_model_object->min_z();
trans.set_offset(Vec3d(-unscale<double>(center_offset.x()), -unscale<double>(center_offset.y()), 0));
mesh.transform(trans.get_matrix());
sla::IndexedMesh imesh(mesh);
imesh.ground_level_offset(-z);
std::mutex mtx;
size_t completed = 0;
tbb::parallel_for(
// Contouring starting with layer second layer to avoid build plate collision
tbb::blocked_range<size_t>(1, m_layers.size()),
[&, this](const tbb::blocked_range<size_t>& range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); layer_idx++) {
m_print->throw_if_canceled();
m_layers[layer_idx]->make_contour_z(imesh);
std::scoped_lock lock(mtx);
completed++;
std::string msg = (boost::format("Z contoured layer %d/%d (%d%%)") % (completed) % m_layers.size() % int(double(completed) / m_layers.size() * 100)).str();
m_print->set_status(40, msg);
}
}
);
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Contouring in parallel - end";
this->set_done(posContouring);
}
// BBS
void PrintObject::clear_overhangs_for_lift()
{
@@ -1370,15 +1436,15 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
// propagate to dependent steps
if (step == posPerimeters) {
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posSimplifyPath, posSimplifyInfill });
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posContouring, posSimplifyPath, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posPrepareInfill) {
invalidated |= this->invalidate_steps({ posInfill, posIroning, posSimplifyPath, posSimplifyInfill });
invalidated |= this->invalidate_steps({ posInfill, posIroning, posContouring, posSimplifyPath, posSimplifyInfill });
} else if (step == posInfill) {
invalidated |= this->invalidate_steps({ posIroning, posSimplifyInfill });
invalidated |= this->invalidate_steps({ posIroning, posContouring, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
} else if (step == posSlice) {
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posContouring, posSupportMaterial, posSimplifyPath, posSimplifyInfill });
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
m_slicing_params.valid = false;
} else if (step == posSupportMaterial) {
+18
View File
@@ -4,6 +4,7 @@
#include "ClipperUtils.hpp"
#include "ElephantFootCompensation.hpp"
#include "Exception.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
@@ -34,6 +35,23 @@ LayerPtrs new_layers(
coordf_t lo = object_layers[i_layer];
coordf_t hi = object_layers[i_layer + 1];
coordf_t slice_z = 0.5 * (lo + hi);
bool zaa_active = false;
coordf_t z_offset = 0.0;
size_t num_regions = print_object->num_printing_regions();
for (size_t rid = 0; rid < num_regions; ++rid) {
const auto &rcfg = print_object->printing_region(rid).config();
if (rcfg.zaa_enabled) {
if (!zaa_active || rcfg.zaa_min_z < z_offset)
z_offset = rcfg.zaa_min_z;
zaa_active = true;
}
}
if (zaa_active) {
slice_z = lo + z_offset;
if (slice_z < lo || slice_z > hi) {
throw RuntimeError("Bad min Z value");
}
}
Layer *layer = new Layer(id ++, print_object, hi - lo, hi + zmin, slice_z);
out.emplace_back(layer);
if (prev != nullptr) {
+7 -2
View File
@@ -1000,7 +1000,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
auto segment_end_point = [&entities](size_t idx, bool first_point) -> Point { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
for (std::pair<size_t, bool> &segment : out) {
@@ -1028,6 +1028,11 @@ void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const s
entities.swap(out);
}
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point &start_near)
{
chain_and_reorder_extrusion_entities(entities, &start_near);
}
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
// this function crashes if there are empty elements in entities
@@ -1038,7 +1043,7 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> Point { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
return chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, extrusion_paths.size(), start_near);
}
+1
View File
@@ -20,6 +20,7 @@ std::vector<size_t> chain_expolygons(const ExPolygons &input_exploy);
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point &start_near);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
+4 -3
View File
@@ -1163,7 +1163,7 @@ static void modulate_extrusion_by_overlapping_layers(
for (ExtrusionEntity *ee : extrusions_in_out) {
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(ee);
assert(path != nullptr);
polylines.emplace_back(Polyline(std::move(path->polyline)));
polylines.emplace_back(path->polyline.to_polyline());
path_ends.emplace_back(std::pair<Point, Point>(polylines.back().points.front(), polylines.back().points.back()));
delete path;
}
@@ -1288,9 +1288,10 @@ static void modulate_extrusion_by_overlapping_layers(
if (! path->polyline.points.empty())
path->polyline.points.pop_back();
// Consume the fragment's polyline, remove it from the input fragments, so it will be ignored the next time.
path->polyline.append(std::move(frag_polyline));
path->polyline.append(Polyline3(std::move(frag_polyline)));
frag_polyline.points.clear();
pt_current = path->polyline.points.back();
const Point3 &pt_back3 = path->polyline.points.back();
pt_current = Point(pt_back3.x(), pt_back3.y());
if (pt_current == pt_end) {
// End of the path.
break;
+3 -1
View File
@@ -1181,7 +1181,9 @@ namespace SupportMaterialInternal {
// This is a complete loop.
// Add the outer contour first.
Polygon poly;
poly.points = ep.polyline.points;
// Convert Points3 to Points
for (const Point3 &p3 : ep.polyline.points)
poly.points.emplace_back(p3.x(), p3.y());
poly.points.pop_back();
if (poly.area() < 0)
poly.reverse();
@@ -165,8 +165,8 @@ void estimate_malformations(LayerPtrs &layers, const Params &params)
flow_width,
params.bridge_distance);
for (size_t i = 0; i < annotated_points.size(); ++i) {
const ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint &b = annotated_points[i];
const ExtendedPoint<2>& a = i > 0 ? annotated_points[i - 1] : annotated_points[i];
const ExtendedPoint<2>& b = annotated_points[i];
ExtrusionLine line_out{a.position.cast<float>(), b.position.cast<float>(), float((a.position - b.position).norm()),
extrusion};
+13 -1
View File
@@ -707,7 +707,19 @@ inline std::string filter_characters(const std::string& str, const std::string&
return filteredStr;
}
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter = nullptr);
void copy_directory_recursively(const boost::filesystem::path& source,
const boost::filesystem::path& target,
std::function<bool(const std::string)> filter = nullptr,
bool merge_mode = false);
// Install vendor bundles from resources directory to data directory
// bundle_names: vector of vendor bundle names (without .json extension)
// resource_subdir: subdirectory under resources_dir() (default: "profiles")
// data_subdir: subdirectory under data_dir() (default: "system")
// Returns: true if all bundles installed successfully, false otherwise
bool install_vendor_bundles_from_resources(const std::vector<std::string>& bundle_names,
const std::string& resource_subdir = "profiles",
const std::string& data_subdir = "system");
// Orca: Since 1.7.9 Boost deprecated save_string_file and load_string_file, copy and modified from boost 1.7.8
void save_string_file(const boost::filesystem::path& p, const std::string& str);
+11 -11
View File
@@ -16,11 +16,11 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
if (line_len < SCALED_EPSILON) {
// The line is so tiny that we don't care about its width when we connect it to another line.
if (!path.empty())
path.polyline.points.back() = line.b; // If the variable path is non-empty, connect this tiny line to it.
path.polyline.points.back() = Point3(line.b); // If the variable path is non-empty, connect this tiny line to it.
else if (i + 1 < (int)lines.size()) // If there is at least one following line, connect this tiny line to it.
lines[i + 1].a = line.a;
else if (!multi_path.paths.empty())
multi_path.paths.back().polyline.points.back() = line.b; // Connect this tiny line to the last finished path.
multi_path.paths.back().polyline.points.back() = Point3(line.b); // Connect this tiny line to the last finished path.
// If any of the above isn't satisfied, then remove this tiny line.
continue;
@@ -65,8 +65,8 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
const double w = fmax(line.a_width, line.b_width);
const Flow new_flow = (role == erOverhangPerimeter && flow.bridge()) ? flow : flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
if (path.polyline.points.empty()) {
path.polyline.append(line.a);
path.polyline.append(line.b);
path.polyline.append(Point3(line.a));
path.polyline.append(Point3(line.b));
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
#ifdef SLIC3R_DEBUG
@@ -81,7 +81,7 @@ ExtrusionMultiPath thick_polyline_to_multi_path(const ThickPolyline& thick_polyl
if (thickness_delta <= merge_tolerance) {
// the width difference between this line and the current flow
// (of the previous line) width is within the accepted tolerance
path.polyline.append(line.b);
path.polyline.append(Point3(line.b));
} else {
// we need to initialize a new line
multi_path.paths.emplace_back(std::move(path));
@@ -124,13 +124,13 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t
path = ExtrusionPath(role);
double length = lines[start_index].length();
double sum = lines[start_index].length() * 0.5 * (lines[start_index].a_width + lines[start_index].b_width);
path.polyline.append(lines[start_index].a);
path.polyline.append(Point3(lines[start_index].a));
for (int idx = start_index + 1; idx < i; idx++) {
length += lines[idx].length();
sum += lines[idx].length() * 0.5 * (lines[idx].a_width + lines[idx].b_width);
path.polyline.append(lines[idx].a);
path.polyline.append(Point3(lines[idx].a));
}
path.polyline.append(lines[i].a);
path.polyline.append(Point3(lines[i].a));
if (length > SCALED_EPSILON) {
double w = sum / length;
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
@@ -193,13 +193,13 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths_2(const ThickPolyline& t
path = ExtrusionPath(role);
double length = lines[start_index].length();
double sum = lines[start_index].length() * lines[start_index].a_width;
path.polyline.append(lines[start_index].a);
path.polyline.append(Point3(lines[start_index].a));
for (int idx = start_index + 1; idx < final_size; idx++) {
length += lines[idx].length();
sum += lines[idx].length() * lines[idx].a_width;
path.polyline.append(lines[idx].a);
path.polyline.append(Point3(lines[idx].a));
}
path.polyline.append(lines[final_size - 1].b);
path.polyline.append(Point3(lines[final_size - 1].b));
if (length > SCALED_EPSILON) {
double w = sum / length;
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
+76 -3
View File
@@ -1597,12 +1597,15 @@ bool bbl_calc_md5(std::string &filename, std::string &md5_out)
}
// SoftFever: copy directory recursively
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter)
void copy_directory_recursively(const boost::filesystem::path& source,
const boost::filesystem::path& target,
std::function<bool(const std::string)> filter,
bool merge_mode)
{
BOOST_LOG_TRIVIAL(info) << Slic3r::format("copy_directory_recursively %1% -> %2%", source, target);
std::string error_message;
if (boost::filesystem::exists(target))
if (!merge_mode && boost::filesystem::exists(target))
boost::filesystem::remove_all(target);
boost::filesystem::create_directories(target);
for (auto &dir_entry : boost::filesystem::directory_iterator(source))
@@ -1613,7 +1616,7 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
if (boost::filesystem::is_directory(dir_entry)) {
const auto target_path = target / name;
copy_directory_recursively(dir_entry, target_path);
copy_directory_recursively(dir_entry, target_path, filter, merge_mode);
}
else {
if(filter && filter(name))
@@ -1630,6 +1633,76 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
return;
}
bool install_vendor_bundles_from_resources(
const std::vector<std::string>& bundle_names,
const std::string& resource_subdir,
const std::string& data_subdir)
{
namespace fs = boost::filesystem;
fs::path rsrc_path = fs::path(Slic3r::resources_dir()) / resource_subdir;
fs::path vendor_path = fs::path(Slic3r::data_dir()) / data_subdir;
BOOST_LOG_TRIVIAL(info) << "Installing " << bundle_names.size() << " bundles from resources...";
for (const auto &bundle : bundle_names) {
try {
// Install the JSON file
auto path_in_rsrc = (rsrc_path / bundle).replace_extension(".json");
auto path_in_vendors = (vendor_path / bundle).replace_extension(".json");
if (!fs::exists(path_in_rsrc)) {
BOOST_LOG_TRIVIAL(warning) << "Bundle not found in resources: " << bundle;
return false;
}
// Create target directory if needed
if (!fs::exists(vendor_path))
fs::create_directories(vendor_path);
// Copy JSON file
std::string error_message;
CopyFileResult cfr = copy_file(path_in_rsrc.string(), path_in_vendors.string(), error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Failed to copy " << bundle << ".json: " << error_message;
return false;
}
// Copy the vendor directory (if it exists)
auto dir_in_rsrc = rsrc_path / bundle;
auto dir_in_vendors = vendor_path / bundle;
if (fs::exists(dir_in_rsrc) && fs::is_directory(dir_in_rsrc)) {
// Remove existing directory
if (fs::exists(dir_in_vendors))
fs::remove_all(dir_in_vendors);
fs::create_directories(dir_in_vendors);
// Copy with file filter (same as PresetUpdater::install_bundles_rsrc)
// Filter out certain file types: .stl, .png, .svg, .jpeg, .jpg, .3mf
auto file_filter = [](const std::string name) -> bool {
return boost::iends_with(name, ".stl") ||
boost::iends_with(name, ".png") ||
boost::iends_with(name, ".svg") ||
boost::iends_with(name, ".jpeg") ||
boost::iends_with(name, ".jpg") ||
boost::iends_with(name, ".3mf");
};
copy_directory_recursively(dir_in_rsrc, dir_in_vendors, file_filter);
}
BOOST_LOG_TRIVIAL(info) << "Successfully installed bundle: " << bundle;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Exception installing bundle " << bundle << ": " << e.what();
return false;
}
}
return true;
}
void save_string_file(const boost::filesystem::path& p, const std::string& str)
{
boost::nowide::ofstream file;
+4
View File
@@ -379,6 +379,10 @@ set(SLIC3R_GUI_SOURCES
GUI/PlateSettingsDialog.hpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetBundleDialog.cpp
GUI/PresetBundleDialog.hpp
GUI/ExportPresetBundleDialog.cpp
GUI/ExportPresetBundleDialog.hpp
GUI/PresetComboBoxes.cpp
GUI/PresetComboBoxes.hpp
GUI/PresetHints.cpp
+3 -3
View File
@@ -2062,7 +2062,7 @@ void _3DScene::thick_lines_to_verts(
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
{
Polyline polyline = extrusion_path.polyline;
Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines = polyline.lines();
@@ -2078,7 +2078,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, flo
std::vector<double> widths;
std::vector<double> heights;
for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) {
Polyline polyline = extrusion_path.polyline;
Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines_this = polyline.lines();
@@ -2096,7 +2096,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_mult
std::vector<double> widths;
std::vector<double> heights;
for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) {
Polyline polyline = extrusion_path.polyline;
Polyline polyline = extrusion_path.polyline.to_polyline();
polyline.remove_duplicate_points();
polyline.translate(copy);
const Lines lines_this = polyline.lines();
+10 -8
View File
@@ -285,7 +285,7 @@ void PingCodeBindDialog::on_bind_printer(wxCommandEvent& event)
}
NetworkAgent* agent = wxGetApp().getAgent();
if (agent && agent->is_user_login() && ping_code.length() == PING_CODE_LENGTH) {
if (agent && agent->is_user_login(wxGetApp().get_printer_cloud_provider()) && ping_code.length() == PING_CODE_LENGTH) {
auto result = agent->ping_bind(ping_code.ToStdString());
if(result < 0){
@@ -868,11 +868,12 @@ void BindMachineDialog::on_show(wxShowEvent &event)
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
if (wxGetApp().is_user_login()) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname());
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_nickname(provider));
m_user_name->SetLabelText(username_text);
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
Slic3r::Http http = Slic3r::Http::get(avatar_url);
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
http.header("accept", "image/" + suffix)
@@ -1017,7 +1018,7 @@ void UnBindMachineDialog::on_cancel(wxCommandEvent &event)
void UnBindMachineDialog::on_unbind_printer(wxCommandEvent &event)
{
if (!wxGetApp().is_user_login()) {
if (!wxGetApp().is_user_login(wxGetApp().get_printer_cloud_provider())) {
m_status_text->SetLabelText(_L("Please log in first."));
return;
}
@@ -1073,11 +1074,12 @@ void UnBindMachineDialog::on_show(wxShowEvent &event)
m_printer_name->SetLabelText(from_u8(m_machine_info->get_dev_name()));
if (wxGetApp().is_user_login()) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name());
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
wxString username_text = from_u8(wxGetApp().getAgent()->get_user_name(provider));
m_user_name->SetLabelText(username_text);
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar();
std::string avatar_url = wxGetApp().getAgent()->get_user_avatar(provider);
Slic3r::Http http = Slic3r::Http::get(avatar_url);
std::string suffix = avatar_url.substr(avatar_url.find_last_of(".") + 1);
http.header("accept", "image/" + suffix)
+5 -4
View File
@@ -244,13 +244,14 @@ void SelectMObjectPopup::Popup(wxWindow* WXUNUSED(focus))
m_refresh_timer->Start(MACHINE_LIST_REFRESH_INTERVAL);
}
if (wxGetApp().is_user_login()) {
const std::string provider = wxGetApp().get_printer_cloud_provider();
if (wxGetApp().is_user_login(provider)) {
if (!get_print_info_thread) {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token)] {
get_print_info_thread = new boost::thread(Slic3r::create_thread([this, token = std::weak_ptr<int>(m_token), provider] {
NetworkAgent* agent = wxGetApp().getAgent();
unsigned int http_code;
std::string body;
int result = agent->get_user_print_info(&http_code, &body);
int result = agent->get_user_print_info(&http_code, &body, provider);
wxGetApp().CallAfter([token, this, result, body]() {
if (token.expired()) {return;}
@@ -585,7 +586,7 @@ void CalibrationPanel::update_all() {
// only disconnected server in cloud mode
if (obj->connection_type() != "lan") {
if (m_agent) {
server_status = m_agent->is_server_connected() ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
server_status = m_agent->is_server_connected(wxGetApp().get_printer_cloud_provider()) ? 0 : (int)MONITOR_DISCONNECTED_SERVER;
}
}
show_status((int)MONITOR_DISCONNECTED + server_status);
@@ -1694,7 +1694,7 @@ void CalibrationPresetPage::update_show_status()
MachineObject* obj_ = dev->get_selected_machine();
if (!obj_) {
if (agent->is_user_login()) {
if (agent->is_user_login(wxGetApp().get_printer_cloud_provider())) {
show_status(CaliPresetPageStatus::CaliPresetStatusInvalidPrinter);
}
else {
@@ -1704,7 +1704,7 @@ void CalibrationPresetPage::update_show_status()
}
if (!obj_->is_lan_mode_printer()) {
if (!agent->is_server_connected()) {
if (!agent->is_server_connected(wxGetApp().get_printer_cloud_provider())) {
show_status(CaliPresetPageStatus::CaliPresetStatusConnectingServer);
return;
}
+4
View File
@@ -801,6 +801,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co
toggle_line("ironing_speed", has_ironing || has_support_ironing);
bool has_zaa = config->opt_bool("zaa_enabled");
for (auto el : {"zaa_minimize_perimeter_height", "zaa_min_z", "zaa_dont_alternate_fill_direction", "ironing_expansion"})
toggle_line(el, has_zaa);
bool have_sequential_printing = (config->opt_enum<PrintSequence>("print_sequence") == PrintSequence::ByObject);
// for (auto el : { "extruder_clearance_radius", "extruder_clearance_height_to_rod", "extruder_clearance_height_to_lid" })
// toggle_field(el, have_sequential_printing);
+4 -5
View File
@@ -248,8 +248,7 @@ static bool delete_filament_preset_by_name(std::string delete_preset_name, std::
if (!need_delete_preset) BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't find delete preset and name: %1%") % delete_preset_name;
if (!need_delete_preset->setting_id.empty()) {
BOOST_LOG_TRIVIAL(info) << "delete preset = " << need_delete_preset->name << ", setting_id = " << need_delete_preset->setting_id;
m_presets.set_sync_info_and_save(need_delete_preset->name, need_delete_preset->setting_id, "delete", 0);
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id);
wxGetApp().delete_preset_from_cloud(need_delete_preset->setting_id, need_delete_preset->file);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" can't preset setting id is empty and name: %1%") % delete_preset_name;
}
@@ -4348,7 +4347,7 @@ void ExportConfigsDialog::data_init()
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
for (const Preset &filament_preset : filament_presets) {
if (filament_preset.is_system || filament_preset.is_default || filament_preset.is_project_embedded) continue;
if (!filament_preset.is_user()) continue;
if (filament_preset.is_compatible) {
Preset *new_filament_preset = new Preset(filament_preset);
m_filament_presets[preset_name].push_back(new_filament_preset);
@@ -4357,7 +4356,7 @@ void ExportConfigsDialog::data_init()
const std::deque<Preset> &process_presets = preset_bundle.prints.get_presets();
for (const Preset &process_preset : process_presets) {
if (process_preset.is_system || process_preset.is_default || process_preset.is_project_embedded) continue;
if (!process_preset.is_user()) continue;
if (process_preset.is_compatible) {
Preset *new_prpcess_preset = new Preset(process_preset);
m_process_presets[preset_name].push_back(new_prpcess_preset);
@@ -4371,7 +4370,7 @@ void ExportConfigsDialog::data_init()
}
const std::deque<Preset> &filament_presets = preset_bundle.filaments.get_presets();
for (const Preset &filament_preset : filament_presets) {
if (filament_preset.is_system || filament_preset.is_default) continue;
if (!filament_preset.can_overwrite()) continue;
Preset *new_filament_preset = new Preset(filament_preset);
const Preset *base_filament_preset = preset_bundle.filaments.get_preset_base(*new_filament_preset);
+85
View File
@@ -18,6 +18,28 @@ namespace Slic3r
{
class MachineObject;
/**
* DevAmsTray - Represents a single filament tray/slot in an AMS unit or virtual tray.
*
* Data Population:
* ================
* This class is used in two contexts with different population paths:
*
* 1. AMS Trays (within DevFilaSystem):
* NetworkAgent → MachineObject::parse_json() → DevFilaSystemParser::ParseV1_0()
*
* 2. Virtual Trays (MachineObject::vt_slot for external/manual filament):
* NetworkAgent → MachineObject::parse_json() → MachineObject::parse_vt_tray()
*
* Key Fields Used by build_filament_ams_list():
* - setting_id: Filament preset identifier (tray_info_idx from printer)
* - tag_uid: RFID tag unique ID for identifying filament spools
* - m_fila_type: Filament material type (e.g., "PLA", "ABS", "PETG")
* - color: Hex color string without '#' prefix (e.g., "FF0000")
* - cols: Multi-color component list for gradient/multi-color filaments
* - ctype: Color type indicator
* - is_exists: Whether filament is currently loaded in the tray
*/
class DevAmsTray
{
public:
@@ -83,6 +105,27 @@ public:
static wxColour decode_color(const std::string& color);
};
/**
* DevAms - Represents a single AMS (Automatic Material System) unit.
*
* An AMS unit is a physical hardware component that holds multiple filament trays.
* Different printer models support different AMS variants with varying slot counts
* and capabilities.
*
* Data Population:
* ================
* Populated by DevFilaSystemParser from printer JSON messages received via NetworkAgent.
*
* Key Properties:
* - m_ams_id: Unique identifier for this AMS unit (string, typically "0", "1", etc.)
* - m_ext_id: Which extruder this AMS is connected to (for multi-extruder setups)
* - m_trays: Map of tray IDs to DevAmsTray pointers containing filament data
*
* AMS Type Variants:
* - AMS (type 1): Standard 4-slot AMS with humidity control
* - AMS_LITE (type 2): Simplified version
* - N3F/N3S (types 3,4): Newer variants with different humidity/drying support
*/
class DevAms
{
friend class DevFilaSystemParser;
@@ -146,6 +189,36 @@ private:
int m_left_dry_time = 0;
};
/**
* DevFilaSystem - Central manager for all AMS-related data on a printer.
*
* This class owns and manages the hierarchy of AMS units (DevAms) and their trays (DevAmsTray).
* It provides the primary interface for querying filament/AMS state used by the GUI.
*
* Data Flow Architecture:
* =======================
* Printer Device (sends status via MQTT/LAN)
* ↓
* NetworkAgent (receives JSON, invokes registered callbacks)
* ↓
* MachineObject::parse_json() (delegates to DevFilaSystemParser)
* ↓
* DevFilaSystemParser::ParseV1_0() (populates this DevFilaSystem instance)
* ↓
* GUI functions like build_filament_ams_list() read from here
*
* Key Methods:
* - GetAmsList(): Returns map of all AMS units (ams_id -> DevAms*)
* - GetAmsTray(): Retrieves specific tray by AMS ID and tray ID
* - HasAms(): Checks if any AMS units are connected
*
* Ownership:
* - Owned by MachineObject (m_fila_system member)
* - Owns all DevAms instances which in turn own DevAmsTray instances
*
* Note: This class does NOT directly communicate with NetworkAgent.
* It is a passive data store populated by the parsing layer.
*/
class DevFilaSystem
{
friend class DevFilaSystemParser;
@@ -200,6 +273,18 @@ private:
};// class DevFilaSystem
/**
* DevFilaSystemParser - Parses printer JSON messages to populate DevFilaSystem.
*
* This is the bridge between NetworkAgent's raw JSON data and the structured
* DevFilaSystem/DevAms/DevAmsTray hierarchy.
*
* Called from MachineObject::parse_json() when AMS-related fields are present
* in printer status messages received via MQTT or LAN communication.
*
* @see MachineObject::parse_json() - Entry point for JSON parsing
* @see DevFilaSystem - Target data structure
*/
class DevFilaSystemParser
{
public:
+26 -19
View File
@@ -363,7 +363,7 @@ namespace Slic3r
return obj;
}
int DeviceManager::query_bind_status(std::string& msg)
int DeviceManager::query_bind_status(std::string& msg, const std::string& provider)
{
if (!m_agent)
{
@@ -381,7 +381,7 @@ namespace Slic3r
unsigned int http_code;
std::string http_body;
int result = m_agent->query_bind_status(query_list, &http_code, &http_body);
int result = m_agent->query_bind_status(query_list, &http_code, &http_body, provider);
if (result < 0)
{
@@ -419,9 +419,9 @@ namespace Slic3r
return result;
}
MachineObject* DeviceManager::get_user_machine(std::string dev_id)
MachineObject* DeviceManager::get_user_machine(std::string dev_id, const std::string& provider)
{
if (!m_agent || !m_agent->is_user_login())
if (!m_agent || !m_agent->is_user_login(provider))
{
return nullptr;
}
@@ -442,14 +442,13 @@ namespace Slic3r
return nullptr;
}
void DeviceManager::clean_user_info()
void DeviceManager::clean_user_info(bool keep_local_selection)
{
BOOST_LOG_TRIVIAL(trace) << "DeviceManager::clean_user_info";
// reset selected_machine
selected_machine = "";
local_selected_machine = "";
OnSelectedMachineChanged(selected_machine, "");
const std::string previous_selected_machine = selected_machine;
const bool keep_selected_machine = keep_local_selection &&
!selected_machine.empty() &&
localMachineList.find(selected_machine) != localMachineList.end();
// clean user list
for (auto it = userMachineList.begin(); it != userMachineList.end(); it++)
@@ -462,6 +461,13 @@ namespace Slic3r
}
}
userMachineList.clear();
if (!keep_selected_machine) {
selected_machine = "";
local_selected_machine = "";
}
OnSelectedMachineChanged(previous_selected_machine, selected_machine);
}
bool DeviceManager::set_selected_machine(std::string dev_id)
@@ -566,7 +572,7 @@ namespace Slic3r
{
if (selected_machine.empty()) return nullptr;
MachineObject* obj = get_user_machine(selected_machine);
MachineObject* obj = get_user_machine(selected_machine, GUI::wxGetApp().get_printer_cloud_provider());
if (obj)
return obj;
@@ -683,15 +689,15 @@ namespace Slic3r
return "";
}
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name)
void DeviceManager::modify_device_name(std::string dev_id, std::string dev_name, const std::string& provider)
{
BOOST_LOG_TRIVIAL(trace) << "modify_device_name";
if (m_agent)
{
int result = m_agent->modify_printer_name(dev_id, dev_name);
int result = m_agent->modify_printer_name(dev_id, dev_name, provider);
if (result == 0)
{
update_user_machine_list_info();
update_user_machine_list_info(provider);
}
}
}
@@ -712,6 +718,7 @@ namespace Slic3r
try
{
json j = json::parse(body);
const std::string provider = GUI::wxGetApp().get_printer_cloud_provider();
#if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << j;
@@ -740,7 +747,7 @@ namespace Slic3r
obj = new MachineObject(this, m_agent, "", "", "");
if (m_agent)
{
obj->set_bind_status(m_agent->get_user_name());
obj->set_bind_status(m_agent->get_user_name(provider));
}
if (obj->get_dev_ip().empty())
@@ -806,14 +813,14 @@ namespace Slic3r
}
}
void DeviceManager::update_user_machine_list_info()
void DeviceManager::update_user_machine_list_info(const std::string& provider)
{
if (!m_agent) return;
BOOST_LOG_TRIVIAL(debug) << "update_user_machine_list_info";
unsigned int http_code;
std::string body;
int result = m_agent->get_user_print_info(&http_code, &body);
int result = m_agent->get_user_print_info(&http_code, &body, provider);
if (result == 0)
{
parse_user_print_info(body);
@@ -906,7 +913,7 @@ namespace Slic3r
}
// do some refresh
if (Slic3r::GUI::wxGetApp().is_user_login())
if (Slic3r::GUI::wxGetApp().is_user_login(Slic3r::GUI::wxGetApp().get_printer_cloud_provider()))
{
m_manager->check_pushing();
try
@@ -922,4 +929,4 @@ namespace Slic3r
// certificate
agent->install_device_cert(obj->get_dev_id(), obj->is_lan_mode_printer());
}
}
}

Some files were not shown because too many files have changed in this diff Show More