mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-23 01:49:17 +00:00
Merge branch 'main' into fix/recursive_include
This commit is contained in:
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-0300\n"
|
||||
"PO-Revision-Date: 2026-08-01 20:32+0300\n"
|
||||
"PO-Revision-Date: 2026-08-04 19:36+0300\n"
|
||||
"Last-Translator: GlauTech\n"
|
||||
"Language-Team: \n"
|
||||
"Language: tr\n"
|
||||
@@ -738,9 +738,8 @@ msgstr "Sabit adım sürükleme"
|
||||
msgid "Context Menu"
|
||||
msgstr "Bağlam Menüsü"
|
||||
|
||||
# AI Translated
|
||||
msgid "Toggle Auto-Drop"
|
||||
msgstr "Otomatik Bırakmayı Aç/Kapat"
|
||||
msgstr "Otomatik düşürmeyi aç / kapat"
|
||||
|
||||
msgid "Single sided scaling"
|
||||
msgstr "Tek taraflı ölçekleme"
|
||||
@@ -791,9 +790,8 @@ msgstr "Nesne"
|
||||
msgid "Part"
|
||||
msgstr "Parça"
|
||||
|
||||
# AI Translated
|
||||
msgid "Relative"
|
||||
msgstr "Göreli"
|
||||
msgstr "Göreceli"
|
||||
|
||||
# AI Translated
|
||||
msgid "Coordinate system used for transform actions."
|
||||
@@ -2306,7 +2304,7 @@ msgid "new or open project file is not allowed during the slicing process!"
|
||||
msgstr "dilimleme işlemi sırasında yeni veya açık proje dosyasına izin verilmez!"
|
||||
|
||||
msgid "Open Project"
|
||||
msgstr "Projeyi Aç"
|
||||
msgstr "Projeyi aç"
|
||||
|
||||
msgid "The version of Orca Slicer is too low and needs to be updated to the latest version before it can be used normally."
|
||||
msgstr "Orca Slicer'ın sürümü çok düşük ve normal şekilde kullanılabilmesi için en son sürüme güncellenmesi gerekiyor."
|
||||
@@ -2734,16 +2732,15 @@ msgstr "Simit"
|
||||
msgid "Orca Cube"
|
||||
msgstr "Orca Küpü"
|
||||
|
||||
# AI Translated
|
||||
msgid "OrcaSliced Combo"
|
||||
msgstr "OrcaSliced Combo"
|
||||
msgstr "Orca Dilimleme Paketi"
|
||||
|
||||
# AI Translated
|
||||
msgid "Orca Badge"
|
||||
msgstr "Orca Rozeti"
|
||||
|
||||
msgid "Orca Tolerance Test"
|
||||
msgstr "Orca tolerans testi"
|
||||
msgstr "Orca Tolerans Testi"
|
||||
|
||||
msgid "3DBenchy"
|
||||
msgstr "3DBenchy"
|
||||
@@ -2807,7 +2804,7 @@ msgid "Set as Individual Objects"
|
||||
msgstr "Bireysel nesneler olarak ayarla"
|
||||
|
||||
msgid "Fill bed with copies"
|
||||
msgstr "Yatağı kopyalarla doldurun"
|
||||
msgstr "Tablayı kopyalarla doldur"
|
||||
|
||||
msgid "Fill the remaining area of bed with copies of the selected object"
|
||||
msgstr "Yatağın kalan alanını seçilen nesnenin kopyalarıyla doldurun"
|
||||
@@ -2815,9 +2812,8 @@ msgstr "Yatağın kalan alanını seçilen nesnenin kopyalarıyla doldurun"
|
||||
msgid "Printable"
|
||||
msgstr "Yazdırılabilir"
|
||||
|
||||
# AI Translated
|
||||
msgid "Auto Drop"
|
||||
msgstr "Otomatik Bırakma"
|
||||
msgstr "Otomatik düşür"
|
||||
|
||||
# AI Translated
|
||||
msgid "Automatically drops the selected object to the build plate."
|
||||
@@ -2967,7 +2963,7 @@ msgid "Add Models"
|
||||
msgstr "Model ekle"
|
||||
|
||||
msgid "Show Labels"
|
||||
msgstr "Etiketleri Göster"
|
||||
msgstr "Etiketleri göster"
|
||||
|
||||
msgid "To Objects"
|
||||
msgstr "Nesnelere"
|
||||
@@ -3009,7 +3005,7 @@ msgid "Select all objects on the current plate"
|
||||
msgstr "Mevcut plakadaki tüm nesneleri seç"
|
||||
|
||||
msgid "Select All Plates"
|
||||
msgstr "Tüm Plakaları Seç"
|
||||
msgstr "Tüm plakaları seç"
|
||||
|
||||
msgid "Select all objects on all plates"
|
||||
msgstr "Tüm plakalardaki tüm nesneleri seç"
|
||||
@@ -3045,13 +3041,13 @@ msgid "Remove the selected plate"
|
||||
msgstr "Seçilen plakayı kaldır"
|
||||
|
||||
msgid "Add instance"
|
||||
msgstr "Örnek ekle"
|
||||
msgstr "Kopya ekle"
|
||||
|
||||
msgid "Add one more instance of the selected object"
|
||||
msgstr "Seçilen nesnenin bir örneğini daha ekle"
|
||||
|
||||
msgid "Remove instance"
|
||||
msgstr "Örneği kaldır"
|
||||
msgstr "Kopyayı kaldır"
|
||||
|
||||
msgid "Remove one instance of the selected object"
|
||||
msgstr "Seçilen nesnenin bir örneğini kaldır"
|
||||
@@ -3060,10 +3056,10 @@ msgid "Set number of instances"
|
||||
msgstr "Örnek sayısını ayarlayın"
|
||||
|
||||
msgid "Change the number of instances of the selected object"
|
||||
msgstr "Seçilen nesnenin örnek sayısını değiştirme"
|
||||
msgstr "Seçilen nesnenin kopya sayısını değiştirme"
|
||||
|
||||
msgid "Fill bed with instances"
|
||||
msgstr "Yatağı örneklerle doldurun"
|
||||
msgstr "Tablayı kopyalarla doldur"
|
||||
|
||||
msgid "Fill the remaining area of bed with instances of the selected object"
|
||||
msgstr "Yatağın kalan alanını seçilen nesnenin örnekleriyle doldurun"
|
||||
@@ -3075,7 +3071,7 @@ msgid "Simplify Model"
|
||||
msgstr "Modeli basitleştir"
|
||||
|
||||
msgid "Subdivision mesh"
|
||||
msgstr "Alt bölüm ağı"
|
||||
msgstr "Poligon artırma"
|
||||
|
||||
msgid "(Lost color)"
|
||||
msgstr "(Renk kaybı)"
|
||||
@@ -3090,10 +3086,10 @@ msgid "Edit Process Settings"
|
||||
msgstr "İşlem ayarlarını düzenle"
|
||||
|
||||
msgid "Copy Process Settings"
|
||||
msgstr "İşlem Ayarlarını Kopyala"
|
||||
msgstr "İşlem ayarlarını kopyala"
|
||||
|
||||
msgid "Paste Process Settings"
|
||||
msgstr "İşlem Ayarlarını Yapıştır"
|
||||
msgstr "İşlem ayarlarını yapıştır"
|
||||
|
||||
msgid "Edit print parameters for a single object"
|
||||
msgstr "Tek bir nesne için yazdırma parametrelerini düzenleme"
|
||||
@@ -3478,7 +3474,7 @@ msgid "More"
|
||||
msgstr "Daha"
|
||||
|
||||
msgid "Open Preferences"
|
||||
msgstr "Tercihleri Aç"
|
||||
msgstr "Tercihleri aç"
|
||||
|
||||
msgid "Open next tip"
|
||||
msgstr "Sonraki ipucunu aç"
|
||||
@@ -5450,10 +5446,10 @@ msgid "Acceleration"
|
||||
msgstr "Hızlanma"
|
||||
|
||||
msgid "Jerk"
|
||||
msgstr "Jerk"
|
||||
msgstr "Sarsıntı"
|
||||
|
||||
msgid "Fan Speed"
|
||||
msgstr "Fan hızı"
|
||||
msgstr "Fan Hızı"
|
||||
|
||||
msgid "Flow"
|
||||
msgstr "Akış"
|
||||
@@ -5468,7 +5464,7 @@ msgid "Layer Time"
|
||||
msgstr "Katman Süresi"
|
||||
|
||||
msgid "Layer Time (log)"
|
||||
msgstr "Katman Süresi (günlük)"
|
||||
msgstr "Katman Süresi (log)"
|
||||
|
||||
msgid "Pressure Advance"
|
||||
msgstr "Basınç İlerlemesi"
|
||||
@@ -5477,10 +5473,10 @@ msgid "Noop"
|
||||
msgstr "Hayır"
|
||||
|
||||
msgid "Retract"
|
||||
msgstr "Geri Çekme"
|
||||
msgstr "Geri çekme"
|
||||
|
||||
msgid "Unretract"
|
||||
msgstr "İleri İtme"
|
||||
msgstr "İleri itme"
|
||||
|
||||
msgid "Seam"
|
||||
msgstr "Dikiş"
|
||||
@@ -5578,7 +5574,7 @@ msgid "Acceleration: "
|
||||
msgstr "İvme: "
|
||||
|
||||
msgid "Jerk: "
|
||||
msgstr "Jerk: "
|
||||
msgstr "Sarsıntı: "
|
||||
|
||||
msgid "PA: "
|
||||
msgstr "PA: "
|
||||
@@ -5608,7 +5604,7 @@ msgid "Actual speed profile"
|
||||
msgstr "Gerçek hız profili"
|
||||
|
||||
msgid "Statistics of All Plates"
|
||||
msgstr "Tüm Plakaların İstatistikleri"
|
||||
msgstr "Tüm plakaların istatistikleri"
|
||||
|
||||
msgid "Display"
|
||||
msgstr "Ekran"
|
||||
@@ -5708,7 +5704,7 @@ msgid "Acceleration (mm/s²)"
|
||||
msgstr "İvme (mm/s²)"
|
||||
|
||||
msgid "Jerk (mm/s)"
|
||||
msgstr "Jerk (mm/s)"
|
||||
msgstr "Sarsıntı (mm/s)"
|
||||
|
||||
msgid "Fan speed (%)"
|
||||
msgstr "Fan hızı (%)"
|
||||
@@ -5759,9 +5755,8 @@ msgstr "Normal mod"
|
||||
msgid "Total Filament"
|
||||
msgstr "Toplam filament"
|
||||
|
||||
# AI Translated
|
||||
msgid "Model Filament"
|
||||
msgstr "Model Filamenti"
|
||||
msgstr "Model filamenti"
|
||||
|
||||
msgid "Prepare time"
|
||||
msgstr "Hazırlık süresi"
|
||||
@@ -6282,20 +6277,19 @@ msgid "Setup Wizard"
|
||||
msgstr "Kurulum sihirbazı"
|
||||
|
||||
msgid "Show Configuration Folder"
|
||||
msgstr "Yapılandırma Klasörünü Göster"
|
||||
msgstr "Yapılandırma klasörünü göster"
|
||||
|
||||
# AI Translated
|
||||
msgid "Troubleshoot Center"
|
||||
msgstr "Sorun Giderme Merkezi"
|
||||
msgstr "Sorun giderme merkezi"
|
||||
|
||||
msgid "Open Network Test"
|
||||
msgstr "Ağ Testini Aç"
|
||||
msgstr "Ağ testini aç"
|
||||
|
||||
msgid "Show Tip of the Day"
|
||||
msgstr "Günün İpucunu Göster"
|
||||
msgstr "Günün ipucunu göster"
|
||||
|
||||
msgid "Check for Updates"
|
||||
msgstr "Güncellemeleri Kontrol Et"
|
||||
msgstr "Güncellemeleri kontrol et"
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "&About %s"
|
||||
@@ -6349,7 +6343,7 @@ msgid "Recent files"
|
||||
msgstr "Son dosyalar"
|
||||
|
||||
msgid "Save Project"
|
||||
msgstr "Projeyi Kaydet"
|
||||
msgstr "Projeyi kaydet"
|
||||
|
||||
msgid "Save current project to file"
|
||||
msgstr "Mevcut projeyi dosyaya kaydet"
|
||||
@@ -6415,13 +6409,13 @@ msgid "Export toolpaths as OBJ"
|
||||
msgstr "Takımyollarını OBJ olarak dışa aktar"
|
||||
|
||||
msgid "Export Preset Bundle"
|
||||
msgstr "Ön Ayar Paketini Dışa Aktar"
|
||||
msgstr "Ön ayar paketini dışa aktar"
|
||||
|
||||
msgid "Export current configuration to files"
|
||||
msgstr "Geçerli yapılandırmayı dosyalara aktar"
|
||||
|
||||
msgid "Export"
|
||||
msgstr "Dışa Aktar"
|
||||
msgstr "Dışa aktar"
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "Çıkış"
|
||||
@@ -6478,13 +6472,13 @@ msgid "Deselects all objects"
|
||||
msgstr "Tüm nesnelerin seçimini kaldırır"
|
||||
|
||||
msgid "Use Perspective View"
|
||||
msgstr "Perspektif Görünüm"
|
||||
msgstr "Perspektif görünüm"
|
||||
|
||||
msgid "Use Orthogonal View"
|
||||
msgstr "Ortogonal Görünüm"
|
||||
msgstr "Ortogonal görünüm"
|
||||
|
||||
msgid "Auto Perspective"
|
||||
msgstr "Otomatik Perspektif"
|
||||
msgstr "Otomatik perspektif"
|
||||
|
||||
msgid "Automatically switch between orthographic and perspective when changing from top/bottom/side views."
|
||||
msgstr "Üst/Alt/Yan görünümler arasında geçiş yaparken ortografik ve perspektif arasında otomatik olarak geçiş yapın."
|
||||
@@ -6496,37 +6490,37 @@ msgid "Show G-code window in Preview scene."
|
||||
msgstr "Previce sahnesinde G-kodu penceresini göster."
|
||||
|
||||
msgid "Show 3D Navigator"
|
||||
msgstr "3D Gezgini Göster"
|
||||
msgstr "3D gezgini göster"
|
||||
|
||||
msgid "Show 3D navigator in Prepare and Preview scene."
|
||||
msgstr "Hazırlama ve Önizleme sahnesinde 3D gezgini göster."
|
||||
|
||||
msgid "Show Gridlines"
|
||||
msgstr "Kılavuz Çizgilerini Göster"
|
||||
msgstr "Kılavuz çizgilerini göster"
|
||||
|
||||
msgid "Show Gridlines on plate"
|
||||
msgstr "Kılavuz Çizgilerini plaka üzerinde göster"
|
||||
|
||||
msgid "Reset Window Layout"
|
||||
msgstr "Pencere Düzenini Sıfırla"
|
||||
msgstr "Pencere düzenini sıfırla"
|
||||
|
||||
msgid "Reset to default window layout"
|
||||
msgstr "Varsayılan pencere düzenine sıfırla"
|
||||
|
||||
msgid "Show &Labels"
|
||||
msgstr "Etiketleri Göster"
|
||||
msgstr "Etiketleri göster"
|
||||
|
||||
msgid "Show object labels in 3D scene."
|
||||
msgstr "3B sahnede nesne etiketlerini göster."
|
||||
|
||||
msgid "Show &Overhang"
|
||||
msgstr "Çıkıntıyı Göster"
|
||||
msgstr "Çıkıntıyı göster"
|
||||
|
||||
msgid "Show object overhang highlight in 3D scene."
|
||||
msgstr "3B sahnede nesne çıkıntısı vurgusunu göster."
|
||||
|
||||
msgid "Show Selected Outline (beta)"
|
||||
msgstr "Seçilen Taslağı Göster (Deneysel)"
|
||||
msgstr "Seçilen taslağı göster (deneysel)"
|
||||
|
||||
msgid "Show outline around selected object in 3D scene."
|
||||
msgstr "3D sahnede seçilen nesnenin etrafındaki ana hatları göster."
|
||||
@@ -6542,13 +6536,11 @@ msgstr "Düzen"
|
||||
msgid "View"
|
||||
msgstr "Görünüm"
|
||||
|
||||
# AI Translated
|
||||
msgid "Preset Bundle"
|
||||
msgstr "Ön Ayar Paketi"
|
||||
msgstr "Ön ayar paketi"
|
||||
|
||||
# AI Translated
|
||||
msgid "Sync Presets"
|
||||
msgstr "Ön Ayarları Eşitle"
|
||||
msgstr "Ön ayarları eşitle"
|
||||
|
||||
# AI Translated
|
||||
msgid "Pull and apply the latest presets from OrcaCloud"
|
||||
@@ -6590,10 +6582,10 @@ msgid "Cornering calibration"
|
||||
msgstr "Viraj kalibrasyonu"
|
||||
|
||||
msgid "Input Shaping Frequency"
|
||||
msgstr "Input shaping Frekansı"
|
||||
msgstr "Input shaping frekansı"
|
||||
|
||||
msgid "Input Shaping Damping/zeta factor"
|
||||
msgstr "Input shaping Sönümleme/zeta faktörü"
|
||||
msgstr "Input shaping sönümleme/zeta faktörü"
|
||||
|
||||
msgid "Input Shaping"
|
||||
msgstr "Input shaping"
|
||||
@@ -6601,9 +6593,8 @@ msgstr "Input shaping"
|
||||
msgid "VFA"
|
||||
msgstr "VFA"
|
||||
|
||||
# AI Translated
|
||||
msgid "Calibration Guide"
|
||||
msgstr "Kalibrasyon Kılavuzu"
|
||||
msgstr "Kalibrasyon kılavuzu"
|
||||
|
||||
msgid "&Open G-code"
|
||||
msgstr "&G kodunu aç"
|
||||
@@ -8219,9 +8210,8 @@ msgstr "Bu dosyalar birden fazla parçadan oluşan tek bir nesne olarak mı yük
|
||||
msgid "An object with multiple parts was detected"
|
||||
msgstr "Birden fazla parçaya sahip nesne algılandı"
|
||||
|
||||
# AI Translated
|
||||
msgid "Auto-Drop"
|
||||
msgstr "Otomatik Bırakma"
|
||||
msgstr "Otomatik düşür"
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid "Connected printer is %s. It must match the project preset for printing.\n"
|
||||
@@ -8296,9 +8286,8 @@ msgstr "Seçilen nesne bölünemedi."
|
||||
msgid "Split to Objects"
|
||||
msgstr "Nesnelere Ayır"
|
||||
|
||||
# AI Translated
|
||||
msgid "Disable Auto-Drop to preserve Z positioning?\n"
|
||||
msgstr "Z konumunu korumak için Otomatik Bırakma devre dışı bırakılsın mı?\n"
|
||||
msgstr "Z konumunu korumak için Otomatik düşürme devre dışı bırakılsın mı?\n"
|
||||
|
||||
# AI Translated
|
||||
msgid "Object with floating parts was detected"
|
||||
@@ -8433,7 +8422,7 @@ msgid "Creating a new project"
|
||||
msgstr "Yeni bir proje oluşturma"
|
||||
|
||||
msgid "Load project"
|
||||
msgstr "Projeyi Aç"
|
||||
msgstr "Projeyi aç"
|
||||
|
||||
msgid ""
|
||||
"Failed to save the project.\n"
|
||||
@@ -8869,10 +8858,10 @@ msgid "Current Association: "
|
||||
msgstr "Mevcut Bağlantı: "
|
||||
|
||||
msgid "Current Instance"
|
||||
msgstr "Mevcut Örnek"
|
||||
msgstr "Mevcut Kopya"
|
||||
|
||||
msgid "Current Instance Path: "
|
||||
msgstr "Mevcut Örnek Yolu: "
|
||||
msgstr "Mevcut Kopya Yolu: "
|
||||
|
||||
msgid "General"
|
||||
msgstr "Genel"
|
||||
@@ -10708,7 +10697,7 @@ msgid "Reserved keywords found"
|
||||
msgstr "Ayrılmış anahtar kelimeler bulundu"
|
||||
|
||||
msgid "Setting Overrides"
|
||||
msgstr "Ayarların Üzerine Yazma"
|
||||
msgstr "Ayarların Üzerine Yaz"
|
||||
|
||||
msgid "Basic information"
|
||||
msgstr "Temel Bilgiler"
|
||||
@@ -12227,7 +12216,7 @@ msgid "Group error in manual mode. Please check nozzle count or regroup."
|
||||
msgstr "Elle modda gruplama hatası. Lütfen nozul sayısını denetleyin veya yeniden gruplayın."
|
||||
|
||||
msgid "Internal Bridge"
|
||||
msgstr "İç Köprü"
|
||||
msgstr "İç köprü"
|
||||
|
||||
msgid "undefined error"
|
||||
msgstr "bilinmeyen hata"
|
||||
@@ -12920,7 +12909,6 @@ msgstr "Çıkıntı bu belirtilen eşiği aştığında, soğutma fanını aşa
|
||||
msgid "External bridge infill direction"
|
||||
msgstr "Dış köprü dolgu yönü"
|
||||
|
||||
# AI Translated
|
||||
#, no-c-format, no-boost-format
|
||||
msgid ""
|
||||
"External Bridging angle override.\n"
|
||||
@@ -12937,14 +12925,13 @@ msgstr ""
|
||||
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
|
||||
" - Mutlak koordinatlar\n"
|
||||
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\n"
|
||||
" - En uygun otomatik açı + bu değer: 'Göreli Köprü Açısı' etkinse\n"
|
||||
" - En uygun otomatik açı + bu değer: ‘Göreceli Köprü Açısı' etkinse\n"
|
||||
"\n"
|
||||
"Sıfır mutlak açı için 180° kullanın."
|
||||
|
||||
msgid "Internal bridge infill direction"
|
||||
msgstr "İç köprü dolgu yönü"
|
||||
|
||||
# AI Translated
|
||||
msgid ""
|
||||
"Internal Bridging angle override.\n"
|
||||
"If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n"
|
||||
@@ -12960,13 +12947,12 @@ msgstr ""
|
||||
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
|
||||
" - Mutlak koordinatlar\n"
|
||||
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\n"
|
||||
" - En uygun otomatik açı + bu değer: 'Göreli Köprü Açısı' etkinse\n"
|
||||
" - En uygun otomatik açı + bu değer: 'Göreceli Köprü Açısı' etkinse\n"
|
||||
"\n"
|
||||
"Sıfır mutlak açı için 180° kullanın."
|
||||
|
||||
# AI Translated
|
||||
msgid "Relative bridge angle"
|
||||
msgstr "Göreli köprü açısı"
|
||||
msgstr "Göreceli köprü açısı"
|
||||
|
||||
# AI Translated
|
||||
msgid "When enabled, the bridge angle values are added to the automatically calculated bridge direction instead of overriding it."
|
||||
@@ -13413,7 +13399,7 @@ msgstr ""
|
||||
"Not: Elde edilen değer ilk katman akış oranından etkilenmez."
|
||||
|
||||
msgid "Brim follows compensated outline"
|
||||
msgstr "Kenar telafi edilen taslağı takip ediyor"
|
||||
msgstr "Kenar toleranslı dış sınırı takip etsin"
|
||||
|
||||
# AI Translated
|
||||
msgid ""
|
||||
@@ -13724,13 +13710,13 @@ msgid ""
|
||||
msgstr ""
|
||||
"Dikey kabuk kalınlığını garanti etmek için eğimli yüzeylerin yakınına katı dolgu ekleyin (üst + alt katı katmanlar)\n"
|
||||
"Yok: Hiçbir yere katı dolgu eklenmez. Dikkat: Modelinizin eğimli yüzeyleri varsa bu seçeneği dikkatli kullanın.\n"
|
||||
"Yalnızca kritik: Duvarlar için katı dolgu eklemekten kaçının\n"
|
||||
"Kritik: Duvarlar için katı dolgu eklemekten kaçının\n"
|
||||
"Orta: Yalnızca çok eğimli yüzeyler için katı dolgu ekleyin\n"
|
||||
"Hepsi: Tüm uygun eğimli yüzeyler için katı dolgu ekleyin\n"
|
||||
"Varsayılan değer Tümü'dür."
|
||||
|
||||
msgid "Critical Only"
|
||||
msgstr "Yalnızca kritik"
|
||||
msgstr "Kritik"
|
||||
|
||||
msgid "Moderate"
|
||||
msgstr "Orta"
|
||||
@@ -14247,7 +14233,7 @@ msgid "By First filament"
|
||||
msgstr "İlk filamente göre"
|
||||
|
||||
msgid "By Highest Temp"
|
||||
msgstr "En Yüksek Sıcaklığa Göre"
|
||||
msgstr "En yüksek sıcaklığa göre"
|
||||
|
||||
msgid "Filament diameter is used to calculate extrusion variables in G-code, so it is important that this is accurate and precise."
|
||||
msgstr "Filament çapı, gcode'da ekstrüzyonu hesaplamak için kullanılır; bu nedenle önemlidir ve doğru olmalıdır."
|
||||
@@ -14685,10 +14671,10 @@ msgid "Marlin Firmware Junction Deviation (replaces the traditional XY Jerk sett
|
||||
msgstr "Marlin Firmware Köşe Sapması (geleneksel XY Sarsıntı ayarının yerini alır)"
|
||||
|
||||
msgid "Jerk of outer walls."
|
||||
msgstr "Dış duvar JERK değeri."
|
||||
msgstr "Dış duvar sarsıntı değeri."
|
||||
|
||||
msgid "Jerk of inner walls."
|
||||
msgstr "İç duvarlar JERK değeri."
|
||||
msgstr "İç duvarlar sarsıntı değeri."
|
||||
|
||||
msgid "Jerk for top surface."
|
||||
msgstr "Üst yüzey için JERK değeri."
|
||||
@@ -15721,7 +15707,7 @@ msgid ""
|
||||
"If your Marlin 2 printer uses Classic Jerk set this value to 0.)"
|
||||
msgstr ""
|
||||
"Maksimum bağlantı sapması (M205 J, yalnızca Marlin Aygıt Yazılımı için JD > 0 ise geçerlidir)\n"
|
||||
"Marlin 2 yazıcınız Classic Jerk kullanıyorsa bu değeri 0 olarak ayarlayın.)"
|
||||
"Marlin 2 yazıcınız Classic sarsıntı kullanıyorsa bu değeri 0 olarak ayarlayın.)"
|
||||
|
||||
msgid "Minimum speed for extruding"
|
||||
msgstr "Ekstrüzyon için minimum hız"
|
||||
@@ -16914,7 +16900,7 @@ msgid "This setting only generates supports that begin on the build plate."
|
||||
msgstr "Model yüzeyinde destek oluşturmayın, yalnızca baskı plakasında."
|
||||
|
||||
msgid "Support critical regions only"
|
||||
msgstr "Yalnızca kritik bölgeleri destekleyin"
|
||||
msgstr "Kritik bölgeleri destekleyin"
|
||||
|
||||
msgid "Only create support for critical regions including sharp tail, cantilever, etc."
|
||||
msgstr "Yalnızca keskin kuyruk, konsol vb. gibi kritik bölgeler için destek oluşturun."
|
||||
@@ -19288,13 +19274,13 @@ msgid ""
|
||||
"To test Classic Jerk, set 'Maximum Junction Deviation' in Motion ability to 0."
|
||||
msgstr ""
|
||||
"Marlin 2 Kavşak Sapması tespit edildi:\n"
|
||||
"Classic Jerk'i test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı 0'a ayarlayın."
|
||||
"Classic sarsıntıyı test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı 0'a ayarlayın."
|
||||
|
||||
msgid ""
|
||||
"Marlin 2 Classic Jerk detected:\n"
|
||||
"To test Junction Deviation, set 'Maximum Junction Deviation' in Motion ability to a value > 0."
|
||||
msgstr ""
|
||||
"Marlin 2 Classic Jerk tespit edildi:\n"
|
||||
"Marlin 2 Classic sarsıntı tespit edildi:\n"
|
||||
"Kavşak Sapmasını test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı > 0 değerine ayarlayın."
|
||||
|
||||
msgid ""
|
||||
@@ -19538,7 +19524,7 @@ msgid "Start Test Single-Thread"
|
||||
msgstr "Tek İş Parçacığı Testini Başlat"
|
||||
|
||||
msgid "Export Log"
|
||||
msgstr "Logu Dışa Aktar"
|
||||
msgstr "Logu dışa aktar"
|
||||
|
||||
msgid "OrcaSlicer Version:"
|
||||
msgstr "OrcaSlicer Sürümü:"
|
||||
@@ -20281,9 +20267,8 @@ msgstr "Sistem klasörü silinemedi..."
|
||||
msgid "Failed to determine executable path."
|
||||
msgstr "Yürütülebilir dosya yolu belirlenemedi."
|
||||
|
||||
# AI Translated
|
||||
msgid "Failed to launch a new instance."
|
||||
msgstr "Yeni bir örnek başlatılamadı."
|
||||
msgstr "Yeni bir kopya başlatılamadı."
|
||||
|
||||
# AI Translated
|
||||
msgid "log(s)"
|
||||
|
||||
@@ -149,6 +149,8 @@ set(lisbslic3r_sources
|
||||
Fill/FillConcentric.hpp
|
||||
Fill/FillConcentricInternal.cpp
|
||||
Fill/FillConcentricInternal.hpp
|
||||
Fill/FillCornerSmoothing.cpp
|
||||
Fill/FillCornerSmoothing.hpp
|
||||
Fill/Fill.cpp
|
||||
Fill/FillCrossHatch.cpp
|
||||
Fill/FillCrossHatch.hpp
|
||||
|
||||
@@ -970,9 +970,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
region_config.sparse_infill_rotate_template.value);
|
||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||
|
||||
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
|
||||
// FillHilbertCurve::generate clamps and validates the value itself.
|
||||
if (params.pattern == ipHilbertCurve)
|
||||
// Orca: the smoothing factor only applies to the sparse infill patterns that
|
||||
// implement it. The fills clamp and validate the value themselves.
|
||||
if (is_smoothable_infill_pattern(params.pattern, params.multiline))
|
||||
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
||||
} else {
|
||||
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "Fill3DHoneycomb.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -271,6 +272,9 @@ void Fill3DHoneycomb::_fill_surface_single(
|
||||
for (Polyline &pl : polylines){
|
||||
pl.translate(bb.min);
|
||||
pl.simplify(5 * spacing); // simplify to 5x line width
|
||||
// Orca: round the corners of the octahedral wave. The layers where the wave degenerates to a
|
||||
// straight line have no corner to round.
|
||||
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
|
||||
}
|
||||
|
||||
// Apply multiline offset if needed
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "Arachne/WallToolPaths.hpp"
|
||||
|
||||
#include "FillConcentric.hpp"
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include <libslic3r/ShortestPath.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -32,12 +33,32 @@ void FillConcentric::_fill_surface_single(
|
||||
|
||||
Polygons loops = to_polygons(contracted);
|
||||
|
||||
ExPolygons last { std::move(contracted) };
|
||||
ExPolygons last { contracted };
|
||||
while (! last.empty()) {
|
||||
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
|
||||
append(loops, to_polygons(last));
|
||||
}
|
||||
|
||||
// Orca: round the corners of the loops. Unlike the other patterns these are never clipped to the
|
||||
// fill region - they are its offsets - so a corner may only be rounded where the curve replacing it
|
||||
// stays inside. Rounding cuts toward the inside of the turn, which around a hole, at a concave
|
||||
// feature or across a thin region is outside the fill and would put the extrusion over a wall.
|
||||
// The reach is capped at half the distance between two loops as well: a loop is as long as the
|
||||
// object, and a corner cut by half of its side would swallow the neighbouring loops.
|
||||
auto corner_stays_inside = [&contracted](const Vec2d &from, const Vec2d &to) {
|
||||
// The straight chord between the ends of the curve is the deepest the curve can cut.
|
||||
for (const double t : { 0.25, 0.5, 0.75 }) {
|
||||
const Vec2d sample = from + t * (to - from);
|
||||
const Point point(coord_t(sample.x()), coord_t(sample.y()));
|
||||
if (std::none_of(contracted.begin(), contracted.end(),
|
||||
[&point](const ExPolygon ®ion) { return region.contains(point); }))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
smooth_polygons_corners(loops, params.smooth_factor, scaled<double>(params.resolution), 0.5 * distance,
|
||||
corner_stays_inside);
|
||||
|
||||
// generate paths from the outermost to the innermost, to avoid
|
||||
// adhesion problems of the first central tiny loops
|
||||
loops = union_pt_chained_outside_in(loops);
|
||||
|
||||
226
src/libslic3r/Fill/FillCornerSmoothing.cpp
Normal file
226
src/libslic3r/Fill/FillCornerSmoothing.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include <array>
|
||||
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Turns sharper than this are left untouched: both ends of the curve replacing such a corner nearly
|
||||
// coincide, so the corner would be rounded into a degenerate loop instead of a hairpin.
|
||||
static constexpr const double min_smoothed_turn_cosine = -0.9;
|
||||
|
||||
// The control points are expressed in the (incoming, outgoing) basis of the corner, which is not
|
||||
// orthonormal for turns other than a right angle.
|
||||
using QuinticBezier = std::array<Vec2d, 6>;
|
||||
|
||||
static bool is_bezier_flat(const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation)
|
||||
{
|
||||
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
|
||||
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
|
||||
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
|
||||
// comparing squared values avoids a square root.
|
||||
auto in_plane = [&incoming, &outgoing](const Vec2d &c) { return c.x() * incoming + c.y() * outgoing; };
|
||||
const Vec2d chord = in_plane(curve.back() - curve.front());
|
||||
const double chord_length_sq = chord.squaredNorm();
|
||||
const double max_cross_sq = deviation * deviation * chord_length_sq;
|
||||
|
||||
for (size_t i = 1; i + 1 < curve.size(); ++i) {
|
||||
const Vec2d offset = in_plane(curve[i] - curve.front());
|
||||
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
|
||||
if (cross * cross > max_cross_sq)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
|
||||
{
|
||||
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
|
||||
// control point to the left half and one to the right half; the latter is filled backwards to keep
|
||||
// both resulting control polygons in their original parameter direction.
|
||||
QuinticBezier subdivision = curve;
|
||||
left.front() = subdivision.front();
|
||||
right.back() = subdivision.back();
|
||||
for (size_t level = 1; level < curve.size(); ++level) {
|
||||
for (size_t i = 0; i + level < curve.size(); ++i)
|
||||
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
|
||||
left[level] = subdivision.front();
|
||||
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_bezier(
|
||||
const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation, std::vector<Vec2d> &output)
|
||||
{
|
||||
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
|
||||
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
|
||||
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
|
||||
static constexpr size_t max_depth = 16;
|
||||
|
||||
std::vector<QuinticBezier> subcurves(2);
|
||||
subdivide_bezier(curve, subcurves[0], subcurves[1]);
|
||||
|
||||
for (size_t depth = 1; depth < max_depth; ++depth) {
|
||||
bool all_flat = true;
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
if (!is_bezier_flat(c, incoming, outgoing, deviation)) {
|
||||
all_flat = false;
|
||||
break;
|
||||
}
|
||||
if (all_flat)
|
||||
break;
|
||||
std::vector<QuinticBezier> finer(subcurves.size() * 2);
|
||||
for (size_t i = 0; i < subcurves.size(); ++i)
|
||||
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
|
||||
subcurves = std::move(finer);
|
||||
}
|
||||
|
||||
// The curve start is deliberately omitted so it can be shared with the straight leg feeding into it.
|
||||
output.clear();
|
||||
output.reserve(subcurves.size());
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
output.emplace_back(c.back());
|
||||
}
|
||||
|
||||
const std::vector<Vec2d>& CornerSmoother::curve_coefficients(
|
||||
const double corner_distance, const Vec2d &incoming, const Vec2d &outgoing)
|
||||
{
|
||||
const double cosine = incoming.dot(outgoing);
|
||||
// Corners of the same size and turn angle are congruent, so they flatten identically. An infill
|
||||
// path walks over the very same corner over and over again, the Hilbert curve over a single one.
|
||||
if (m_has_cached_coefficients && corner_distance == m_cached_distance && cosine == m_cached_cosine)
|
||||
return m_cached_coefficients;
|
||||
|
||||
// One canonical corner running from -corner_distance along the incoming leg to corner_distance
|
||||
// along the outgoing one. At each end, the first three control points are collinear and equally
|
||||
// spaced: the tangent follows the adjoining straight leg and the second derivative is zero. The
|
||||
// endpoint curvature is therefore zero, giving G2 joins to both legs.
|
||||
const double d = corner_distance;
|
||||
const QuinticBezier corner_curve {{
|
||||
{-d, 0.}, {-0.7 * d, 0.}, {-0.4 * d, 0.}, {0., 0.4 * d}, {0., 0.7 * d}, {0., d}
|
||||
}};
|
||||
// Retain a finite positive tolerance if the smoother was set up with an invalid one.
|
||||
const double deviation = m_tolerance > 0. && std::isfinite(m_tolerance) ? m_tolerance : EPSILON;
|
||||
flatten_bezier(corner_curve, incoming, outgoing, deviation, m_cached_coefficients);
|
||||
|
||||
m_cached_distance = corner_distance;
|
||||
m_cached_cosine = cosine;
|
||||
m_has_cached_coefficients = true;
|
||||
return m_cached_coefficients;
|
||||
}
|
||||
|
||||
void CornerSmoother::round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next)
|
||||
{
|
||||
m_corner_points.clear();
|
||||
|
||||
const Vec2d incoming_leg = corner - previous;
|
||||
const Vec2d outgoing_leg = next - corner;
|
||||
const double incoming_length = incoming_leg.norm();
|
||||
const double outgoing_length = outgoing_leg.norm();
|
||||
if (incoming_length < EPSILON || outgoing_length < EPSILON) {
|
||||
m_corner_points.emplace_back(corner);
|
||||
return;
|
||||
}
|
||||
|
||||
const Vec2d incoming = incoming_leg / incoming_length;
|
||||
const Vec2d outgoing = outgoing_leg / outgoing_length;
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
// A collinear vertex is no corner at all, and a hairpin cannot be rounded, see above.
|
||||
if (std::abs(cross) < EPSILON || incoming.dot(outgoing) < min_smoothed_turn_cosine) {
|
||||
m_corner_points.emplace_back(corner);
|
||||
return;
|
||||
}
|
||||
|
||||
// Consuming at most half of the shorter leg keeps the curves of two adjacent corners apart.
|
||||
double corner_distance = m_corner_distance_ratio * std::min(incoming_length, outgoing_length);
|
||||
if (m_max_corner_distance > 0.)
|
||||
corner_distance = std::min(corner_distance, m_max_corner_distance);
|
||||
|
||||
const Vec2d curve_start = corner - corner_distance * incoming;
|
||||
const Vec2d curve_end = corner + corner_distance * outgoing;
|
||||
if (m_corner_filter && !m_corner_filter(curve_start, curve_end)) {
|
||||
m_corner_points.emplace_back(corner);
|
||||
return;
|
||||
}
|
||||
|
||||
const std::vector<Vec2d> &coefficients = curve_coefficients(corner_distance, incoming, outgoing);
|
||||
m_corner_points.reserve(coefficients.size() + 1);
|
||||
m_corner_points.emplace_back(curve_start);
|
||||
for (const Vec2d &coefficient : coefficients)
|
||||
m_corner_points.emplace_back(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
|
||||
}
|
||||
|
||||
// Rounds the corners of a scaled point sequence. A polygon closes implicitly, so all of its vertices
|
||||
// are corners; a polyline is an open path that keeps both of its ends, even where they coincide - a
|
||||
// path returning to where it started retraces its way back and is not a loop.
|
||||
static Points smooth_corners(const Points &points, const bool polygon, CornerSmoother &smoother)
|
||||
{
|
||||
// A polygon has no free ends, so its first vertex is a corner like any other. Rounding it takes
|
||||
// feeding the smoother the last vertex first, whose own output point is then dropped again.
|
||||
size_t skip = polygon ? 1 : 0;
|
||||
|
||||
Points smoothed;
|
||||
smoothed.reserve(2 * points.size());
|
||||
auto emit = [&smoothed, &skip](const Vec2d &point) {
|
||||
if (skip > 0) {
|
||||
--skip;
|
||||
return;
|
||||
}
|
||||
smoothed.emplace_back(coord_t(std::floor(point.x() + 0.5)), coord_t(std::floor(point.y() + 0.5)));
|
||||
};
|
||||
|
||||
if (polygon)
|
||||
smoother.push(points.back().cast<double>(), emit);
|
||||
for (const Point &point : points)
|
||||
smoother.push(point.cast<double>(), emit);
|
||||
if (polygon)
|
||||
// Wrap the first vertex around, so that the last one is a corner as well.
|
||||
smoother.push(points.front().cast<double>(), emit);
|
||||
smoother.flush(emit);
|
||||
|
||||
if (polygon)
|
||||
// The flushed point is the wrapped first vertex, which a polygon does not store.
|
||||
smoothed.pop_back();
|
||||
return smoothed;
|
||||
}
|
||||
|
||||
void smooth_polyline_corners(Polyline &polyline, const double smooth_factor, const double tolerance,
|
||||
const double max_corner_distance, const CornerFilter &corner_filter)
|
||||
{
|
||||
CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
|
||||
if (!smoother.enabled() || polyline.size() < 3)
|
||||
return;
|
||||
|
||||
polyline.points = smooth_corners(polyline.points, false, smoother);
|
||||
// Rounding back to the integer grid may collapse neighbouring samples of a curve.
|
||||
polyline.remove_duplicate_points();
|
||||
}
|
||||
|
||||
void smooth_polylines_corners(Polylines &polylines, const double smooth_factor, const double tolerance,
|
||||
const double max_corner_distance, const CornerFilter &corner_filter)
|
||||
{
|
||||
if (sanitize_smooth_factor(smooth_factor) == 0.)
|
||||
return;
|
||||
for (Polyline &polyline : polylines)
|
||||
smooth_polyline_corners(polyline, smooth_factor, tolerance, max_corner_distance, corner_filter);
|
||||
}
|
||||
|
||||
void smooth_polygons_corners(Polygons &polygons, const double smooth_factor, const double tolerance,
|
||||
const double max_corner_distance, const CornerFilter &corner_filter)
|
||||
{
|
||||
CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
|
||||
if (!smoother.enabled())
|
||||
return;
|
||||
|
||||
for (Polygon &polygon : polygons) {
|
||||
if (polygon.size() < 3)
|
||||
continue;
|
||||
polygon.points = smooth_corners(polygon.points, true, smoother);
|
||||
polygon.remove_duplicate_points();
|
||||
// The curves of the first and of the last corner may have met on the segment they share. A
|
||||
// polygon closes implicitly, so it must not repeat its first vertex at the end.
|
||||
if (polygon.points.size() > 1 && polygon.points.front() == polygon.points.back())
|
||||
polygon.points.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
108
src/libslic3r/Fill/FillCornerSmoothing.hpp
Normal file
108
src/libslic3r/Fill/FillCornerSmoothing.hpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Point.hpp"
|
||||
#include "../Polygon.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Orca: NaN or infinite factors disable the smoothing, everything else is clamped to <0, 1>.
|
||||
inline double sanitize_smooth_factor(double smooth_factor)
|
||||
{
|
||||
return std::isfinite(smooth_factor) ? std::clamp(smooth_factor, 0., 1.) : 0.;
|
||||
}
|
||||
|
||||
// Decides whether a corner may be replaced by the curve that leaves the path at `from` and rejoins it
|
||||
// at `to`, both in the coordinate system of the pushed points. Rounding cuts toward the inside of the
|
||||
// turn, so a path that is not clipped to the fill region afterwards needs this to stay inside it.
|
||||
using CornerFilter = std::function<bool(const Vec2d &from, const Vec2d &to)>;
|
||||
|
||||
// Orca: Replaces the sharp vertices of an infill path with curves that join the adjoining straight
|
||||
// legs with a continuous curvature, so the toolhead does not have to stop in every corner.
|
||||
// Points are pushed one by one, because the plane path fills produce their path on the fly, and
|
||||
// every point of the smoothed path is handed over to the caller supplied emit callback.
|
||||
// Fully smoothed adjacent corners meet at the midpoint of the segment they share, so the emitted
|
||||
// points may collapse onto each other once rounded to the integer grid of the caller. Dropping such
|
||||
// duplicates is left to the caller, which is the only one knowing that grid.
|
||||
class CornerSmoother
|
||||
{
|
||||
public:
|
||||
// tolerance is the maximum chordal deviation of the flattened curves, in the units of the pushed
|
||||
// points. max_corner_distance caps how far a curve may reach along a leg, in the same units; it
|
||||
// bounds how far a rounded corner moves away from the original path, which matters where the legs
|
||||
// are much longer than the spacing of the pattern. Zero leaves the reach uncapped.
|
||||
CornerSmoother(double smooth_factor, double tolerance, double max_corner_distance = 0.,
|
||||
CornerFilter corner_filter = {})
|
||||
: m_corner_distance_ratio(0.5 * sanitize_smooth_factor(smooth_factor)), m_tolerance(tolerance),
|
||||
m_max_corner_distance(max_corner_distance), m_corner_filter(std::move(corner_filter))
|
||||
{}
|
||||
|
||||
bool enabled() const { return m_corner_distance_ratio > 0.; }
|
||||
|
||||
template<typename Emit> void push(const Vec2d &point, Emit &emit)
|
||||
{
|
||||
if (m_pending == 0) {
|
||||
emit(point);
|
||||
m_previous = point;
|
||||
} else if (m_pending > 1) {
|
||||
round_corner(m_previous, m_corner, point);
|
||||
for (const Vec2d &corner_point : m_corner_points)
|
||||
emit(corner_point);
|
||||
m_previous = m_corner;
|
||||
}
|
||||
m_corner = point;
|
||||
m_pending = std::min(m_pending + 1, 2);
|
||||
}
|
||||
|
||||
// Emits the last point of the path and prepares the smoother for a new one.
|
||||
template<typename Emit> void flush(Emit &emit)
|
||||
{
|
||||
if (m_pending > 1)
|
||||
emit(m_corner);
|
||||
m_pending = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Fills m_corner_points with the points replacing the corner vertex.
|
||||
void round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next);
|
||||
// Flattens the canonical corner curve of the given size and turn into coordinates of the
|
||||
// (incoming, outgoing) basis of the corner. Cached, as an infill path repeats the same corner.
|
||||
const std::vector<Vec2d>& curve_coefficients(double corner_distance, const Vec2d &incoming, const Vec2d &outgoing);
|
||||
|
||||
// Fraction of the shorter adjoining segment consumed on each side of a corner. Half of a segment
|
||||
// is the maximum, otherwise the curves of two adjacent corners would overlap.
|
||||
const double m_corner_distance_ratio;
|
||||
const double m_tolerance;
|
||||
const double m_max_corner_distance;
|
||||
const CornerFilter m_corner_filter;
|
||||
std::vector<Vec2d> m_corner_points;
|
||||
// Cached flattening of the last corner, valid for corners of the same size and turn angle.
|
||||
std::vector<Vec2d> m_cached_coefficients;
|
||||
double m_cached_distance { 0. };
|
||||
double m_cached_cosine { 0. };
|
||||
bool m_has_cached_coefficients { false };
|
||||
|
||||
Vec2d m_previous { Vec2d::Zero() };
|
||||
Vec2d m_corner { Vec2d::Zero() };
|
||||
// Number of points held back: none, the first point of a path, or a corner candidate.
|
||||
int m_pending { 0 };
|
||||
};
|
||||
|
||||
// Rounds the corners of already scaled paths in place. Paths of less than three points are left alone.
|
||||
// Both ends of a polyline are kept where they are, even when they coincide: such a path retraces its
|
||||
// way back and joining its ends would turn it into a loop. See CornerSmoother for max_corner_distance.
|
||||
void smooth_polyline_corners(Polyline &polyline, double smooth_factor, double tolerance,
|
||||
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
|
||||
void smooth_polylines_corners(Polylines &polylines, double smooth_factor, double tolerance,
|
||||
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
|
||||
// Polygons close implicitly, so every one of their vertices is a corner.
|
||||
void smooth_polygons_corners(Polygons &polygons, double smooth_factor, double tolerance,
|
||||
double max_corner_distance = 0., const CornerFilter &corner_filter = {});
|
||||
|
||||
} // namespace Slic3r
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../Surface.hpp"
|
||||
#include <cmath>
|
||||
#include "FillBase.hpp"
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "FillCrossHatch.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -205,6 +206,9 @@ void FillCrossHatch ::_fill_surface_single(
|
||||
// shift the pattern to the actual space
|
||||
for (Polyline &pl : polylines) { pl.translate(bb.min); }
|
||||
|
||||
// Orca: round the corners of the transition layers. The repeat layers are straight lines and stay as they are.
|
||||
smooth_polylines_corners(polylines, params.smooth_factor, scaled<double>(params.resolution));
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "FillHoneycomb.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -70,6 +71,9 @@ void FillHoneycomb::_fill_surface_single(
|
||||
}
|
||||
p.rotate(-direction.first, m.hex_center);
|
||||
p.simplify(5 * spacing); // simplify to 5x line width
|
||||
// Orca: round the corners of the honeycomb cells. Done before the clipping, so that the
|
||||
// curves are cut by the region boundary just like the sharp path would be.
|
||||
smooth_polyline_corners(p, params.smooth_factor, scaled<double>(params.resolution));
|
||||
all_polylines.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../Print.hpp"
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "Lightning/Generator.hpp"
|
||||
|
||||
@@ -17,6 +18,19 @@ void Filler::_fill_surface_single(
|
||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
|
||||
|
||||
// Orca: round the turns of the branches. Hairpins are left sharp, as they cannot be rounded, and
|
||||
// the reach is capped: cutting a corner moves the branch, and a branch is as long as the object
|
||||
// rather than as long as one cell of a pattern, so half of a leg would merge it with its neighbour
|
||||
// instead of rounding the turn between them. Half the distance between two branches keeps them
|
||||
// apart. With more than one line per infill wall the branches are printed as outlines drawn around
|
||||
// them, and the outlines of branches that run into each other merge into a single one; moving a
|
||||
// branch by more than a fraction of its printed width breaks such an outline up into separate
|
||||
// loops, so that width bounds the reach as well.
|
||||
const double branch_width = scaled<double>(this->spacing) * params.multiline;
|
||||
const double branch_spacing = branch_width / std::max(double(params.density), EPSILON);
|
||||
const double max_reach = 0.5 * (params.multiline > 1 ? branch_width : branch_spacing);
|
||||
smooth_polylines_corners(fill_lines, params.smooth_factor, scaled<double>(params.resolution), max_reach);
|
||||
|
||||
// Apply multiline offset if needed
|
||||
multiline_fill(fill_lines, params, spacing);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../Surface.hpp"
|
||||
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "FillPlanePath.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
@@ -288,145 +289,60 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
|
||||
}
|
||||
}
|
||||
|
||||
using QuinticBezier = std::array<Vec2d, 6>;
|
||||
|
||||
static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
|
||||
{
|
||||
// A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
|
||||
// control point within a deviation-wide strip around the endpoint chord conservatively bounds the
|
||||
// flattening error. The cross product is the perpendicular distance scaled by the chord length;
|
||||
// comparing squared values avoids a square root.
|
||||
const Vec2d chord = curve.back() - curve.front();
|
||||
const double chord_length_sq = chord.squaredNorm();
|
||||
const double max_cross_sq = deviation * deviation * chord_length_sq;
|
||||
|
||||
for (size_t i = 1; i + 1 < curve.size(); ++i) {
|
||||
const Vec2d offset = curve[i] - curve.front();
|
||||
const double cross = chord.x() * offset.y() - chord.y() * offset.x();
|
||||
if (cross * cross > max_cross_sq)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
|
||||
{
|
||||
// Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
|
||||
// control point to the left half and one to the right half; the latter is filled backwards to keep
|
||||
// both resulting control polygons in their original parameter direction.
|
||||
QuinticBezier subdivision = curve;
|
||||
left.front() = subdivision.front();
|
||||
right.back() = subdivision.back();
|
||||
for (size_t level = 1; level < curve.size(); ++level) {
|
||||
for (size_t i = 0; i + level < curve.size(); ++i)
|
||||
subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
|
||||
left[level] = subdivision.front();
|
||||
right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
|
||||
}
|
||||
}
|
||||
|
||||
static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector<Vec2d> &output)
|
||||
{
|
||||
// Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
|
||||
// A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
|
||||
// avoiding abrupt segment-length jumps at adaptive-depth boundaries.
|
||||
static constexpr size_t max_depth = 16;
|
||||
|
||||
std::vector<QuinticBezier> subcurves(2);
|
||||
subdivide_bezier(curve, subcurves[0], subcurves[1]);
|
||||
|
||||
for (size_t depth = 1; depth < max_depth; ++depth) {
|
||||
bool all_flat = true;
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
if (!is_bezier_flat(c, deviation)) {
|
||||
all_flat = false;
|
||||
break;
|
||||
}
|
||||
if (all_flat)
|
||||
break;
|
||||
std::vector<QuinticBezier> finer(subcurves.size() * 2);
|
||||
for (size_t i = 0; i < subcurves.size(); ++i)
|
||||
subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
|
||||
subcurves = std::move(finer);
|
||||
}
|
||||
|
||||
// The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
|
||||
output.reserve(output.size() + subcurves.size());
|
||||
for (const QuinticBezier &c : subcurves)
|
||||
output.emplace_back(c.back());
|
||||
}
|
||||
|
||||
// Rounds the corners of the generated path on its way to the infill output.
|
||||
template<typename Output>
|
||||
static void generate_smooth_hilbert_curve(
|
||||
coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const double corner_distance, Output &output)
|
||||
class SmoothingPolylineOutput
|
||||
{
|
||||
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
|
||||
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
|
||||
// clipper or the later region intersection removes the padded part of the traversal.
|
||||
size_t sz = 2;
|
||||
const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
|
||||
while (sz < sz0)
|
||||
sz <<= 1;
|
||||
public:
|
||||
SmoothingPolylineOutput(Output &output, const double smooth_factor, const double tolerance)
|
||||
: m_output(output), m_smoother(smooth_factor, tolerance) {}
|
||||
|
||||
const size_t point_count = sz * sz;
|
||||
output.reserve(point_count);
|
||||
void reserve(size_t n) { m_output.reserve(n); }
|
||||
void add_point(const Vec2d &pt) { auto emit = emitter(); m_smoother.push(pt, emit); }
|
||||
// The smoother holds back the last point of the path until it knows there is no corner left to round.
|
||||
void finish() { auto emit = emitter(); m_smoother.flush(emit); }
|
||||
|
||||
// The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
|
||||
// if this helper is invoked with an invalid resolution.
|
||||
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
|
||||
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
|
||||
// At each end, the first three control points are collinear and equally spaced: the tangent follows
|
||||
// the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
|
||||
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
|
||||
// it only once to the requested chordal-deviation tolerance.
|
||||
const QuinticBezier corner_curve {{
|
||||
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
|
||||
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
|
||||
}};
|
||||
std::vector<Vec2d> curve_coefficients;
|
||||
flatten_bezier(corner_curve, deviation, curve_coefficients);
|
||||
|
||||
auto translated_point = [min_x, min_y](size_t idx) {
|
||||
Point p = hilbert_n_to_xy(idx);
|
||||
return Point(p.x() + min_x, p.y() + min_y);
|
||||
};
|
||||
auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
|
||||
bool has_last_output = false;
|
||||
Vec2d last_output;
|
||||
// Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
|
||||
// to avoid emitting zero-length extrusion segments.
|
||||
auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
|
||||
if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
|
||||
output.add_point(point);
|
||||
last_output = point;
|
||||
has_last_output = true;
|
||||
}
|
||||
};
|
||||
|
||||
Vec2d previous = to_vec2d(translated_point(0));
|
||||
Vec2d corner = to_vec2d(translated_point(1));
|
||||
add_point(previous);
|
||||
// Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
|
||||
// its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
|
||||
for (size_t i = 1; i + 1 < point_count; ++i) {
|
||||
const Vec2d next = to_vec2d(translated_point(i + 1));
|
||||
const Vec2d incoming = (corner - previous).normalized();
|
||||
const Vec2d outgoing = (next - corner).normalized();
|
||||
const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
|
||||
|
||||
if (std::abs(cross) < EPSILON) {
|
||||
add_point(corner);
|
||||
} else {
|
||||
add_point(corner - corner_distance * incoming);
|
||||
for (const Vec2d &coefficient : curve_coefficients)
|
||||
add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
|
||||
}
|
||||
|
||||
previous = corner;
|
||||
corner = next;
|
||||
private:
|
||||
// The curves of two adjacent corners meet at the midpoint of the segment they share, where they
|
||||
// may round to the very same output point. Drop those, they would be zero length extrusions.
|
||||
auto emitter()
|
||||
{
|
||||
return [this](const Vec2d &pt) {
|
||||
const Point snapped = m_output.scaled(pt);
|
||||
if (m_has_last_snapped && snapped == m_last_snapped)
|
||||
return;
|
||||
m_last_snapped = snapped;
|
||||
m_has_last_snapped = true;
|
||||
m_output.add_point(pt);
|
||||
};
|
||||
}
|
||||
add_point(corner);
|
||||
|
||||
Output &m_output;
|
||||
CornerSmoother m_smoother;
|
||||
Point m_last_snapped { Point::Zero() };
|
||||
bool m_has_last_snapped { false };
|
||||
};
|
||||
|
||||
// Runs the path generator against the concrete output type, optionally through the corner smoother.
|
||||
// The outputs do not share a virtual add_point(), so the type has to be resolved here.
|
||||
template<typename GenerateFn>
|
||||
static void generate_path(InfillPolylineOutput &output, const FillParams ¶ms, const double resolution, GenerateFn generate)
|
||||
{
|
||||
const double smooth_factor = sanitize_smooth_factor(params.smooth_factor);
|
||||
auto run = [smooth_factor, resolution, &generate](auto &out) {
|
||||
if (smooth_factor == 0.) {
|
||||
generate(out);
|
||||
} else {
|
||||
SmoothingPolylineOutput<std::remove_reference_t<decltype(out)>> smoothing(out, smooth_factor, resolution);
|
||||
generate(smoothing);
|
||||
smoothing.finish();
|
||||
}
|
||||
};
|
||||
|
||||
if (output.clips())
|
||||
run(static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
run(output);
|
||||
}
|
||||
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
|
||||
@@ -440,19 +356,8 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
|
||||
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output)
|
||||
{
|
||||
const double smooth_factor = std::isfinite(params.smooth_factor) ?
|
||||
std::clamp(params.smooth_factor, 0., 1.) : 0.;
|
||||
if (smooth_factor == 0.) {
|
||||
this->generate(min_x, min_y, max_x, max_y, resolution, output);
|
||||
return;
|
||||
}
|
||||
|
||||
const double corner_distance = 0.5 * smooth_factor;
|
||||
if (output.clips())
|
||||
generate_smooth_hilbert_curve(
|
||||
min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast<InfillPolylineClipper&>(output));
|
||||
else
|
||||
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
|
||||
generate_path(output, params, resolution,
|
||||
[min_x, min_y, max_x, max_y](auto &out) { generate_hilbert_curve(min_x, min_y, max_x, max_y, out); });
|
||||
}
|
||||
|
||||
template<typename Output>
|
||||
@@ -495,4 +400,11 @@ void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, c
|
||||
generate_octagram_spiral(min_x, min_y, max_x, max_y, output);
|
||||
}
|
||||
|
||||
void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output)
|
||||
{
|
||||
generate_path(output, params, resolution,
|
||||
[min_x, min_y, max_x, max_y](auto &out) { generate_octagram_spiral(min_x, min_y, max_x, max_y, out); });
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -21,10 +21,10 @@ public:
|
||||
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
|
||||
Points&& result() { return std::move(m_out); }
|
||||
virtual bool clips() const { return false; }
|
||||
|
||||
protected:
|
||||
// The output grid the generated points are snapped to.
|
||||
const Point scaled(const Vec2d& fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; }
|
||||
|
||||
protected:
|
||||
// Output polyline.
|
||||
Points m_out;
|
||||
|
||||
@@ -93,6 +93,8 @@ public:
|
||||
protected:
|
||||
bool centered() const override { return true; }
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
|
||||
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
|
||||
const FillParams ¶ms, InfillPolylineOutput &output) override;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "../ShortestPath.hpp"
|
||||
#include "../VariableWidth.hpp"
|
||||
|
||||
#include "FillCornerSmoothing.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
|
||||
// #define SLIC3R_DEBUG
|
||||
@@ -3364,6 +3365,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(rotate_vector.second);
|
||||
|
||||
// Orca: round the corners of the trapezoids. The straight base lines of the triangular family
|
||||
// have no corner to round.
|
||||
smooth_polylines_corners(polylines, params.smooth_factor, scaled<double>(params.resolution));
|
||||
|
||||
// Apply multiline fill
|
||||
multiline_fill(polylines, params, spacing);
|
||||
|
||||
|
||||
@@ -3469,9 +3469,8 @@ void PrintConfigDef::init_fff_params()
|
||||
def = this->add("sparse_infill_smooth_factor", coPercent);
|
||||
def->label = L("Sparse infill smooth factor");
|
||||
def->category = L("Strength");
|
||||
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
|
||||
"while 100% produces the largest possible curves between adjacent infill lines. "
|
||||
"Currently applies only to the Hilbert Curve.");
|
||||
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original sharp path, "
|
||||
"while 100% produces the largest possible curves between adjacent infill lines.");
|
||||
def->sidetext = "%";
|
||||
def->min = 0;
|
||||
def->max = 100;
|
||||
|
||||
@@ -146,6 +146,29 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
|
||||
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
|
||||
// than one line per infill wall; a single line makes them plain crossing lines with nothing to round.
|
||||
inline bool is_smoothable_infill_pattern(InfillPattern pattern, int multiline = 1)
|
||||
{
|
||||
switch (pattern) {
|
||||
case ipHilbertCurve:
|
||||
case ipOctagramSpiral:
|
||||
case ipLightning:
|
||||
case ipHoneycomb:
|
||||
case ip3DHoneycomb:
|
||||
case ipConcentric:
|
||||
case ipCrossHatch:
|
||||
return true;
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars:
|
||||
return multiline > 1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum class IroningType {
|
||||
NoIroning,
|
||||
TopSurfaces,
|
||||
|
||||
@@ -752,7 +752,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
|
||||
toggle_line("sparse_infill_smooth_factor", is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
|
||||
@@ -2790,7 +2790,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
|
||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
||||
|
||||
@@ -698,3 +698,290 @@ TEST_CASE("Solid infill direction offsets every layer when no template is set",
|
||||
CHECK(delta == 30);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Honeycomb infill rounds its cell corners with the smooth factor", "[Fill]")
|
||||
{
|
||||
// A cell whose sides are several times the line width, so that the corners have room to be rounded.
|
||||
const double spacing = 0.45;
|
||||
const double density = 0.1;
|
||||
auto fill = [spacing, density](double smooth_factor) {
|
||||
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("honeycomb"));
|
||||
filler->spacing = spacing;
|
||||
|
||||
FillParams params;
|
||||
params.density = float(density);
|
||||
params.dont_adjust = true;
|
||||
// Keep the fragments apart, so that only the turns of the pattern itself are measured.
|
||||
params.anchor_length_max = 0.f;
|
||||
params.smooth_factor = smooth_factor;
|
||||
|
||||
Slic3r::ExPolygon square{ Slic3r::Points{
|
||||
Point::new_scale(0., 0.), Point::new_scale(50., 0.), Point::new_scale(50., 50.), Point::new_scale(0., 50.) } };
|
||||
Slic3r::Surface surface(stInternal, square);
|
||||
return filler->fill_surface(&surface, params);
|
||||
};
|
||||
|
||||
// Cosine of the sharpest turn of any of the paths, 1 meaning none of them turns at all.
|
||||
auto sharpest_turn_cosine = [](const Slic3r::Polylines &polylines) {
|
||||
double sharpest = 1.;
|
||||
for (const Polyline &polyline : polylines)
|
||||
for (size_t i = 1; i + 1 < polyline.size(); ++i) {
|
||||
const Vec2d incoming = (polyline[i] - polyline[i - 1]).cast<double>().normalized();
|
||||
const Vec2d outgoing = (polyline[i + 1] - polyline[i]).cast<double>().normalized();
|
||||
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||
}
|
||||
return sharpest;
|
||||
};
|
||||
auto point_count = [](const Slic3r::Polylines &polylines) {
|
||||
return std::accumulate(polylines.begin(), polylines.end(), size_t(0),
|
||||
[](size_t count, const Polyline &polyline) { return count + polyline.size(); });
|
||||
};
|
||||
|
||||
const Slic3r::Polylines sharp = fill(0.);
|
||||
const Slic3r::Polylines smooth = fill(1.);
|
||||
|
||||
REQUIRE(!sharp.empty());
|
||||
REQUIRE(smooth.size() == sharp.size());
|
||||
REQUIRE(point_count(smooth) > point_count(sharp));
|
||||
// The cell corners turn by 60 degrees; smoothing replaces them by gentle curves.
|
||||
REQUIRE(sharpest_turn_cosine(sharp) < 0.6);
|
||||
REQUIRE(sharpest_turn_cosine(smooth) > 0.9);
|
||||
}
|
||||
|
||||
// Point count, number of turns sharper than 25 degrees and length of the sparse infill of a print.
|
||||
// A rounded corner is a run of much gentler turns, so smoothing shows up as fewer sharp ones.
|
||||
struct SparseInfillShape {
|
||||
size_t point_count { 0 };
|
||||
size_t sharp_turns { 0 };
|
||||
size_t path_count { 0 };
|
||||
double length { 0. };
|
||||
};
|
||||
|
||||
static SparseInfillShape sparse_infill_shape(const Print &print)
|
||||
{
|
||||
SparseInfillShape shape;
|
||||
|
||||
auto account = [&shape](const ExtrusionPath &path) {
|
||||
if (!sparse_role(path.role()))
|
||||
return;
|
||||
const Points3 &pts = path.polyline.points;
|
||||
++shape.path_count;
|
||||
shape.point_count += pts.size();
|
||||
for (size_t i = 1; i < pts.size(); ++i)
|
||||
shape.length += (pts[i] - pts[i - 1]).head<2>().cast<double>().norm();
|
||||
for (size_t i = 1; i + 1 < pts.size(); ++i) {
|
||||
const Vec2d incoming = (pts[i] - pts[i - 1]).head<2>().cast<double>();
|
||||
const Vec2d outgoing = (pts[i + 1] - pts[i]).head<2>().cast<double>();
|
||||
if (incoming.squaredNorm() > 0. && outgoing.squaredNorm() > 0. &&
|
||||
incoming.normalized().dot(outgoing.normalized()) < 0.9)
|
||||
++shape.sharp_turns;
|
||||
}
|
||||
};
|
||||
|
||||
for (const Layer *layer : print.objects().front()->layers())
|
||||
for (const LayerRegion *region : layer->regions())
|
||||
for (const ExtrusionEntity *entity : region->fills.flatten().entities) {
|
||||
if (auto *path = dynamic_cast<const ExtrusionPath *>(entity))
|
||||
account(*path);
|
||||
else if (auto *multi = dynamic_cast<const ExtrusionMultiPath *>(entity))
|
||||
for (const ExtrusionPath &p : multi->paths)
|
||||
account(p);
|
||||
else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(entity))
|
||||
for (const ExtrusionPath &p : loop->paths)
|
||||
account(p);
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
TEST_CASE("Lightning infill rounds the turns of its branches with the smooth factor", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "lightning"},
|
||||
{"sparse_infill_density", "15%"},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for("0%");
|
||||
const SparseInfillShape smooth = shape_for("100%");
|
||||
|
||||
REQUIRE(sharp.point_count > 0);
|
||||
// The branch turns are replaced by curves, which cut the corners off and take more points to
|
||||
// describe. The turns where two branches are joined into one path stay sharp.
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
REQUIRE(smooth.length < sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("Concentric infill rounds its loops with the smooth factor", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "concentric"},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for("0%");
|
||||
const SparseInfillShape smooth = shape_for("100%");
|
||||
|
||||
REQUIRE(sharp.point_count > 0);
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
REQUIRE(smooth.length < sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("Cross hatch infill rounds its transition layers with the smooth factor", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "crosshatch"},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for("0%");
|
||||
const SparseInfillShape smooth = shape_for("100%");
|
||||
|
||||
REQUIRE(sharp.point_count > 0);
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
REQUIRE(smooth.length < sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("Trapezoidal grid infill rounds its corners only with more than one line", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](int multiline, const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "grid"},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"fill_multiline", multiline},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for(2, "0%");
|
||||
const SparseInfillShape smooth = shape_for(2, "100%");
|
||||
|
||||
REQUIRE(sharp.point_count > 0);
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
REQUIRE(smooth.length < sharp.length);
|
||||
|
||||
// A single line per infill wall is the plain crossing line grid, which has no corner of its own.
|
||||
const SparseInfillShape single_sharp = shape_for(1, "0%");
|
||||
const SparseInfillShape single_smooth = shape_for(1, "100%");
|
||||
REQUIRE(single_sharp.point_count > 0);
|
||||
REQUIRE(single_smooth.point_count == single_sharp.point_count);
|
||||
REQUIRE(single_smooth.length == single_sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("3D honeycomb infill rounds its octahedral waves with the smooth factor", "[Fill]")
|
||||
{
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "3dhoneycomb"},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for("0%");
|
||||
const SparseInfillShape smooth = shape_for("100%");
|
||||
|
||||
REQUIRE(sharp.point_count > 0);
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
REQUIRE(smooth.length < sharp.length);
|
||||
}
|
||||
|
||||
TEST_CASE("Smoothed concentric infill stays inside the fill region", "[Fill][Regression]")
|
||||
{
|
||||
// The concentric loops are offsets of the fill region and are never clipped to it, so a corner
|
||||
// rounded across its boundary ends up in a hole or over a wall. Rounding cuts toward the inside of
|
||||
// the turn, which leaves the region at every corner of a hole, and in a region thinner than the
|
||||
// curve even at a corner turning inwards.
|
||||
const bool thin_region = GENERATE(false, true);
|
||||
ExPolygon region;
|
||||
if (thin_region) {
|
||||
// An L of two 1.2mm wide arms: cutting the corner they meet at crosses both of them.
|
||||
region = ExPolygon{ Slic3r::Points{
|
||||
Point::new_scale(0., 0.), Point::new_scale(20., 0.), Point::new_scale(20., 1.2),
|
||||
Point::new_scale(1.2, 1.2), Point::new_scale(1.2, 20.), Point::new_scale(0., 20.) } };
|
||||
} else {
|
||||
region = ExPolygon{ Slic3r::Points{ Point::new_scale(0., 0.), Point::new_scale(50., 0.),
|
||||
Point::new_scale(50., 50.), Point::new_scale(0., 50.) },
|
||||
Slic3r::Points{ Point::new_scale(30., 20.), Point::new_scale(30., 30.),
|
||||
Point::new_scale(20., 30.), Point::new_scale(20., 20.) } };
|
||||
}
|
||||
CAPTURE(thin_region);
|
||||
|
||||
auto fill = [®ion](double smooth_factor) {
|
||||
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("concentric"));
|
||||
filler->spacing = 0.45;
|
||||
|
||||
FillParams params;
|
||||
params.density = 0.1f;
|
||||
params.dont_adjust = true;
|
||||
params.smooth_factor = smooth_factor;
|
||||
|
||||
Slic3r::Surface surface(stInternal, region);
|
||||
return filler->fill_surface(&surface, params);
|
||||
};
|
||||
auto point_count = [](const Slic3r::Polylines &polylines) {
|
||||
return std::accumulate(polylines.begin(), polylines.end(), size_t(0),
|
||||
[](size_t count, const Polyline &polyline) { return count + polyline.size(); });
|
||||
};
|
||||
|
||||
const Slic3r::Polylines sharp = fill(0.);
|
||||
const Slic3r::Polylines smooth = fill(1.);
|
||||
REQUIRE(!sharp.empty());
|
||||
|
||||
// Nothing leaves the fill region, which the unrounded loops already touch from the inside.
|
||||
const ExPolygons bounds = offset_ex(region, float(SCALED_EPSILON));
|
||||
REQUIRE(diff_pl(sharp, bounds).empty());
|
||||
REQUIRE(diff_pl(smooth, bounds).empty());
|
||||
// The corners that the region has room for are still rounded.
|
||||
if (!thin_region)
|
||||
REQUIRE(point_count(smooth) > point_count(sharp));
|
||||
}
|
||||
|
||||
TEST_CASE("Smoothing multiline lightning infill keeps its outlines connected", "[Fill][Regression]")
|
||||
{
|
||||
// With more than one line per infill wall, the branches are printed as outlines drawn around them,
|
||||
// and the outlines of branches that run close to each other merge into one. Rounding the branches
|
||||
// before those outlines are built moves them apart, which breaks the merged outlines up into
|
||||
// separate loops - many more of them, each needing its own travel move.
|
||||
auto shape_for = [](const std::string &smooth_factor) {
|
||||
Print print;
|
||||
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||
{{"sparse_infill_pattern", "lightning"},
|
||||
{"sparse_infill_density", "50%"},
|
||||
{"fill_multiline", 2},
|
||||
{"sparse_infill_smooth_factor", smooth_factor},
|
||||
{"layer_height", 0.2}});
|
||||
return sparse_infill_shape(print);
|
||||
};
|
||||
|
||||
const SparseInfillShape sharp = shape_for("0%");
|
||||
const SparseInfillShape smooth = shape_for("100%");
|
||||
|
||||
REQUIRE(sharp.path_count > 0);
|
||||
REQUIRE(smooth.path_count <= sharp.path_count);
|
||||
// The outlines are still rounded.
|
||||
REQUIRE(smooth.point_count > sharp.point_count);
|
||||
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_preset_setting_id.cpp
|
||||
test_preset_diff.cpp
|
||||
test_elephant_foot_compensation.cpp
|
||||
test_fill_corner_smoothing.cpp
|
||||
test_fill_plane_path.cpp
|
||||
test_geometry.cpp
|
||||
test_multimaterial_segmentation.cpp
|
||||
|
||||
173
tests/libslic3r/test_fill_corner_smoothing.cpp
Normal file
173
tests/libslic3r/test_fill_corner_smoothing.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "libslic3r/Fill/FillCornerSmoothing.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
// A right angle turn, with the outgoing leg ten times longer than the incoming one.
|
||||
Polyline asymmetric_corner()
|
||||
{
|
||||
return Polyline{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 100.) };
|
||||
}
|
||||
|
||||
double max_turn_cosine(const Polyline &polyline)
|
||||
{
|
||||
double sharpest = 1.;
|
||||
for (size_t i = 1; i + 1 < polyline.size(); ++i) {
|
||||
const Vec2d incoming = (polyline[i] - polyline[i - 1]).cast<double>().normalized();
|
||||
const Vec2d outgoing = (polyline[i + 1] - polyline[i]).cast<double>().normalized();
|
||||
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||
}
|
||||
return sharpest;
|
||||
}
|
||||
|
||||
bool contains(const Polyline &polyline, const Point &point)
|
||||
{
|
||||
return std::find(polyline.points.begin(), polyline.points.end(), point) != polyline.points.end();
|
||||
}
|
||||
|
||||
const double tolerance = scaled<double>(0.0125);
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Corner smoothing replaces a sharp vertex by a curve", "[FillCornerSmoothing]")
|
||||
{
|
||||
const Polyline sharp = asymmetric_corner();
|
||||
Polyline smooth = sharp;
|
||||
smooth_polyline_corners(smooth, 1., tolerance);
|
||||
|
||||
REQUIRE(smooth.size() > sharp.size());
|
||||
REQUIRE(smooth.front() == sharp.front());
|
||||
REQUIRE(smooth.back() == sharp.back());
|
||||
// The right angle is gone, every remaining turn is a gentle one.
|
||||
REQUIRE(max_turn_cosine(sharp) < 0.1);
|
||||
REQUIRE(max_turn_cosine(smooth) > 0.9);
|
||||
REQUIRE(smooth.length() < sharp.length());
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing keeps the path untouched at a zero factor", "[FillCornerSmoothing]")
|
||||
{
|
||||
const Polyline sharp = asymmetric_corner();
|
||||
|
||||
Polyline none = sharp;
|
||||
smooth_polyline_corners(none, 0., tolerance);
|
||||
REQUIRE(none.points == sharp.points);
|
||||
|
||||
Polyline invalid = sharp;
|
||||
smooth_polyline_corners(invalid, std::numeric_limits<double>::quiet_NaN(), tolerance);
|
||||
REQUIRE(invalid.points == sharp.points);
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing consumes at most half of the shorter leg", "[FillCornerSmoothing]")
|
||||
{
|
||||
// The curve must not reach beyond the middle of either adjoining segment, otherwise the curves of
|
||||
// two adjacent corners would overlap. The shorter leg is 10mm long, so the corner at (10, 0) is
|
||||
// left 5mm before it and rejoined 5mm past it, even though the other leg is 100mm long.
|
||||
Polyline smooth = asymmetric_corner();
|
||||
smooth_polyline_corners(smooth, 1., tolerance);
|
||||
|
||||
REQUIRE(contains(smooth, Point::new_scale(5., 0.)));
|
||||
REQUIRE(contains(smooth, Point::new_scale(10., 5.)));
|
||||
// A Bezier curve stays within the convex hull of its control points, so the rounded path stays
|
||||
// inside the box spanned by the two legs.
|
||||
for (const Point &point : smooth.points) {
|
||||
REQUIRE(point.x() >= 0);
|
||||
REQUIRE(point.y() >= 0);
|
||||
REQUIRE(point.x() <= Point::new_scale(10., 0.).x());
|
||||
REQUIRE(point.y() <= Point::new_scale(0., 100.).y());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing scales the curve with the factor", "[FillCornerSmoothing]")
|
||||
{
|
||||
Polyline half = asymmetric_corner();
|
||||
smooth_polyline_corners(half, 0.5, tolerance);
|
||||
Polyline full = asymmetric_corner();
|
||||
smooth_polyline_corners(full, 1., tolerance);
|
||||
|
||||
// Half of the factor leaves the 10mm leg half as far from the corner.
|
||||
REQUIRE(contains(half, Point::new_scale(7.5, 0.)));
|
||||
REQUIRE(contains(full, Point::new_scale(5., 0.)));
|
||||
// A larger factor rounds a wider portion of the legs, cutting more of the corner off.
|
||||
REQUIRE(full.length() < half.length());
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing leaves hairpins sharp", "[FillCornerSmoothing]")
|
||||
{
|
||||
// Both ends of a curve replacing a nearly reversing turn coincide, which would round the hairpin
|
||||
// into a degenerate loop instead of a tip.
|
||||
Polyline hairpin{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(0., 0.5) };
|
||||
const Polyline sharp = hairpin;
|
||||
smooth_polyline_corners(hairpin, 1., tolerance);
|
||||
REQUIRE(hairpin == sharp);
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing follows the flattening tolerance", "[FillCornerSmoothing]")
|
||||
{
|
||||
Polyline coarse = asymmetric_corner();
|
||||
smooth_polyline_corners(coarse, 1., scaled<double>(0.2));
|
||||
Polyline fine = asymmetric_corner();
|
||||
smooth_polyline_corners(fine, 1., scaled<double>(0.001));
|
||||
|
||||
REQUIRE(fine.size() > coarse.size());
|
||||
REQUIRE(fine.front() == coarse.front());
|
||||
REQUIRE(fine.back() == coarse.back());
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing emits no zero length segments", "[FillCornerSmoothing]")
|
||||
{
|
||||
// Fully smoothed adjacent corners meet at the midpoint of the segment they share.
|
||||
Polyline zigzag;
|
||||
for (int i = 0; i < 8; ++i)
|
||||
zigzag.points.emplace_back(Point::new_scale(i, i % 2 ? 1. : 0.));
|
||||
smooth_polyline_corners(zigzag, 1., tolerance);
|
||||
|
||||
for (size_t i = 1; i < zigzag.size(); ++i)
|
||||
REQUIRE((zigzag[i] - zigzag[i - 1]).cast<double>().squaredNorm() > 0.);
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing rounds every vertex of a polygon", "[FillCornerSmoothing]")
|
||||
{
|
||||
// A polygon closes implicitly, so none of its corners may stay sharp, not even the first one.
|
||||
const Polygon square{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 10.),
|
||||
Point::new_scale(0., 10.) };
|
||||
Polygons smooth{ square };
|
||||
smooth_polygons_corners(smooth, 1., tolerance);
|
||||
const Polyline rounded = smooth.front().split_at_first_point();
|
||||
|
||||
REQUIRE(smooth.front().size() > square.size());
|
||||
REQUIRE(max_turn_cosine(rounded) > 0.9);
|
||||
// The turn from the closing segment back into the first one must be gentle as well.
|
||||
const Vec2d incoming = (rounded[rounded.size() - 1] - rounded[rounded.size() - 2]).cast<double>().normalized();
|
||||
const Vec2d outgoing = (rounded[1] - rounded[0]).cast<double>().normalized();
|
||||
REQUIRE(incoming.dot(outgoing) > 0.9);
|
||||
// None of the corners is cut by more than half of a 10mm side.
|
||||
for (const Point &point : smooth.front().points) {
|
||||
REQUIRE(point.x() >= 0);
|
||||
REQUIRE(point.y() >= 0);
|
||||
REQUIRE(point.x() <= Point::new_scale(10., 0.).x());
|
||||
REQUIRE(point.y() <= Point::new_scale(0., 10.).y());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Corner smoothing keeps the ends of a path that returns to its start", "[FillCornerSmoothing][Regression]")
|
||||
{
|
||||
// A branch of a lightning tree walks out and retraces its way back, ending where it started. Its
|
||||
// ends are two free ends that happen to coincide, and joining them would close it into a loop.
|
||||
Polyline retrace{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 10.),
|
||||
Point::new_scale(5., 10.), Point::new_scale(0., 0.) };
|
||||
const Polyline sharp = retrace;
|
||||
smooth_polyline_corners(retrace, 1., tolerance);
|
||||
|
||||
REQUIRE(retrace.size() > sharp.size());
|
||||
REQUIRE(retrace.front() == sharp.front());
|
||||
REQUIRE(retrace.back() == sharp.back());
|
||||
}
|
||||
@@ -27,6 +27,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class TestableOctagramSpiral : public FillOctagramSpiral
|
||||
{
|
||||
public:
|
||||
Points generate_points(double resolution, double smooth_factor = 0., coord_t max_coordinate = 7)
|
||||
{
|
||||
InfillPolylineOutput output(output_scale);
|
||||
FillParams params;
|
||||
params.smooth_factor = smooth_factor;
|
||||
FillOctagramSpiral::generate(-max_coordinate, -max_coordinate, max_coordinate, max_coordinate, resolution, params, output);
|
||||
return std::move(output.result());
|
||||
}
|
||||
};
|
||||
|
||||
// Cosine of the sharpest turn of a path, 1 meaning it has no turn at all.
|
||||
double sharpest_turn_cosine(const Points &points)
|
||||
{
|
||||
double sharpest = 1.;
|
||||
for (size_t i = 1; i + 1 < points.size(); ++i) {
|
||||
const Vec2d incoming = (points[i] - points[i - 1]).cast<double>().normalized();
|
||||
const Vec2d outgoing = (points[i + 1] - points[i]).cast<double>().normalized();
|
||||
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||
}
|
||||
return sharpest;
|
||||
}
|
||||
|
||||
double path_length(const Points &points)
|
||||
{
|
||||
double length = 0.;
|
||||
@@ -146,6 +171,35 @@ TEST_CASE("Hilbert smoothing joins straight segments with continuous curvature",
|
||||
REQUIRE(fine_entry_curvature < 0.25 * coarse_entry_curvature);
|
||||
}
|
||||
|
||||
TEST_CASE("Octagram spiral smoothing rounds the turns of the spiral", "[FillPlanePath]")
|
||||
{
|
||||
const Points sharp = TestableOctagramSpiral().generate_points(0.005);
|
||||
const Points smooth = TestableOctagramSpiral().generate_points(0.005, 1.);
|
||||
|
||||
REQUIRE(smooth.size() > sharp.size());
|
||||
REQUIRE(smooth.front() == sharp.front());
|
||||
REQUIRE(smooth.back() == sharp.back());
|
||||
// The spiral alternates between 90 and 135 degree turns; both are rounded into gentle ones.
|
||||
REQUIRE(sharpest_turn_cosine(sharp) < -0.7);
|
||||
REQUIRE(sharpest_turn_cosine(smooth) > 0.9);
|
||||
|
||||
for (size_t i = 1; i < smooth.size(); ++i)
|
||||
REQUIRE((smooth[i] - smooth[i - 1]).cast<double>().squaredNorm() > 0.);
|
||||
}
|
||||
|
||||
TEST_CASE("Octagram spiral smooth factor controls corner curvature", "[FillPlanePath]")
|
||||
{
|
||||
const Points sharp = TestableOctagramSpiral().generate_points(0.005);
|
||||
const Points half_smooth = TestableOctagramSpiral().generate_points(0.005, 0.5);
|
||||
const Points full_smooth = TestableOctagramSpiral().generate_points(0.005, 1.);
|
||||
const Points invalid_factor = TestableOctagramSpiral().generate_points(
|
||||
0.005, std::numeric_limits<double>::quiet_NaN());
|
||||
|
||||
REQUIRE(path_length(full_smooth) < path_length(half_smooth));
|
||||
REQUIRE(path_length(half_smooth) < path_length(sharp));
|
||||
REQUIRE(invalid_factor == sharp);
|
||||
}
|
||||
|
||||
TEST_CASE("Hilbert curve smooth factor controls corner curvature", "[FillPlanePath]")
|
||||
{
|
||||
const Points sharp = TestableHilbertCurve().generate_points(0.005);
|
||||
|
||||
Reference in New Issue
Block a user