GCode.cpp 182 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541
  1. ///|/ Copyright (c) Prusa Research 2016 - 2023 Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, David Kocík @kocikdav
  2. ///|/ Copyright (c) SuperSlicer 2023 Remi Durand @supermerill
  3. ///|/ Copyright (c) 2021 Justin Schuh @jschuh
  4. ///|/ Copyright (c) 2020 Paul Arden @ardenpm
  5. ///|/ Copyright (c) 2020 sckunkle
  6. ///|/ Copyright (c) 2020 Kyle Maas @KyleMaas
  7. ///|/ Copyright (c) 2019 Thomas Moore
  8. ///|/ Copyright (c) 2019 Bryan Smith
  9. ///|/ Copyright (c) Slic3r 2015 - 2016 Alessandro Ranellucci @alranel
  10. ///|/ Copyright (c) 2016 Chow Loong Jin @hyperair
  11. ///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
  12. ///|/ Copyright (c) 2015 Vicious-one @Vicious-one
  13. ///|/ Copyright (c) 2015 Luís Andrade
  14. ///|/
  15. ///|/ ported from lib/Slic3r/GCode.pm:
  16. ///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel
  17. ///|/ Copyright (c) 2013 Robert Giseburt
  18. ///|/ Copyright (c) 2012 Mark Hindess
  19. ///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen
  20. ///|/
  21. ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
  22. ///|/
  23. #include "Config.hpp"
  24. #include "Geometry/Circle.hpp"
  25. #include "libslic3r.h"
  26. #include "GCode/ExtrusionProcessor.hpp"
  27. #include "I18N.hpp"
  28. #include "GCode.hpp"
  29. #include "Exception.hpp"
  30. #include "ExtrusionEntity.hpp"
  31. #include "Geometry/ConvexHull.hpp"
  32. #include "GCode/LabelObjects.hpp"
  33. #include "GCode/PrintExtents.hpp"
  34. #include "GCode/Thumbnails.hpp"
  35. #include "GCode/WipeTower.hpp"
  36. #include "GCode/WipeTowerIntegration.hpp"
  37. #include "GCode/Travels.hpp"
  38. #include "Point.hpp"
  39. #include "Polygon.hpp"
  40. #include "PrintConfig.hpp"
  41. #include "ShortestPath.hpp"
  42. #include "Print.hpp"
  43. #include "Thread.hpp"
  44. #include "Utils.hpp"
  45. #include "ClipperUtils.hpp"
  46. #include "libslic3r.h"
  47. #include "LocalesUtils.hpp"
  48. #include "format.hpp"
  49. #include <algorithm>
  50. #include <cstdlib>
  51. #include <chrono>
  52. #include <math.h>
  53. #include <optional>
  54. #include <string>
  55. #include <string_view>
  56. #include <boost/algorithm/string.hpp>
  57. #include <boost/algorithm/string/find.hpp>
  58. #include <boost/foreach.hpp>
  59. #include <boost/filesystem.hpp>
  60. #include <boost/log/trivial.hpp>
  61. #include <boost/nowide/iostream.hpp>
  62. #include <boost/nowide/cstdio.hpp>
  63. #include <boost/nowide/cstdlib.hpp>
  64. #include "SVG.hpp"
  65. #include <tbb/parallel_for.h>
  66. // Intel redesigned some TBB interface considerably when merging TBB with their oneAPI set of libraries, see GH #7332.
  67. // We are using quite an old TBB 2017 U7. Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
  68. #if ! defined(TBB_VERSION_MAJOR)
  69. #include <tbb/version.h>
  70. #endif
  71. #if ! defined(TBB_VERSION_MAJOR)
  72. static_assert(false, "TBB_VERSION_MAJOR not defined");
  73. #endif
  74. #if TBB_VERSION_MAJOR >= 2021
  75. #include <tbb/parallel_pipeline.h>
  76. using slic3r_tbb_filtermode = tbb::filter_mode;
  77. #else
  78. #include <tbb/pipeline.h>
  79. using slic3r_tbb_filtermode = tbb::filter;
  80. #endif
  81. using namespace std::literals::string_view_literals;
  82. #if 0
  83. // Enable debugging and asserts, even in the release build.
  84. #define DEBUG
  85. #define _DEBUG
  86. #undef NDEBUG
  87. #endif
  88. #include <assert.h>
  89. namespace Slic3r {
  90. // Only add a newline in case the current G-code does not end with a newline.
  91. static inline void check_add_eol(std::string& gcode)
  92. {
  93. if (!gcode.empty() && gcode.back() != '\n')
  94. gcode += '\n';
  95. }
  96. // Return true if tch_prefix is found in custom_gcode
  97. static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std::string& tch_prefix, unsigned next_extruder)
  98. {
  99. bool ok = false;
  100. size_t from_pos = 0;
  101. size_t pos = 0;
  102. while ((pos = custom_gcode.find(tch_prefix, from_pos)) != std::string::npos) {
  103. if (pos + 1 == custom_gcode.size())
  104. break;
  105. from_pos = pos + 1;
  106. // only whitespace is allowed before the command
  107. while (--pos < custom_gcode.size() && custom_gcode[pos] != '\n') {
  108. if (!std::isspace(custom_gcode[pos]))
  109. goto NEXT;
  110. }
  111. {
  112. // we should also check that the extruder changes to what was expected
  113. std::istringstream ss(custom_gcode.substr(from_pos, std::string::npos));
  114. unsigned num = 0;
  115. if (ss >> num)
  116. ok = (num == next_extruder);
  117. }
  118. NEXT:;
  119. }
  120. return ok;
  121. }
  122. std::string OozePrevention::pre_toolchange(GCodeGenerator &gcodegen)
  123. {
  124. std::string gcode;
  125. unsigned int extruder_id = gcodegen.writer().extruder()->id();
  126. const ConfigOptionIntsNullable& filament_idle_temp = gcodegen.config().idle_temperature;
  127. if (filament_idle_temp.is_nil(extruder_id)) {
  128. // There is no idle temperature defined in filament settings.
  129. // Use the delta value from print config.
  130. if (gcodegen.config().standby_temperature_delta.value != 0) {
  131. // we assume that heating is always slower than cooling, so no need to block
  132. gcode += gcodegen.writer().set_temperature
  133. (this->_get_temp(gcodegen) + gcodegen.config().standby_temperature_delta.value, false, extruder_id);
  134. gcode.pop_back();
  135. gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
  136. }
  137. } else {
  138. // Use the value from filament settings. That one is absolute, not delta.
  139. gcode += gcodegen.writer().set_temperature(filament_idle_temp.get_at(extruder_id), false, extruder_id);
  140. gcode.pop_back();
  141. gcode += " ;cooldown\n"; // this is a marker for GCodeProcessor, so it can supress the commands when needed
  142. }
  143. return gcode;
  144. }
  145. std::string OozePrevention::post_toolchange(GCodeGenerator &gcodegen)
  146. {
  147. return (gcodegen.config().standby_temperature_delta.value != 0) ?
  148. gcodegen.writer().set_temperature(this->_get_temp(gcodegen), true, gcodegen.writer().extruder()->id()) :
  149. std::string();
  150. }
  151. int OozePrevention::_get_temp(const GCodeGenerator &gcodegen) const
  152. {
  153. // First layer temperature should be used when on the first layer (obviously) and when
  154. // "other layers" is set to zero (which means it should not be used).
  155. return (gcodegen.layer() == nullptr || gcodegen.layer()->id() == 0
  156. || gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id()) == 0)
  157. ? gcodegen.config().first_layer_temperature.get_at(gcodegen.writer().extruder()->id())
  158. : gcodegen.config().temperature.get_at(gcodegen.writer().extruder()->id());
  159. }
  160. const std::vector<std::string> ColorPrintColors::Colors = { "#C0392B", "#E67E22", "#F1C40F", "#27AE60", "#1ABC9C", "#2980B9", "#9B59B6" };
  161. #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
  162. void GCodeGenerator::PlaceholderParserIntegration::reset()
  163. {
  164. this->failed_templates.clear();
  165. this->output_config.clear();
  166. this->opt_position = nullptr;
  167. this->opt_zhop = nullptr;
  168. this->opt_e_position = nullptr;
  169. this->opt_e_retracted = nullptr;
  170. this->opt_e_restart_extra = nullptr;
  171. this->opt_extruded_volume = nullptr;
  172. this->opt_extruded_weight = nullptr;
  173. this->opt_extruded_volume_total = nullptr;
  174. this->opt_extruded_weight_total = nullptr;
  175. this->num_extruders = 0;
  176. this->position.clear();
  177. this->e_position.clear();
  178. this->e_retracted.clear();
  179. this->e_restart_extra.clear();
  180. }
  181. void GCodeGenerator::PlaceholderParserIntegration::init(const GCodeWriter &writer)
  182. {
  183. this->reset();
  184. const std::vector<Extruder> &extruders = writer.extruders();
  185. if (! extruders.empty()) {
  186. this->num_extruders = extruders.back().id() + 1;
  187. this->e_retracted.assign(num_extruders, 0);
  188. this->e_restart_extra.assign(num_extruders, 0);
  189. this->opt_e_retracted = new ConfigOptionFloats(e_retracted);
  190. this->opt_e_restart_extra = new ConfigOptionFloats(e_restart_extra);
  191. this->output_config.set_key_value("e_retracted", this->opt_e_retracted);
  192. this->output_config.set_key_value("e_restart_extra", this->opt_e_restart_extra);
  193. if (! writer.config.use_relative_e_distances) {
  194. e_position.assign(num_extruders, 0);
  195. opt_e_position = new ConfigOptionFloats(e_position);
  196. this->output_config.set_key_value("e_position", opt_e_position);
  197. }
  198. }
  199. this->opt_extruded_volume = new ConfigOptionFloats(this->num_extruders, 0.f);
  200. this->opt_extruded_weight = new ConfigOptionFloats(this->num_extruders, 0.f);
  201. this->opt_extruded_volume_total = new ConfigOptionFloat(0.f);
  202. this->opt_extruded_weight_total = new ConfigOptionFloat(0.f);
  203. this->parser.set("extruded_volume", this->opt_extruded_volume);
  204. this->parser.set("extruded_weight", this->opt_extruded_weight);
  205. this->parser.set("extruded_volume_total", this->opt_extruded_volume_total);
  206. this->parser.set("extruded_weight_total", this->opt_extruded_weight_total);
  207. // Reserve buffer for current position.
  208. this->position.assign(3, 0);
  209. this->opt_position = new ConfigOptionFloats(this->position);
  210. this->output_config.set_key_value("position", this->opt_position);
  211. // Store zhop variable into the parser itself, it is a read-only variable to the script.
  212. this->opt_zhop = new ConfigOptionFloat(writer.get_zhop());
  213. this->parser.set("zhop", this->opt_zhop);
  214. }
  215. void GCodeGenerator::PlaceholderParserIntegration::update_from_gcodewriter(const GCodeWriter &writer)
  216. {
  217. memcpy(this->position.data(), writer.get_position().data(), sizeof(double) * 3);
  218. this->opt_position->values = this->position;
  219. if (this->num_extruders > 0) {
  220. const std::vector<Extruder> &extruders = writer.extruders();
  221. assert(! extruders.empty() && num_extruders == extruders.back().id() + 1);
  222. this->e_retracted.assign(num_extruders, 0);
  223. this->e_restart_extra.assign(num_extruders, 0);
  224. this->opt_extruded_volume->values.assign(num_extruders, 0);
  225. this->opt_extruded_weight->values.assign(num_extruders, 0);
  226. double total_volume = 0.;
  227. double total_weight = 0.;
  228. for (const Extruder &e : extruders) {
  229. this->e_retracted[e.id()] = e.retracted();
  230. this->e_restart_extra[e.id()] = e.restart_extra();
  231. double v = e.extruded_volume();
  232. double w = v * e.filament_density() * 0.001;
  233. this->opt_extruded_volume->values[e.id()] = v;
  234. this->opt_extruded_weight->values[e.id()] = w;
  235. total_volume += v;
  236. total_weight += w;
  237. }
  238. opt_extruded_volume_total->value = total_volume;
  239. opt_extruded_weight_total->value = total_weight;
  240. opt_e_retracted->values = this->e_retracted;
  241. opt_e_restart_extra->values = this->e_restart_extra;
  242. if (! writer.config.use_relative_e_distances) {
  243. this->e_position.assign(num_extruders, 0);
  244. for (const Extruder &e : extruders)
  245. this->e_position[e.id()] = e.position();
  246. this->opt_e_position->values = this->e_position;
  247. }
  248. }
  249. }
  250. // Throw if any of the output vector variables were resized by the script.
  251. void GCodeGenerator::PlaceholderParserIntegration::validate_output_vector_variables()
  252. {
  253. if (this->opt_position->values.size() != 3)
  254. throw Slic3r::RuntimeError("\"position\" output variable must not be resized by the script.");
  255. if (this->num_extruders > 0) {
  256. if (this->opt_e_position && this->opt_e_position->values.size() != this->num_extruders)
  257. throw Slic3r::RuntimeError("\"e_position\" output variable must not be resized by the script.");
  258. if (this->opt_e_retracted->values.size() != this->num_extruders)
  259. throw Slic3r::RuntimeError("\"e_retracted\" output variable must not be resized by the script.");
  260. if (this->opt_e_restart_extra->values.size() != this->num_extruders)
  261. throw Slic3r::RuntimeError("\"e_restart_extra\" output variable must not be resized by the script.");
  262. }
  263. }
  264. // Collect pairs of object_layer + support_layer sorted by print_z.
  265. // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
  266. GCodeGenerator::ObjectsLayerToPrint GCodeGenerator::collect_layers_to_print(const PrintObject& object)
  267. {
  268. GCodeGenerator::ObjectsLayerToPrint layers_to_print;
  269. layers_to_print.reserve(object.layers().size() + object.support_layers().size());
  270. /*
  271. // Calculate a minimum support layer height as a minimum over all extruders, but not smaller than 10um.
  272. // This is the same logic as in support generator.
  273. //FIXME should we use the printing extruders instead?
  274. double gap_over_supports = object.config().support_material_contact_distance;
  275. // FIXME should we test object.config().support_material_synchronize_layers ? Currently the support layers are synchronized with object layers iff soluble supports.
  276. assert(!object.has_support() || gap_over_supports != 0. || object.config().support_material_synchronize_layers);
  277. if (gap_over_supports != 0.) {
  278. gap_over_supports = std::max(0., gap_over_supports);
  279. // Not a soluble support,
  280. double support_layer_height_min = 1000000.;
  281. for (auto lh : object.print()->config().min_layer_height.values)
  282. support_layer_height_min = std::min(support_layer_height_min, std::max(0.01, lh));
  283. gap_over_supports += support_layer_height_min;
  284. }*/
  285. std::vector<std::pair<double, double>> warning_ranges;
  286. // Pair the object layers with the support layers by z.
  287. size_t idx_object_layer = 0;
  288. size_t idx_support_layer = 0;
  289. const ObjectLayerToPrint* last_extrusion_layer = nullptr;
  290. while (idx_object_layer < object.layers().size() || idx_support_layer < object.support_layers().size()) {
  291. ObjectLayerToPrint layer_to_print;
  292. layer_to_print.object_layer = (idx_object_layer < object.layers().size()) ? object.layers()[idx_object_layer++] : nullptr;
  293. layer_to_print.support_layer = (idx_support_layer < object.support_layers().size()) ? object.support_layers()[idx_support_layer++] : nullptr;
  294. if (layer_to_print.object_layer && layer_to_print.support_layer) {
  295. if (layer_to_print.object_layer->print_z < layer_to_print.support_layer->print_z - EPSILON) {
  296. layer_to_print.support_layer = nullptr;
  297. --idx_support_layer;
  298. }
  299. else if (layer_to_print.support_layer->print_z < layer_to_print.object_layer->print_z - EPSILON) {
  300. layer_to_print.object_layer = nullptr;
  301. --idx_object_layer;
  302. }
  303. }
  304. layers_to_print.emplace_back(layer_to_print);
  305. bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
  306. || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions());
  307. // Check that there are extrusions on the very first layer. The case with empty
  308. // first layer may result in skirt/brim in the air and maybe other issues.
  309. if (layers_to_print.size() == 1u) {
  310. if (!has_extrusions)
  311. throw Slic3r::SlicingError(_u8L("There is an object with no extrusions in the first layer.") + "\n" +
  312. _u8L("Object name") + ": " + object.model_object()->name);
  313. }
  314. // In case there are extrusions on this layer, check there is a layer to lay it on.
  315. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions())
  316. // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions.
  317. || (layer_to_print.support_layer /* && layer_to_print.support_layer->has_extrusions() */)) {
  318. double top_cd = object.config().support_material_contact_distance;
  319. double bottom_cd = object.config().support_material_bottom_contact_distance == 0. ? top_cd : object.config().support_material_bottom_contact_distance;
  320. double extra_gap = (layer_to_print.support_layer ? bottom_cd : top_cd);
  321. double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.)
  322. + layer_to_print.layer()->height
  323. + std::max(0., extra_gap);
  324. // Negative support_contact_z is not taken into account, it can result in false positives in cases
  325. // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752)
  326. if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON)
  327. warning_ranges.emplace_back(std::make_pair((last_extrusion_layer ? last_extrusion_layer->print_z() : 0.), layers_to_print.back().print_z()));
  328. }
  329. // Remember last layer with extrusions.
  330. if (has_extrusions)
  331. last_extrusion_layer = &layers_to_print.back();
  332. }
  333. if (! warning_ranges.empty()) {
  334. std::string warning;
  335. size_t i = 0;
  336. for (i = 0; i < std::min(warning_ranges.size(), size_t(3)); ++i)
  337. warning += Slic3r::format(_u8L("Empty layer between %1% and %2%."),
  338. warning_ranges[i].first, warning_ranges[i].second) + "\n";
  339. if (i < warning_ranges.size())
  340. warning += _u8L("(Some lines not shown)") + "\n";
  341. warning += "\n";
  342. warning += Slic3r::format(_u8L("Object name: %1%"), object.model_object()->name) + "\n\n"
  343. + _u8L("Make sure the object is printable. This is usually caused by negligibly small extrusions or by a faulty model. "
  344. "Try to repair the model or change its orientation on the bed.");
  345. const_cast<Print*>(object.print())->active_step_add_warning(
  346. PrintStateBase::WarningLevel::CRITICAL, warning);
  347. }
  348. return layers_to_print;
  349. }
  350. // Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z
  351. // will be printed for all objects at once.
  352. // Return a list of <print_z, per object ObjectLayerToPrint> items.
  353. std::vector<std::pair<coordf_t, GCodeGenerator::ObjectsLayerToPrint>> GCodeGenerator::collect_layers_to_print(const Print& print)
  354. {
  355. struct OrderingItem {
  356. coordf_t print_z;
  357. size_t object_idx;
  358. size_t layer_idx;
  359. };
  360. std::vector<ObjectsLayerToPrint> per_object(print.objects().size(), ObjectsLayerToPrint());
  361. std::vector<OrderingItem> ordering;
  362. for (size_t i = 0; i < print.objects().size(); ++i) {
  363. per_object[i] = collect_layers_to_print(*print.objects()[i]);
  364. OrderingItem ordering_item;
  365. ordering_item.object_idx = i;
  366. ordering.reserve(ordering.size() + per_object[i].size());
  367. const ObjectLayerToPrint &front = per_object[i].front();
  368. for (const ObjectLayerToPrint &ltp : per_object[i]) {
  369. ordering_item.print_z = ltp.print_z();
  370. ordering_item.layer_idx = &ltp - &front;
  371. ordering.emplace_back(ordering_item);
  372. }
  373. }
  374. std::sort(ordering.begin(), ordering.end(), [](const OrderingItem& oi1, const OrderingItem& oi2) { return oi1.print_z < oi2.print_z; });
  375. std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print;
  376. // Merge numerically very close Z values.
  377. for (size_t i = 0; i < ordering.size();) {
  378. // Find the last layer with roughly the same print_z.
  379. size_t j = i + 1;
  380. coordf_t zmax = ordering[i].print_z + EPSILON;
  381. for (; j < ordering.size() && ordering[j].print_z <= zmax; ++j);
  382. // Merge into layers_to_print.
  383. std::pair<coordf_t, ObjectsLayerToPrint> merged;
  384. // Assign an average print_z to the set of layers with nearly equal print_z.
  385. merged.first = 0.5 * (ordering[i].print_z + ordering[j - 1].print_z);
  386. merged.second.assign(print.objects().size(), ObjectLayerToPrint());
  387. for (; i < j; ++i) {
  388. const OrderingItem& oi = ordering[i];
  389. assert(merged.second[oi.object_idx].layer() == nullptr);
  390. merged.second[oi.object_idx] = std::move(per_object[oi.object_idx][oi.layer_idx]);
  391. }
  392. layers_to_print.emplace_back(std::move(merged));
  393. }
  394. return layers_to_print;
  395. }
  396. // free functions called by GCodeGenerator::do_export()
  397. namespace DoExport {
  398. // static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics)
  399. // {
  400. // const GCodeProcessorResult& result = processor.get_result();
  401. // print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time);
  402. // print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
  403. // get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A";
  404. // }
  405. static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector<Extruder>& extruders, PrintStatistics& print_statistics)
  406. {
  407. const GCodeProcessorResult& result = processor.get_result();
  408. print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time);
  409. print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ?
  410. get_time_dhms(result.print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A";
  411. // update filament statictics
  412. double total_extruded_volume = 0.0;
  413. double total_used_filament = 0.0;
  414. double total_weight = 0.0;
  415. double total_cost = 0.0;
  416. for (auto volume : result.print_statistics.volumes_per_extruder) {
  417. total_extruded_volume += volume.second;
  418. size_t extruder_id = volume.first;
  419. auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) { return extr.id() == extruder_id; });
  420. if (extruder == extruders.end())
  421. continue;
  422. double s = PI * sqr(0.5* extruder->filament_diameter());
  423. double weight = volume.second * extruder->filament_density() * 0.001;
  424. total_used_filament += volume.second/s;
  425. total_weight += weight;
  426. total_cost += weight * extruder->filament_cost() * 0.001;
  427. }
  428. print_statistics.total_extruded_volume = total_extruded_volume;
  429. print_statistics.total_used_filament = total_used_filament;
  430. print_statistics.total_weight = total_weight;
  431. print_statistics.total_cost = total_cost;
  432. print_statistics.filament_stats = result.print_statistics.volumes_per_extruder;
  433. }
  434. // if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found
  435. // into pairs containing:
  436. // first: source
  437. // second: keyword
  438. // to be shown in the warning notification
  439. // The returned vector is empty if no keyword has been found
  440. static std::vector<std::pair<std::string, std::string>> validate_custom_gcode(const Print& print) {
  441. static const unsigned int MAX_TAGS_COUNT = 5;
  442. std::vector<std::pair<std::string, std::string>> ret;
  443. auto check = [&ret](const std::string& source, const std::string& gcode) {
  444. std::vector<std::string> tags;
  445. if (GCodeProcessor::contains_reserved_tags(gcode, MAX_TAGS_COUNT, tags)) {
  446. if (!tags.empty()) {
  447. size_t i = 0;
  448. while (ret.size() < MAX_TAGS_COUNT && i < tags.size()) {
  449. ret.push_back({ source, tags[i] });
  450. ++i;
  451. }
  452. }
  453. }
  454. };
  455. const GCodeConfig& config = print.config();
  456. check(_u8L("Start G-code"), config.start_gcode.value);
  457. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("End G-code"), config.end_gcode.value);
  458. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Before layer change G-code"), config.before_layer_gcode.value);
  459. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("After layer change G-code"), config.layer_gcode.value);
  460. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Tool change G-code"), config.toolchange_gcode.value);
  461. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Between objects G-code (for sequential printing)"), config.between_objects_gcode.value);
  462. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Color Change G-code"), config.color_change_gcode.value);
  463. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Pause Print G-code"), config.pause_print_gcode.value);
  464. if (ret.size() < MAX_TAGS_COUNT) check(_u8L("Template Custom G-code"), config.template_custom_gcode.value);
  465. if (ret.size() < MAX_TAGS_COUNT) {
  466. for (const std::string& value : config.start_filament_gcode.values) {
  467. check(_u8L("Filament Start G-code"), value);
  468. if (ret.size() == MAX_TAGS_COUNT)
  469. break;
  470. }
  471. }
  472. if (ret.size() < MAX_TAGS_COUNT) {
  473. for (const std::string& value : config.end_filament_gcode.values) {
  474. check(_u8L("Filament End G-code"), value);
  475. if (ret.size() == MAX_TAGS_COUNT)
  476. break;
  477. }
  478. }
  479. if (ret.size() < MAX_TAGS_COUNT) {
  480. const CustomGCode::Info& custom_gcode_per_print_z = print.model().custom_gcode_per_print_z;
  481. for (const auto& gcode : custom_gcode_per_print_z.gcodes) {
  482. check(_u8L("Custom G-code"), gcode.extra);
  483. if (ret.size() == MAX_TAGS_COUNT)
  484. break;
  485. }
  486. }
  487. return ret;
  488. }
  489. } // namespace DoExport
  490. void GCodeGenerator::do_export(Print* print, const char* path, GCodeProcessorResult* result, ThumbnailsGeneratorCallback thumbnail_cb)
  491. {
  492. CNumericLocalesSetter locales_setter;
  493. // Does the file exist? If so, we hope that it is still valid.
  494. {
  495. PrintStateBase::StateWithTimeStamp state = print->step_state_with_timestamp(psGCodeExport);
  496. if (! state.enabled || (state.is_done() && boost::filesystem::exists(boost::filesystem::path(path))))
  497. return;
  498. }
  499. // Enabled and either not done, or marked as done while the output file is missing.
  500. print->set_started(psGCodeExport);
  501. // check if any custom gcode contains keywords used by the gcode processor to
  502. // produce time estimation and gcode toolpaths
  503. std::vector<std::pair<std::string, std::string>> validation_res = DoExport::validate_custom_gcode(*print);
  504. if (!validation_res.empty()) {
  505. std::string reports;
  506. for (const auto& [source, keyword] : validation_res) {
  507. reports += source + ": \"" + keyword + "\"\n";
  508. }
  509. print->active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
  510. _u8L("In the custom G-code were found reserved keywords:") + "\n" +
  511. reports +
  512. _u8L("This may cause problems in g-code visualization and printing time estimation."));
  513. }
  514. BOOST_LOG_TRIVIAL(info) << "Exporting G-code..." << log_memory_info();
  515. // Remove the old g-code if it exists.
  516. boost::nowide::remove(path);
  517. std::string path_tmp(path);
  518. path_tmp += ".tmp";
  519. m_processor.initialize(path_tmp);
  520. m_processor.set_print(print);
  521. m_processor.get_binary_data() = bgcode::binarize::BinaryData();
  522. GCodeOutputStream file(boost::nowide::fopen(path_tmp.c_str(), "wb"), m_processor);
  523. if (! file.is_open())
  524. throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n");
  525. try {
  526. this->_do_export(*print, file, thumbnail_cb);
  527. file.flush();
  528. if (file.is_error()) {
  529. file.close();
  530. boost::nowide::remove(path_tmp.c_str());
  531. throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n");
  532. }
  533. } catch (std::exception & /* ex */) {
  534. // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown.
  535. // Close and remove the file.
  536. file.close();
  537. boost::nowide::remove(path_tmp.c_str());
  538. throw;
  539. }
  540. file.close();
  541. if (! m_placeholder_parser_integration.failed_templates.empty()) {
  542. // G-code export proceeded, but some of the PlaceholderParser substitutions failed.
  543. //FIXME localize!
  544. std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n";
  545. for (const auto &name_and_error : m_placeholder_parser_integration.failed_templates)
  546. msg += name_and_error.first + "\n" + name_and_error.second + "\n";
  547. msg += "\nPlease inspect the file ";
  548. msg += path_tmp + " for error messages enclosed between\n";
  549. msg += " !!!!! Failed to process the custom G-code template ...\n";
  550. msg += "and\n";
  551. msg += " !!!!! End of an error report for the custom G-code template ...\n";
  552. msg += "for all macro processing errors.";
  553. throw Slic3r::PlaceholderParserError(msg);
  554. }
  555. BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info();
  556. // Post-process the G-code to update time stamps.
  557. m_processor.finalize(true);
  558. // DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics);
  559. DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics);
  560. if (result != nullptr) {
  561. *result = std::move(m_processor.extract_result());
  562. // set the filename to the correct value
  563. result->filename = path;
  564. }
  565. BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info();
  566. if (rename_file(path_tmp, path))
  567. throw Slic3r::RuntimeError(
  568. std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
  569. "Is " + path_tmp + " locked?" + '\n');
  570. BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info();
  571. print->set_done(psGCodeExport);
  572. }
  573. // free functions called by GCodeGenerator::_do_export()
  574. namespace DoExport {
  575. static void init_gcode_processor(const PrintConfig& config, GCodeProcessor& processor, bool& silent_time_estimator_enabled)
  576. {
  577. silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlinLegacy || config.gcode_flavor == gcfMarlinFirmware)
  578. && config.silent_mode;
  579. processor.reset();
  580. processor.initialize_result_moves();
  581. processor.apply_config(config);
  582. processor.enable_stealth_time_estimator(silent_time_estimator_enabled);
  583. }
  584. static double autospeed_volumetric_limit(const Print &print)
  585. {
  586. // get the minimum cross-section used in the print
  587. std::vector<double> mm3_per_mm;
  588. for (auto object : print.objects()) {
  589. for (size_t region_id = 0; region_id < object->num_printing_regions(); ++ region_id) {
  590. const PrintRegion &region = object->printing_region(region_id);
  591. for (auto layer : object->layers()) {
  592. const LayerRegion* layerm = layer->regions()[region_id];
  593. if (region.config().get_abs_value("perimeter_speed") == 0 ||
  594. region.config().get_abs_value("small_perimeter_speed") == 0 ||
  595. region.config().get_abs_value("external_perimeter_speed") == 0 ||
  596. region.config().get_abs_value("bridge_speed") == 0)
  597. mm3_per_mm.push_back(layerm->perimeters().min_mm3_per_mm());
  598. if (region.config().get_abs_value("infill_speed") == 0 ||
  599. region.config().get_abs_value("solid_infill_speed") == 0 ||
  600. region.config().get_abs_value("top_solid_infill_speed") == 0 ||
  601. region.config().get_abs_value("bridge_speed") == 0)
  602. {
  603. // Minimal volumetric flow should not be calculated over ironing extrusions.
  604. // Use following lambda instead of the built-it method.
  605. // https://github.com/prusa3d/PrusaSlicer/issues/5082
  606. auto min_mm3_per_mm_no_ironing = [](const ExtrusionEntityCollection& eec) -> double {
  607. double min = std::numeric_limits<double>::max();
  608. for (const ExtrusionEntity* ee : eec.entities)
  609. if (ee->role() != ExtrusionRole::Ironing)
  610. min = std::min(min, ee->min_mm3_per_mm());
  611. return min;
  612. };
  613. mm3_per_mm.push_back(min_mm3_per_mm_no_ironing(layerm->fills()));
  614. }
  615. }
  616. }
  617. if (object->config().get_abs_value("support_material_speed") == 0 ||
  618. object->config().get_abs_value("support_material_interface_speed") == 0)
  619. for (auto layer : object->support_layers())
  620. mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
  621. }
  622. // filter out 0-width segments
  623. mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
  624. double volumetric_speed = 0.;
  625. if (! mm3_per_mm.empty()) {
  626. // In order to honor max_print_speed we need to find a target volumetric
  627. // speed that we can use throughout the print. So we define this target
  628. // volumetric speed as the volumetric speed produced by printing the
  629. // smallest cross-section at the maximum speed: any larger cross-section
  630. // will need slower feedrates.
  631. volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value;
  632. // limit such volumetric speed with max_volumetric_speed if set
  633. if (print.config().max_volumetric_speed.value > 0)
  634. volumetric_speed = std::min(volumetric_speed, print.config().max_volumetric_speed.value);
  635. }
  636. return volumetric_speed;
  637. }
  638. static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention)
  639. {
  640. ooze_prevention.enable = print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material;
  641. }
  642. // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
  643. static std::string update_print_stats_and_format_filament_stats(
  644. const bool has_wipe_tower,
  645. const WipeTowerData &wipe_tower_data,
  646. const FullPrintConfig &config,
  647. const std::vector<Extruder> &extruders,
  648. unsigned int initial_extruder_id,
  649. int total_toolchanges,
  650. PrintStatistics &print_statistics,
  651. bool export_binary_data,
  652. bgcode::binarize::BinaryData &binary_data)
  653. {
  654. std::string filament_stats_string_out;
  655. print_statistics.clear();
  656. print_statistics.total_toolchanges = total_toolchanges;
  657. print_statistics.initial_extruder_id = initial_extruder_id;
  658. std::vector<std::string> filament_types;
  659. if (! extruders.empty()) {
  660. std::pair<std::string, unsigned int> out_filament_used_mm(PrintStatistics::FilamentUsedMmMask + " ", 0);
  661. std::pair<std::string, unsigned int> out_filament_used_cm3(PrintStatistics::FilamentUsedCm3Mask + " ", 0);
  662. std::pair<std::string, unsigned int> out_filament_used_g(PrintStatistics::FilamentUsedGMask + " ", 0);
  663. std::pair<std::string, unsigned int> out_filament_cost(PrintStatistics::FilamentCostMask + " ", 0);
  664. for (const Extruder &extruder : extruders) {
  665. print_statistics.printing_extruders.emplace_back(extruder.id());
  666. filament_types.emplace_back(config.filament_type.get_at(extruder.id()));
  667. double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
  668. double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
  669. double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
  670. double filament_cost = filament_weight * extruder.filament_cost() * 0.001;
  671. auto append = [&extruder](std::pair<std::string, unsigned int> &dst, const char *tmpl, double value) {
  672. assert(is_decimal_separator_point());
  673. while (dst.second < extruder.id()) {
  674. // Fill in the non-printing extruders with zeros.
  675. dst.first += (dst.second > 0) ? ", 0" : "0";
  676. ++ dst.second;
  677. }
  678. if (dst.second > 0)
  679. dst.first += ", ";
  680. char buf[64];
  681. sprintf(buf, tmpl, value);
  682. dst.first += buf;
  683. ++ dst.second;
  684. };
  685. if (!export_binary_data) {
  686. append(out_filament_used_mm, "%.2lf", used_filament);
  687. append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001);
  688. }
  689. if (filament_weight > 0.) {
  690. print_statistics.total_weight = print_statistics.total_weight + filament_weight;
  691. if (!export_binary_data)
  692. append(out_filament_used_g, "%.2lf", filament_weight);
  693. if (filament_cost > 0.) {
  694. print_statistics.total_cost = print_statistics.total_cost + filament_cost;
  695. if (!export_binary_data)
  696. append(out_filament_cost, "%.2lf", filament_cost);
  697. }
  698. }
  699. print_statistics.total_used_filament += used_filament;
  700. print_statistics.total_extruded_volume += extruded_volume;
  701. print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.;
  702. print_statistics.total_wipe_tower_filament_weight += has_wipe_tower ? (extruded_volume - extruder.extruded_volume()) * extruder.filament_density() * 0.001 : 0.;
  703. print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.;
  704. }
  705. if (!export_binary_data) {
  706. filament_stats_string_out += out_filament_used_mm.first;
  707. filament_stats_string_out += "\n" + out_filament_used_cm3.first;
  708. if (out_filament_used_g.second)
  709. filament_stats_string_out += "\n" + out_filament_used_g.first;
  710. if (out_filament_cost.second)
  711. filament_stats_string_out += "\n" + out_filament_cost.first;
  712. }
  713. print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id);
  714. std::sort(filament_types.begin(), filament_types.end());
  715. print_statistics.printing_filament_types = filament_types.front();
  716. for (size_t i = 1; i < filament_types.size(); ++ i) {
  717. print_statistics.printing_filament_types += ",";
  718. print_statistics.printing_filament_types += filament_types[i];
  719. }
  720. }
  721. return filament_stats_string_out;
  722. }
  723. }
  724. #if 0
  725. // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints.
  726. static inline std::vector<const PrintInstance*> sort_object_instances_by_max_z(const Print &print)
  727. {
  728. std::vector<const PrintObject*> objects(print.objects().begin(), print.objects().end());
  729. std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); });
  730. std::vector<const PrintInstance*> instances;
  731. instances.reserve(objects.size());
  732. for (const PrintObject *object : objects)
  733. for (size_t i = 0; i < object->instances().size(); ++ i)
  734. instances.emplace_back(&object->instances()[i]);
  735. return instances;
  736. }
  737. #endif
  738. // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model().
  739. std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Print& print)
  740. {
  741. // Build up map from ModelInstance* to PrintInstance*
  742. std::vector<std::pair<const ModelInstance*, const PrintInstance*>> model_instance_to_print_instance;
  743. model_instance_to_print_instance.reserve(print.num_object_instances());
  744. for (const PrintObject *print_object : print.objects())
  745. for (const PrintInstance &print_instance : print_object->instances())
  746. model_instance_to_print_instance.emplace_back(print_instance.model_instance, &print_instance);
  747. std::sort(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), [](auto &l, auto &r) { return l.first < r.first; });
  748. std::vector<const PrintInstance*> instances;
  749. instances.reserve(model_instance_to_print_instance.size());
  750. for (const ModelObject *model_object : print.model().objects)
  751. for (const ModelInstance *model_instance : model_object->instances) {
  752. auto it = std::lower_bound(model_instance_to_print_instance.begin(), model_instance_to_print_instance.end(), std::make_pair(model_instance, nullptr), [](auto &l, auto &r) { return l.first < r.first; });
  753. if (it != model_instance_to_print_instance.end() && it->first == model_instance)
  754. instances.emplace_back(it->second);
  755. }
  756. return instances;
  757. }
  758. static inline bool arc_welder_enabled(const PrintConfig& print_config)
  759. {
  760. return
  761. // Enabled
  762. print_config.arc_fitting != ArcFittingType::Disabled &&
  763. // Not a spiral vase print
  764. !print_config.spiral_vase &&
  765. // Presure equalizer not used
  766. print_config.max_volumetric_extrusion_rate_slope_negative == 0. &&
  767. print_config.max_volumetric_extrusion_rate_slope_positive == 0.;
  768. }
  769. static inline GCode::SmoothPathCache::InterpolationParameters interpolation_parameters(const PrintConfig& print_config)
  770. {
  771. return {
  772. scaled<double>(print_config.gcode_resolution.value),
  773. arc_welder_enabled(print_config) ? Geometry::ArcWelder::default_arc_length_percent_tolerance : 0
  774. };
  775. }
  776. static inline GCode::SmoothPathCache smooth_path_interpolate_global(const Print& print)
  777. {
  778. const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config());
  779. GCode::SmoothPathCache out;
  780. out.interpolate_add(print.skirt(), interpolation_params);
  781. out.interpolate_add(print.brim(), interpolation_params);
  782. return out;
  783. }
  784. void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGeneratorCallback thumbnail_cb)
  785. {
  786. const bool export_to_binary_gcode = print.full_print_config().option<ConfigOptionBool>("binary_gcode")->value;
  787. // if exporting gcode in binary format:
  788. // we generate here the data to be passed to the post-processor, who is responsible to export them to file
  789. // 1) generate the thumbnails
  790. // 2) collect the config data
  791. if (export_to_binary_gcode) {
  792. bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data();
  793. // Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
  794. // If "thumbnails_format" is not defined, export to PNG.
  795. auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
  796. if (errors != enum_bitmask<ThumbnailError>()) {
  797. std::string error_str = format("Invalid thumbnails value:");
  798. error_str += GCodeThumbnails::get_error_string(errors);
  799. throw Slic3r::ExportError(error_str);
  800. }
  801. if (!thumbnails.empty())
  802. GCodeThumbnails::generate_binary_thumbnails(
  803. thumbnail_cb, binary_data.thumbnails, thumbnails,
  804. [&print]() { print.throw_if_canceled(); });
  805. // file data
  806. binary_data.file_metadata.raw_data.emplace_back("Producer", std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION));
  807. // config data
  808. encode_full_config(print, binary_data.slicer_metadata.raw_data);
  809. // printer data - this section contains duplicates from the slicer metadata
  810. // that we just created. Find and copy the entries that we want to duplicate.
  811. const auto& slicer_metadata = binary_data.slicer_metadata.raw_data;
  812. const std::vector<std::string> keys_to_duplicate = { "printer_model", "filament_type", "nozzle_diameter", "bed_temperature",
  813. "brim_width", "fill_density", "layer_height", "temperature", "ironing", "support_material", "extruder_colour" };
  814. assert(std::is_sorted(slicer_metadata.begin(), slicer_metadata.end(),
  815. [](const auto& a, const auto& b) { return a.first < b.first; }));
  816. for (const std::string& key : keys_to_duplicate) {
  817. auto it = std::lower_bound(slicer_metadata.begin(), slicer_metadata.end(), std::make_pair(key, 0),
  818. [](const auto& a, const auto& b) { return a.first < b.first; });
  819. if (it != slicer_metadata.end() && it->first == key)
  820. binary_data.printer_metadata.raw_data.emplace_back(*it);
  821. }
  822. }
  823. // modifies m_silent_time_estimator_enabled
  824. DoExport::init_gcode_processor(print.config(), m_processor, m_silent_time_estimator_enabled);
  825. if (! print.config().gcode_substitutions.values.empty()) {
  826. m_find_replace = make_unique<GCodeFindReplace>(print.config());
  827. file.set_find_replace(m_find_replace.get(), false);
  828. }
  829. // resets analyzer's tracking data
  830. m_last_height = 0.f;
  831. m_last_layer_z = 0.f;
  832. m_max_layer_z = 0.f;
  833. m_last_width = 0.f;
  834. #if ENABLE_GCODE_VIEWER_DATA_CHECKING
  835. m_last_mm3_per_mm = 0.;
  836. #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
  837. // How many times will be change_layer() called?
  838. // change_layer() in turn increments the progress bar status.
  839. m_layer_count = 0;
  840. if (print.config().complete_objects.value) {
  841. // Add each of the object's layers separately.
  842. for (auto object : print.objects()) {
  843. std::vector<coordf_t> zs;
  844. zs.reserve(object->layers().size() + object->support_layers().size());
  845. for (auto layer : object->layers())
  846. zs.push_back(layer->print_z);
  847. for (auto layer : object->support_layers())
  848. zs.push_back(layer->print_z);
  849. std::sort(zs.begin(), zs.end());
  850. m_layer_count += (unsigned int)(object->instances().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
  851. }
  852. }
  853. print.throw_if_canceled();
  854. m_enable_cooling_markers = true;
  855. this->apply_print_config(print.config());
  856. m_volumetric_speed = DoExport::autospeed_volumetric_limit(print);
  857. print.throw_if_canceled();
  858. if (print.config().spiral_vase.value)
  859. m_spiral_vase = make_unique<SpiralVase>(print.config());
  860. if (print.config().max_volumetric_extrusion_rate_slope_positive.value > 0 ||
  861. print.config().max_volumetric_extrusion_rate_slope_negative.value > 0)
  862. m_pressure_equalizer = make_unique<PressureEqualizer>(print.config());
  863. m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
  864. if (print.config().avoid_crossing_curled_overhangs){
  865. this->m_avoid_crossing_curled_overhangs.init_bed_shape(get_bed_shape(print.config()));
  866. }
  867. if (!export_to_binary_gcode)
  868. // Write information on the generator.
  869. file.write_format("; %s\n\n", Slic3r::header_slic3r_generated().c_str());
  870. if (! export_to_binary_gcode) {
  871. // if exporting gcode in ascii format, generate the thumbnails here
  872. auto [thumbnails, errors] = GCodeThumbnails::make_and_check_thumbnail_list(print.full_print_config());
  873. if (errors != enum_bitmask<ThumbnailError>()) {
  874. std::string error_str = format("Invalid thumbnails value:");
  875. error_str += GCodeThumbnails::get_error_string(errors);
  876. throw Slic3r::ExportError(error_str);
  877. }
  878. if (!thumbnails.empty())
  879. GCodeThumbnails::export_thumbnails_to_file(thumbnail_cb, thumbnails,
  880. [&file](const char* sz) { file.write(sz); },
  881. [&print]() { print.throw_if_canceled(); });
  882. }
  883. // Write notes (content of the Print Settings tab -> Notes)
  884. {
  885. std::list<std::string> lines;
  886. boost::split(lines, print.config().notes.value, boost::is_any_of("\n"), boost::token_compress_off);
  887. for (auto line : lines) {
  888. // Remove the trailing '\r' from the '\r\n' sequence.
  889. if (! line.empty() && line.back() == '\r')
  890. line.pop_back();
  891. file.write_format("; %s\n", line.c_str());
  892. }
  893. if (! lines.empty())
  894. file.write("\n");
  895. }
  896. print.throw_if_canceled();
  897. // Write some terse information on the slicing parameters.
  898. const PrintObject *first_object = print.objects().front();
  899. const double layer_height = first_object->config().layer_height.value;
  900. assert(! print.config().first_layer_height.percent);
  901. const double first_layer_height = print.config().first_layer_height.value;
  902. if (!export_to_binary_gcode) {
  903. for (size_t region_id = 0; region_id < print.num_print_regions(); ++ region_id) {
  904. const PrintRegion &region = print.get_print_region(region_id);
  905. file.write_format("; external perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frExternalPerimeter, layer_height).width());
  906. file.write_format("; perimeters extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, layer_height).width());
  907. file.write_format("; infill extrusion width = %.2fmm\n", region.flow(*first_object, frInfill, layer_height).width());
  908. file.write_format("; solid infill extrusion width = %.2fmm\n", region.flow(*first_object, frSolidInfill, layer_height).width());
  909. file.write_format("; top infill extrusion width = %.2fmm\n", region.flow(*first_object, frTopSolidInfill, layer_height).width());
  910. if (print.has_support_material())
  911. file.write_format("; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
  912. if (print.config().first_layer_extrusion_width.value > 0)
  913. file.write_format("; first layer extrusion width = %.2fmm\n", region.flow(*first_object, frPerimeter, first_layer_height, true).width());
  914. file.write_format("\n");
  915. }
  916. print.throw_if_canceled();
  917. }
  918. // adds tags for time estimators
  919. if (print.config().remaining_times.value)
  920. file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::First_Line_M73_Placeholder).c_str());
  921. // Starting now, the G-code find / replace post-processor will be enabled.
  922. file.find_replace_enable();
  923. // Prepare the helper object for replacing placeholders in custom G-code and output filename.
  924. m_placeholder_parser_integration.parser = print.placeholder_parser();
  925. m_placeholder_parser_integration.parser.update_timestamp();
  926. m_placeholder_parser_integration.context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count());
  927. // Enable passing global variables between PlaceholderParser invocations.
  928. m_placeholder_parser_integration.context.global_config = std::make_unique<DynamicConfig>();
  929. print.update_object_placeholders(m_placeholder_parser_integration.parser.config_writable(), ".gcode");
  930. // Get optimal tool ordering to minimize tool switches of a multi-exruder print.
  931. // For a print by objects, find the 1st printing object.
  932. ToolOrdering tool_ordering;
  933. unsigned int initial_extruder_id = (unsigned int)-1;
  934. unsigned int final_extruder_id = (unsigned int)-1;
  935. bool has_wipe_tower = false;
  936. std::vector<const PrintInstance*> print_object_instances_ordering;
  937. std::vector<const PrintInstance*>::const_iterator print_object_instance_sequential_active;
  938. if (print.config().complete_objects.value) {
  939. // Order object instances for sequential print.
  940. print_object_instances_ordering = sort_object_instances_by_model_order(print);
  941. // print_object_instances_ordering = sort_object_instances_by_max_z(print);
  942. // Find the 1st printing object, find its tool ordering and the initial extruder ID.
  943. print_object_instance_sequential_active = print_object_instances_ordering.begin();
  944. for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
  945. tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id);
  946. if ((initial_extruder_id = tool_ordering.first_extruder()) != static_cast<unsigned int>(-1))
  947. break;
  948. }
  949. if (initial_extruder_id == static_cast<unsigned int>(-1))
  950. // No object to print was found, cancel the G-code export.
  951. throw Slic3r::SlicingError(_u8L("No extrusions were generated for objects."));
  952. // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode.
  953. // Use the extruder IDs collected from Regions.
  954. this->set_extruders(print.extruders());
  955. } else {
  956. // Find tool ordering for all the objects at once, and the initial extruder ID.
  957. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it.
  958. tool_ordering = print.tool_ordering();
  959. tool_ordering.assign_custom_gcodes(print);
  960. if (tool_ordering.all_extruders().empty())
  961. // No object to print was found, cancel the G-code export.
  962. throw Slic3r::SlicingError(_u8L("No extrusions were generated for objects."));
  963. has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower();
  964. initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ?
  965. // The priming towers will be skipped.
  966. tool_ordering.all_extruders().back() :
  967. // Don't skip the priming towers.
  968. tool_ordering.first_extruder();
  969. // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z.
  970. // Therefore initialize the printing extruders from there.
  971. this->set_extruders(tool_ordering.all_extruders());
  972. // Order object instances using a nearest neighbor search.
  973. print_object_instances_ordering = chain_print_object_instances(print);
  974. m_layer_count = tool_ordering.layer_tools().size();
  975. }
  976. if (initial_extruder_id == (unsigned int)-1) {
  977. // Nothing to print!
  978. initial_extruder_id = 0;
  979. final_extruder_id = 0;
  980. } else {
  981. final_extruder_id = tool_ordering.last_extruder();
  982. assert(final_extruder_id != (unsigned int)-1);
  983. }
  984. print.throw_if_canceled();
  985. m_cooling_buffer = make_unique<CoolingBuffer>(*this);
  986. m_cooling_buffer->set_current_extruder(initial_extruder_id);
  987. // Emit machine envelope limits for the Marlin firmware.
  988. this->print_machine_envelope(file, print);
  989. // Label all objects so printer knows about them since the start.
  990. m_label_objects.init(print);
  991. file.write(m_label_objects.all_objects_header());
  992. // Update output variables after the extruders were initialized.
  993. m_placeholder_parser_integration.init(m_writer);
  994. // Let the start-up script prime the 1st printing tool.
  995. this->placeholder_parser().set("initial_tool", initial_extruder_id);
  996. this->placeholder_parser().set("initial_extruder", initial_extruder_id);
  997. this->placeholder_parser().set("current_extruder", initial_extruder_id);
  998. //Set variable for total layer count so it can be used in custom gcode.
  999. this->placeholder_parser().set("total_layer_count", m_layer_count);
  1000. // Useful for sequential prints.
  1001. this->placeholder_parser().set("current_object_idx", 0);
  1002. // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
  1003. this->placeholder_parser().set("has_wipe_tower", has_wipe_tower);
  1004. this->placeholder_parser().set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
  1005. this->placeholder_parser().set("total_toolchanges", tool_ordering.toolchanges_count());
  1006. {
  1007. BoundingBoxf bbox(print.config().bed_shape.values);
  1008. assert(bbox.defined);
  1009. if (! bbox.defined)
  1010. // This should not happen, but let's make the compiler happy.
  1011. bbox.min = bbox.max = Vec2d::Zero();
  1012. this->placeholder_parser().set("print_bed_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
  1013. this->placeholder_parser().set("print_bed_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
  1014. this->placeholder_parser().set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
  1015. }
  1016. {
  1017. // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line.
  1018. // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower.
  1019. // It does NOT encompass user extrusions generated by custom G-code,
  1020. // therefore it does NOT encompass the initial purge line.
  1021. // It does NOT encompass MMU/MMU2 starting (wipe) areas.
  1022. auto pts = std::make_unique<ConfigOptionPoints>();
  1023. pts->values.reserve(print.first_layer_convex_hull().size());
  1024. for (const Point &pt : print.first_layer_convex_hull().points)
  1025. pts->values.emplace_back(unscale(pt));
  1026. BoundingBoxf bbox(pts->values);
  1027. this->placeholder_parser().set("first_layer_print_convex_hull", pts.release());
  1028. this->placeholder_parser().set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() }));
  1029. this->placeholder_parser().set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() }));
  1030. this->placeholder_parser().set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() }));
  1031. this->placeholder_parser().set("num_extruders", int(print.config().nozzle_diameter.values.size()));
  1032. // PlaceholderParser currently substitues non-existent vector values with the zero'th value, which is harmful in the case of "is_extruder_used[]"
  1033. // as Slicer may lie about availability of such non-existent extruder.
  1034. // We rather sacrifice 256B of memory before we change the behavior of the PlaceholderParser, which should really only fill in the non-existent
  1035. // vector elements for filament parameters.
  1036. std::vector<unsigned char> is_extruder_used(std::max(size_t(255), print.config().nozzle_diameter.size()), 0);
  1037. for (unsigned int extruder_id : tool_ordering.all_extruders())
  1038. is_extruder_used[extruder_id] = true;
  1039. this->placeholder_parser().set("is_extruder_used", new ConfigOptionBools(is_extruder_used));
  1040. }
  1041. // Enable ooze prevention if configured so.
  1042. DoExport::init_ooze_prevention(print, m_ooze_prevention);
  1043. std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
  1044. // Set bed temperature if the start G-code does not contain any bed temp control G-codes.
  1045. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
  1046. // Set extruder(s) temperature before and after start G-code.
  1047. this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
  1048. // adds tag for processor
  1049. file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), gcode_extrusion_role_to_string(GCodeExtrusionRole::Custom).c_str());
  1050. // Write the custom start G-code
  1051. file.writeln(start_gcode);
  1052. this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
  1053. print.throw_if_canceled();
  1054. // Set other general things.
  1055. file.write(this->preamble());
  1056. print.throw_if_canceled();
  1057. // Collect custom seam data from all objects.
  1058. std::function<void(void)> throw_if_canceled_func = [&print]() { print.throw_if_canceled();};
  1059. m_seam_placer.init(print, throw_if_canceled_func);
  1060. if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) {
  1061. // Set initial extruder only after custom start G-code.
  1062. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed.
  1063. file.write(this->set_extruder(initial_extruder_id, 0.));
  1064. }
  1065. GCode::SmoothPathCache smooth_path_cache_global = smooth_path_interpolate_global(print);
  1066. // Do all objects for each layer.
  1067. if (print.config().complete_objects.value) {
  1068. size_t finished_objects = 0;
  1069. const PrintObject *prev_object = (*print_object_instance_sequential_active)->print_object;
  1070. for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++ print_object_instance_sequential_active) {
  1071. const PrintObject &object = *(*print_object_instance_sequential_active)->print_object;
  1072. if (&object != prev_object || tool_ordering.first_extruder() != final_extruder_id) {
  1073. tool_ordering = ToolOrdering(object, final_extruder_id);
  1074. unsigned int new_extruder_id = tool_ordering.first_extruder();
  1075. if (new_extruder_id == (unsigned int)-1)
  1076. // Skip this object.
  1077. continue;
  1078. initial_extruder_id = new_extruder_id;
  1079. final_extruder_id = tool_ordering.last_extruder();
  1080. assert(final_extruder_id != (unsigned int)-1);
  1081. }
  1082. print.throw_if_canceled();
  1083. this->set_origin(unscale((*print_object_instance_sequential_active)->shift));
  1084. if (finished_objects > 0) {
  1085. // Move to the origin position for the copy we're going to print.
  1086. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
  1087. m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
  1088. m_avoid_crossing_perimeters.use_external_mp_once();
  1089. file.write(this->retract_and_wipe());
  1090. file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object"));
  1091. m_enable_cooling_markers = true;
  1092. // Disable motion planner when traveling to first object point.
  1093. m_avoid_crossing_perimeters.disable_once();
  1094. // Ff we are printing the bottom layer of an object, and we have already finished
  1095. // another one, set first layer temperatures. This happens before the Z move
  1096. // is triggered, so machine has more time to reach such temperatures.
  1097. this->placeholder_parser().set("current_object_idx", int(finished_objects));
  1098. std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config().between_objects_gcode.value, initial_extruder_id);
  1099. // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
  1100. this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
  1101. this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
  1102. file.writeln(between_objects_gcode);
  1103. }
  1104. // Reset the cooling buffer internal state (the current position, feed rate, accelerations).
  1105. m_cooling_buffer->reset(this->writer().get_position());
  1106. m_cooling_buffer->set_current_extruder(initial_extruder_id);
  1107. // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
  1108. // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
  1109. // and export G-code into file.
  1110. this->process_layers(print, tool_ordering, collect_layers_to_print(object),
  1111. *print_object_instance_sequential_active - object.instances().data(),
  1112. smooth_path_cache_global, file);
  1113. ++ finished_objects;
  1114. // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
  1115. // Reset it when starting another object from 1st layer.
  1116. m_second_layer_things_done = false;
  1117. prev_object = &object;
  1118. }
  1119. } else {
  1120. // Sort layers by Z.
  1121. // All extrusion moves with the same top layer height are extruded uninterrupted.
  1122. std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> layers_to_print = collect_layers_to_print(print);
  1123. // Prusa Multi-Material wipe tower.
  1124. if (has_wipe_tower && ! layers_to_print.empty()) {
  1125. m_wipe_tower = std::make_unique<GCode::WipeTowerIntegration>(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get());
  1126. // Set position for wipe tower generation.
  1127. Vec3d new_position = this->writer().get_position();
  1128. new_position.z() = first_layer_height + m_config.z_offset.value;
  1129. this->writer().update_position(new_position);
  1130. if (print.config().single_extruder_multi_material_priming) {
  1131. file.write(m_wipe_tower->prime(*this));
  1132. // Verify, whether the print overaps the priming extrusions.
  1133. BoundingBoxf bbox_print(get_print_extrusions_extents(print));
  1134. coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
  1135. for (const PrintObject *print_object : print.objects())
  1136. bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz));
  1137. bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz));
  1138. BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
  1139. bbox_prime.offset(0.5f);
  1140. bool overlap = bbox_prime.overlap(bbox_print);
  1141. if (print.config().gcode_flavor == gcfMarlinLegacy || print.config().gcode_flavor == gcfMarlinFirmware) {
  1142. file.write(this->retract_and_wipe());
  1143. file.write("M300 S800 P500\n"); // Beep for 500ms, tone 800Hz.
  1144. if (overlap) {
  1145. // Wait for the user to remove the priming extrusions.
  1146. file.write("M1 Remove priming towers and click button.\n");
  1147. } else {
  1148. // Just wait for a bit to let the user check, that the priming succeeded.
  1149. //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
  1150. file.write("M1 S10\n");
  1151. }
  1152. } else {
  1153. // This is not Marlin, M1 command is probably not supported.
  1154. // (See https://github.com/prusa3d/PrusaSlicer/issues/5441.)
  1155. if (overlap) {
  1156. print.active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
  1157. _u8L("Your print is very close to the priming regions. "
  1158. "Make sure there is no collision."));
  1159. } else {
  1160. // Just continue printing, no action necessary.
  1161. }
  1162. }
  1163. }
  1164. print.throw_if_canceled();
  1165. }
  1166. // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
  1167. // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
  1168. // and export G-code into file.
  1169. this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print,
  1170. smooth_path_cache_global, file);
  1171. if (m_wipe_tower)
  1172. // Purge the extruder, pull out the active filament.
  1173. file.write(m_wipe_tower->finalize(*this));
  1174. }
  1175. // Write end commands to file.
  1176. file.write(this->retract_and_wipe());
  1177. file.write(m_writer.set_fan(0));
  1178. // adds tag for processor
  1179. file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), gcode_extrusion_role_to_string(GCodeExtrusionRole::Custom).c_str());
  1180. // Process filament-specific gcode in extruder order.
  1181. {
  1182. DynamicConfig config;
  1183. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  1184. config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position().z() - m_config.z_offset.value));
  1185. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  1186. if (print.config().single_extruder_multi_material) {
  1187. // Process the end_filament_gcode for the active filament only.
  1188. int extruder_id = m_writer.extruder()->id();
  1189. config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id));
  1190. file.writeln(this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config));
  1191. } else {
  1192. for (const std::string &end_gcode : print.config().end_filament_gcode.values) {
  1193. int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front());
  1194. config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id));
  1195. file.writeln(this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config));
  1196. }
  1197. }
  1198. file.writeln(this->placeholder_parser_process("end_gcode", print.config().end_gcode, m_writer.extruder()->id(), &config));
  1199. }
  1200. file.write(m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
  1201. file.write(m_writer.postamble());
  1202. // From now to the end of G-code, the G-code find / replace post-processor will be disabled.
  1203. // Thus the PrusaSlicer generated config will NOT be processed by the G-code post-processor, see GH issue #7952.
  1204. file.find_replace_supress();
  1205. // adds tags for time estimators
  1206. if (print.config().remaining_times.value)
  1207. file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Last_Line_M73_Placeholder).c_str());
  1208. print.throw_if_canceled();
  1209. // Get filament stats.
  1210. const std::string filament_stats_string_out = DoExport::update_print_stats_and_format_filament_stats(
  1211. // Const inputs
  1212. has_wipe_tower, print.wipe_tower_data(),
  1213. this->config(),
  1214. m_writer.extruders(),
  1215. initial_extruder_id,
  1216. tool_ordering.toolchanges_count(),
  1217. // Modifies
  1218. print.m_print_statistics,
  1219. export_to_binary_gcode,
  1220. m_processor.get_binary_data()
  1221. );
  1222. if (!export_to_binary_gcode)
  1223. file.write(filament_stats_string_out);
  1224. if (export_to_binary_gcode) {
  1225. bgcode::binarize::BinaryData& binary_data = m_processor.get_binary_data();
  1226. if (print.m_print_statistics.total_toolchanges > 0)
  1227. binary_data.print_metadata.raw_data.emplace_back("total toolchanges", std::to_string(print.m_print_statistics.total_toolchanges));
  1228. char buf[1024];
  1229. sprintf(buf, "%.2lf", m_max_layer_z);
  1230. binary_data.printer_metadata.raw_data.emplace_back("max_layer_z", buf);
  1231. }
  1232. else {
  1233. // if exporting gcode in ascii format, statistics export is done here
  1234. file.write("\n");
  1235. file.write_format(PrintStatistics::TotalFilamentUsedGValueMask.c_str(), print.m_print_statistics.total_weight);
  1236. file.write_format(PrintStatistics::TotalFilamentCostValueMask.c_str(), print.m_print_statistics.total_cost);
  1237. file.write_format(PrintStatistics::TotalFilamentUsedWipeTowerValueMask.c_str(), print.m_print_statistics.total_wipe_tower_filament_weight);
  1238. if (print.m_print_statistics.total_toolchanges > 0)
  1239. file.write_format("; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
  1240. file.write_format(";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
  1241. // if exporting gcode in ascii format, config export is done here
  1242. // Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
  1243. // The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
  1244. {
  1245. file.write("\n; prusaslicer_config = begin\n");
  1246. std::string full_config;
  1247. append_full_config(print, full_config);
  1248. if (!full_config.empty())
  1249. file.write(full_config);
  1250. file.write("; prusaslicer_config = end\n");
  1251. }
  1252. }
  1253. print.throw_if_canceled();
  1254. }
  1255. // Fill in cache of smooth paths for perimeters, fills and supports of the given object layers.
  1256. // Based on params, the paths are either decimated to sparser polylines, or interpolated with circular arches.
  1257. void GCodeGenerator::smooth_path_interpolate(
  1258. const ObjectLayerToPrint &object_layer_to_print,
  1259. const GCode::SmoothPathCache::InterpolationParameters &params,
  1260. GCode::SmoothPathCache &out)
  1261. {
  1262. if (const Layer *layer = object_layer_to_print.object_layer; layer) {
  1263. for (const LayerRegion *layerm : layer->regions()) {
  1264. out.interpolate_add(layerm->perimeters(), params);
  1265. out.interpolate_add(layerm->fills(), params);
  1266. }
  1267. }
  1268. if (const SupportLayer *layer = object_layer_to_print.support_layer; layer)
  1269. out.interpolate_add(layer->support_fills, params);
  1270. }
  1271. // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
  1272. // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
  1273. // and export G-code into file.
  1274. void GCodeGenerator::process_layers(
  1275. const Print &print,
  1276. const ToolOrdering &tool_ordering,
  1277. const std::vector<const PrintInstance*> &print_object_instances_ordering,
  1278. const std::vector<std::pair<coordf_t, ObjectsLayerToPrint>> &layers_to_print,
  1279. const GCode::SmoothPathCache &smooth_path_cache_global,
  1280. GCodeOutputStream &output_stream)
  1281. {
  1282. size_t layer_to_print_idx = 0;
  1283. const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config());
  1284. const auto smooth_path_interpolator = tbb::make_filter<void, std::pair<size_t, GCode::SmoothPathCache>>(slic3r_tbb_filtermode::serial_in_order,
  1285. [this, &print, &layers_to_print, &layer_to_print_idx, &interpolation_params](tbb::flow_control &fc) -> std::pair<size_t, GCode::SmoothPathCache> {
  1286. if (layer_to_print_idx >= layers_to_print.size()) {
  1287. if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) {
  1288. fc.stop();
  1289. return {};
  1290. } else {
  1291. // Pressure equalizer need insert empty input. Because it returns one layer back.
  1292. // Insert NOP (no operation) layer;
  1293. return { layer_to_print_idx ++, {} };
  1294. }
  1295. } else {
  1296. print.throw_if_canceled();
  1297. size_t idx = layer_to_print_idx ++;
  1298. GCode::SmoothPathCache smooth_path_cache;
  1299. for (const ObjectLayerToPrint &l : layers_to_print[idx].second)
  1300. GCodeGenerator::smooth_path_interpolate(l, interpolation_params, smooth_path_cache);
  1301. return { idx, std::move(smooth_path_cache) };
  1302. }
  1303. });
  1304. const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1305. [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &smooth_path_cache_global](
  1306. std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult {
  1307. size_t layer_to_print_idx = in.first;
  1308. if (layer_to_print_idx == layers_to_print.size()) {
  1309. // Pressure equalizer need insert empty input. Because it returns one layer back.
  1310. // Insert NOP (no operation) layer;
  1311. return LayerResult::make_nop_layer_result();
  1312. } else {
  1313. const std::pair<coordf_t, ObjectsLayerToPrint> &layer = layers_to_print[layer_to_print_idx];
  1314. const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
  1315. if (m_wipe_tower && layer_tools.has_wipe_tower)
  1316. m_wipe_tower->next_layer();
  1317. print.throw_if_canceled();
  1318. return this->process_layer(print, layer.second, layer_tools,
  1319. GCode::SmoothPathCaches{ smooth_path_cache_global, in.second },
  1320. &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1));
  1321. }
  1322. });
  1323. // The pipeline is variable: The vase mode filter is optional.
  1324. const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1325. [spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult {
  1326. if (in.nop_layer_result)
  1327. return in;
  1328. spiral_vase->enable(in.spiral_vase_enable);
  1329. return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
  1330. });
  1331. const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1332. [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
  1333. return pressure_equalizer->process_layer(std::move(in));
  1334. });
  1335. const auto cooling = tbb::make_filter<LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
  1336. [cooling_buffer = this->m_cooling_buffer.get()](LayerResult in) -> std::string {
  1337. if (in.nop_layer_result)
  1338. return in.gcode;
  1339. return cooling_buffer->process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
  1340. });
  1341. const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
  1342. [find_replace = this->m_find_replace.get()](std::string s) -> std::string {
  1343. return find_replace->process_layer(std::move(s));
  1344. });
  1345. const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
  1346. [&output_stream](std::string s) { output_stream.write(s); }
  1347. );
  1348. tbb::filter<void, LayerResult> pipeline_to_layerresult = smooth_path_interpolator & generator;
  1349. if (m_spiral_vase)
  1350. pipeline_to_layerresult = pipeline_to_layerresult & spiral_vase;
  1351. if (m_pressure_equalizer)
  1352. pipeline_to_layerresult = pipeline_to_layerresult & pressure_equalizer;
  1353. tbb::filter<LayerResult, std::string> pipeline_to_string = cooling;
  1354. if (m_find_replace)
  1355. pipeline_to_string = pipeline_to_string & find_replace;
  1356. // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline.
  1357. // Handler is unregistered when the destructor is called.
  1358. TBBLocalesSetter locales_setter;
  1359. // The pipeline elements are joined using const references, thus no copying is performed.
  1360. output_stream.find_replace_supress();
  1361. tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output);
  1362. output_stream.find_replace_enable();
  1363. }
  1364. // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
  1365. // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
  1366. // and export G-code into file.
  1367. void GCodeGenerator::process_layers(
  1368. const Print &print,
  1369. const ToolOrdering &tool_ordering,
  1370. ObjectsLayerToPrint layers_to_print,
  1371. const size_t single_object_idx,
  1372. const GCode::SmoothPathCache &smooth_path_cache_global,
  1373. GCodeOutputStream &output_stream)
  1374. {
  1375. size_t layer_to_print_idx = 0;
  1376. const GCode::SmoothPathCache::InterpolationParameters interpolation_params = interpolation_parameters(print.config());
  1377. const auto smooth_path_interpolator = tbb::make_filter<void, std::pair<size_t, GCode::SmoothPathCache>> (slic3r_tbb_filtermode::serial_in_order,
  1378. [this, &print, &layers_to_print, &layer_to_print_idx, interpolation_params](tbb::flow_control &fc) -> std::pair<size_t, GCode::SmoothPathCache> {
  1379. if (layer_to_print_idx >= layers_to_print.size()) {
  1380. if (layer_to_print_idx == layers_to_print.size() + (m_pressure_equalizer ? 1 : 0)) {
  1381. fc.stop();
  1382. return {};
  1383. } else {
  1384. // Pressure equalizer need insert empty input. Because it returns one layer back.
  1385. // Insert NOP (no operation) layer;
  1386. return { layer_to_print_idx ++, {} };
  1387. }
  1388. } else {
  1389. print.throw_if_canceled();
  1390. size_t idx = layer_to_print_idx ++;
  1391. GCode::SmoothPathCache smooth_path_cache;
  1392. GCodeGenerator::smooth_path_interpolate(layers_to_print[idx], interpolation_params, smooth_path_cache);
  1393. return { idx, std::move(smooth_path_cache) };
  1394. }
  1395. });
  1396. const auto generator = tbb::make_filter<std::pair<size_t, GCode::SmoothPathCache>, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1397. [this, &print, &tool_ordering, &layers_to_print, &smooth_path_cache_global, single_object_idx](std::pair<size_t, GCode::SmoothPathCache> in) -> LayerResult {
  1398. size_t layer_to_print_idx = in.first;
  1399. if (layer_to_print_idx == layers_to_print.size()) {
  1400. // Pressure equalizer need insert empty input. Because it returns one layer back.
  1401. // Insert NOP (no operation) layer;
  1402. return LayerResult::make_nop_layer_result();
  1403. } else {
  1404. ObjectLayerToPrint &layer = layers_to_print[layer_to_print_idx];
  1405. print.throw_if_canceled();
  1406. return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()),
  1407. GCode::SmoothPathCaches{ smooth_path_cache_global, in.second },
  1408. &layer == &layers_to_print.back(), nullptr, single_object_idx);
  1409. }
  1410. });
  1411. // The pipeline is variable: The vase mode filter is optional.
  1412. const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1413. [spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult {
  1414. if (in.nop_layer_result)
  1415. return in;
  1416. spiral_vase->enable(in.spiral_vase_enable);
  1417. return { spiral_vase->process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
  1418. });
  1419. const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
  1420. [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
  1421. return pressure_equalizer->process_layer(std::move(in));
  1422. });
  1423. const auto cooling = tbb::make_filter<LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
  1424. [cooling_buffer = this->m_cooling_buffer.get()](LayerResult in)->std::string {
  1425. if (in.nop_layer_result)
  1426. return in.gcode;
  1427. return cooling_buffer->process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
  1428. });
  1429. const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
  1430. [find_replace = this->m_find_replace.get()](std::string s) -> std::string {
  1431. return find_replace->process_layer(std::move(s));
  1432. });
  1433. const auto output = tbb::make_filter<std::string, void>(slic3r_tbb_filtermode::serial_in_order,
  1434. [&output_stream](std::string s) { output_stream.write(s); }
  1435. );
  1436. tbb::filter<void, LayerResult> pipeline_to_layerresult = smooth_path_interpolator & generator;
  1437. if (m_spiral_vase)
  1438. pipeline_to_layerresult = pipeline_to_layerresult & spiral_vase;
  1439. if (m_pressure_equalizer)
  1440. pipeline_to_layerresult = pipeline_to_layerresult & pressure_equalizer;
  1441. tbb::filter<LayerResult, std::string> pipeline_to_string = cooling;
  1442. if (m_find_replace)
  1443. pipeline_to_string = pipeline_to_string & find_replace;
  1444. // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline.
  1445. // Handler is unregistered when the destructor is called.
  1446. TBBLocalesSetter locales_setter;
  1447. // The pipeline elements are joined using const references, thus no copying is performed.
  1448. output_stream.find_replace_supress();
  1449. tbb::parallel_pipeline(12, pipeline_to_layerresult & pipeline_to_string & output);
  1450. output_stream.find_replace_enable();
  1451. }
  1452. std::string GCodeGenerator::placeholder_parser_process(
  1453. const std::string &name,
  1454. const std::string &templ,
  1455. unsigned int current_extruder_id,
  1456. const DynamicConfig *config_override)
  1457. {
  1458. #ifndef NDEBUG // CHECK_CUSTOM_GCODE_PLACEHOLDERS
  1459. if (config_override) {
  1460. const auto& custom_gcode_placeholders = custom_gcode_specific_placeholders();
  1461. // 1-st check: custom G-code "name" have to be present in s_CustomGcodeSpecificOptions;
  1462. //if (custom_gcode_placeholders.count(name) > 0) {
  1463. // const auto& placeholders = custom_gcode_placeholders.at(name);
  1464. if (auto it = custom_gcode_placeholders.find(name); it != custom_gcode_placeholders.end()) {
  1465. const auto& placeholders = it->second;
  1466. for (const std::string& key : config_override->keys()) {
  1467. // 2-nd check: "key" have to be present in s_CustomGcodeSpecificOptions for "name" custom G-code ;
  1468. if (std::find(placeholders.begin(), placeholders.end(), key) == placeholders.end())
  1469. throw Slic3r::PlaceholderParserError(format("\"%s\" placeholder for \"%s\" custom G-code \n"
  1470. "needs to be added to s_CustomGcodeSpecificOptions", key.c_str(), name.c_str()));
  1471. // 3-rd check: "key" have to be present in CustomGcodeSpecificConfigDef for "key" placeholder;
  1472. if (!custom_gcode_specific_config_def.has(key))
  1473. throw Slic3r::PlaceholderParserError(format("Definition of \"%s\" placeholder \n"
  1474. "needs to be added to CustomGcodeSpecificConfigDef", key.c_str()));
  1475. }
  1476. }
  1477. else
  1478. throw Slic3r::PlaceholderParserError(format("\"%s\" custom G-code needs to be added to s_CustomGcodeSpecificOptions", name.c_str()));
  1479. }
  1480. #endif
  1481. PlaceholderParserIntegration &ppi = m_placeholder_parser_integration;
  1482. try {
  1483. ppi.update_from_gcodewriter(m_writer);
  1484. std::string output = ppi.parser.process(templ, current_extruder_id, config_override, &ppi.output_config, &ppi.context);
  1485. ppi.validate_output_vector_variables();
  1486. if (const std::vector<double> &pos = ppi.opt_position->values; ppi.position != pos) {
  1487. // Update G-code writer.
  1488. m_writer.update_position({ pos[0], pos[1], pos[2] });
  1489. this->last_position = this->gcode_to_point({ pos[0], pos[1] });
  1490. }
  1491. for (const Extruder &e : m_writer.extruders()) {
  1492. unsigned int eid = e.id();
  1493. assert(eid < ppi.num_extruders);
  1494. if ( eid < ppi.num_extruders) {
  1495. if (! m_writer.config.use_relative_e_distances && ! is_approx(ppi.e_position[eid], ppi.opt_e_position->values[eid]))
  1496. const_cast<Extruder&>(e).set_position(ppi.opt_e_position->values[eid]);
  1497. if (! is_approx(ppi.e_retracted[eid], ppi.opt_e_retracted->values[eid]) ||
  1498. ! is_approx(ppi.e_restart_extra[eid], ppi.opt_e_restart_extra->values[eid]))
  1499. const_cast<Extruder&>(e).set_retracted(ppi.opt_e_retracted->values[eid], ppi.opt_e_restart_extra->values[eid]);
  1500. }
  1501. }
  1502. return output;
  1503. }
  1504. catch (std::runtime_error &err)
  1505. {
  1506. // Collect the names of failed template substitutions for error reporting.
  1507. auto it = ppi.failed_templates.find(name);
  1508. if (it == ppi.failed_templates.end())
  1509. // Only if there was no error reported for this template, store the first error message into the map to be reported.
  1510. // We don't want to collect error message for each and every occurence of a single custom G-code section.
  1511. ppi.failed_templates.insert(it, std::make_pair(name, std::string(err.what())));
  1512. // Insert the macro error message into the G-code.
  1513. return
  1514. std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" +
  1515. err.what() +
  1516. "!!!!! End of an error report for the custom G-code template " + name + "\n\n";
  1517. }
  1518. }
  1519. // Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait or optionally G10 with temperature inside the custom G-code.
  1520. // Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out.
  1521. static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, const bool include_g10, int &temp_out)
  1522. {
  1523. temp_out = -1;
  1524. if (gcode.empty())
  1525. return false;
  1526. const char *ptr = gcode.data();
  1527. bool temp_set_by_gcode = false;
  1528. while (*ptr != 0) {
  1529. // Skip whitespaces.
  1530. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
  1531. if (*ptr == 'M' || // Line starts with 'M'. It is a machine command.
  1532. (*ptr == 'G' && include_g10)) { // Only check for G10 if requested
  1533. bool is_gcode = *ptr == 'G';
  1534. ++ ptr;
  1535. // Parse the M or G code value.
  1536. char *endptr = nullptr;
  1537. int mgcode = int(strtol(ptr, &endptr, 10));
  1538. if (endptr != nullptr && endptr != ptr &&
  1539. is_gcode ?
  1540. // G10 found
  1541. mgcode == 10 :
  1542. // M104/M109 or M140/M190 found.
  1543. (mgcode == mcode_set_temp_dont_wait || mgcode == mcode_set_temp_and_wait)) {
  1544. ptr = endptr;
  1545. if (! is_gcode)
  1546. // Let the caller know that the custom M-code sets the temperature.
  1547. temp_set_by_gcode = true;
  1548. // Now try to parse the temperature value.
  1549. // While not at the end of the line:
  1550. while (strchr(";\r\n\0", *ptr) == nullptr) {
  1551. // Skip whitespaces.
  1552. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr);
  1553. if (*ptr == 'S') {
  1554. // Skip whitespaces.
  1555. for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr);
  1556. // Parse an int.
  1557. endptr = nullptr;
  1558. long temp_parsed = strtol(ptr, &endptr, 10);
  1559. if (endptr > ptr) {
  1560. ptr = endptr;
  1561. temp_out = temp_parsed;
  1562. // Let the caller know that the custom G-code sets the temperature
  1563. // Only do this after successfully parsing temperature since G10
  1564. // can be used for other reasons
  1565. temp_set_by_gcode = true;
  1566. }
  1567. } else {
  1568. // Skip this word.
  1569. for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr);
  1570. }
  1571. }
  1572. }
  1573. }
  1574. // Skip the rest of the line.
  1575. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr);
  1576. // Skip the end of line indicators.
  1577. for (; *ptr == '\r' || *ptr == '\n'; ++ ptr);
  1578. }
  1579. return temp_set_by_gcode;
  1580. }
  1581. // Print the machine envelope G-code for the Marlin firmware based on the "machine_max_xxx" parameters.
  1582. // Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
  1583. void GCodeGenerator::print_machine_envelope(GCodeOutputStream &file, Print &print)
  1584. {
  1585. const GCodeFlavor flavor = print.config().gcode_flavor.value;
  1586. if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware)
  1587. && print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) {
  1588. int factor = flavor == gcfRepRapFirmware ? 60 : 1; // RRF M203 and M566 are in mm/min
  1589. file.write_format("M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
  1590. int(print.config().machine_max_acceleration_x.values.front() + 0.5),
  1591. int(print.config().machine_max_acceleration_y.values.front() + 0.5),
  1592. int(print.config().machine_max_acceleration_z.values.front() + 0.5),
  1593. int(print.config().machine_max_acceleration_e.values.front() + 0.5));
  1594. file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, %s\n",
  1595. int(print.config().machine_max_feedrate_x.values.front() * factor + 0.5),
  1596. int(print.config().machine_max_feedrate_y.values.front() * factor + 0.5),
  1597. int(print.config().machine_max_feedrate_z.values.front() * factor + 0.5),
  1598. int(print.config().machine_max_feedrate_e.values.front() * factor + 0.5),
  1599. factor == 60 ? "mm / min" : "mm / sec");
  1600. // Now M204 - acceleration. This one is quite hairy...
  1601. if (flavor == gcfRepRapFirmware)
  1602. // Uses M204 P[print] T[travel]
  1603. file.write_format("M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n",
  1604. int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
  1605. int(print.config().machine_max_acceleration_travel.values.front() + 0.5));
  1606. else if (flavor == gcfMarlinLegacy)
  1607. // Legacy Marlin uses M204 S[print] T[retract]
  1608. file.write_format("M204 S%d T%d ; sets acceleration (S) and retract acceleration (R), mm/sec^2\n",
  1609. int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
  1610. int(print.config().machine_max_acceleration_retracting.values.front() + 0.5));
  1611. else if (flavor == gcfMarlinFirmware)
  1612. // New Marlin uses M204 P[print] R[retract] T[travel]
  1613. file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
  1614. int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
  1615. int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
  1616. int(print.config().machine_max_acceleration_travel.values.front() + 0.5));
  1617. else
  1618. assert(false);
  1619. assert(is_decimal_separator_point());
  1620. file.write_format(flavor == gcfRepRapFirmware
  1621. ? "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n"
  1622. : "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
  1623. print.config().machine_max_jerk_x.values.front() * factor,
  1624. print.config().machine_max_jerk_y.values.front() * factor,
  1625. print.config().machine_max_jerk_z.values.front() * factor,
  1626. print.config().machine_max_jerk_e.values.front() * factor);
  1627. if (flavor != gcfRepRapFirmware)
  1628. file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n",
  1629. int(print.config().machine_min_extruding_rate.values.front() + 0.5),
  1630. int(print.config().machine_min_travel_rate.values.front() + 0.5));
  1631. else {
  1632. // M205 Sn Tn not supported in RRF. They use M203 Inn to set minimum feedrate for
  1633. // all moves. This is currently not implemented.
  1634. }
  1635. }
  1636. }
  1637. // Write 1st layer bed temperatures into the G-code.
  1638. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
  1639. // M140 - Set Extruder Temperature
  1640. // M190 - Set Extruder Temperature and Wait
  1641. void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
  1642. {
  1643. bool autoemit = print.config().autoemit_temperature_commands;
  1644. // Initial bed temperature based on the first extruder.
  1645. int temp = print.config().first_layer_bed_temperature.get_at(first_printing_extruder_id);
  1646. // Is the bed temperature set by the provided custom G-code?
  1647. int temp_by_gcode = -1;
  1648. bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, false, temp_by_gcode);
  1649. if (autoemit && temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000)
  1650. temp = temp_by_gcode;
  1651. // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if
  1652. // the custom start G-code emited these.
  1653. std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
  1654. if (autoemit && ! temp_set_by_gcode)
  1655. file.write(set_temp_gcode);
  1656. }
  1657. // Write 1st layer extruder temperatures into the G-code.
  1658. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature.
  1659. // M104 - Set Extruder Temperature
  1660. // M109 - Set Extruder Temperature and Wait
  1661. // RepRapFirmware: G10 Sxx
  1662. void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream &file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait)
  1663. {
  1664. bool autoemit = print.config().autoemit_temperature_commands;
  1665. // Is the bed temperature set by the provided custom G-code?
  1666. int temp_by_gcode = -1;
  1667. bool include_g10 = print.config().gcode_flavor == gcfRepRapFirmware;
  1668. if (! autoemit || custom_gcode_sets_temperature(gcode, 104, 109, include_g10, temp_by_gcode)) {
  1669. // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code.
  1670. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
  1671. if (autoemit && temp_by_gcode >= 0 && temp_by_gcode < 1000)
  1672. temp = temp_by_gcode;
  1673. m_writer.set_temperature(temp, wait, first_printing_extruder_id);
  1674. } else {
  1675. // Custom G-code does not set the extruder temperature. Do it now.
  1676. if (print.config().single_extruder_multi_material.value) {
  1677. // Set temperature of the first printing extruder only.
  1678. int temp = print.config().first_layer_temperature.get_at(first_printing_extruder_id);
  1679. if (temp > 0)
  1680. file.write(m_writer.set_temperature(temp, wait, first_printing_extruder_id));
  1681. } else {
  1682. // Set temperatures of all the printing extruders.
  1683. for (unsigned int tool_id : print.extruders()) {
  1684. int temp = print.config().first_layer_temperature.get_at(tool_id);
  1685. if (print.config().ooze_prevention.value && tool_id != first_printing_extruder_id) {
  1686. if (print.config().idle_temperature.is_nil(tool_id))
  1687. temp += print.config().standby_temperature_delta.value;
  1688. else
  1689. temp = print.config().idle_temperature.get_at(tool_id);
  1690. }
  1691. if (temp > 0)
  1692. file.write(m_writer.set_temperature(temp, wait, tool_id));
  1693. }
  1694. }
  1695. }
  1696. }
  1697. std::vector<GCodeGenerator::InstanceToPrint> GCodeGenerator::sort_print_object_instances(
  1698. const std::vector<ObjectLayerToPrint> &object_layers,
  1699. // Ordering must be defined for normal (non-sequential print).
  1700. const std::vector<const PrintInstance*> *ordering,
  1701. // For sequential print, the instance of the object to be printing has to be defined.
  1702. const size_t single_object_instance_idx)
  1703. {
  1704. std::vector<InstanceToPrint> out;
  1705. if (ordering == nullptr) {
  1706. // Sequential print, single object is being printed.
  1707. assert(object_layers.size() == 1);
  1708. out.emplace_back(0, *object_layers.front().object(), single_object_instance_idx);
  1709. } else {
  1710. // Create mapping from PrintObject* to ObjectLayerToPrint ID.
  1711. std::vector<std::pair<const PrintObject*, size_t>> sorted;
  1712. sorted.reserve(object_layers.size());
  1713. for (const ObjectLayerToPrint &object : object_layers)
  1714. if (const PrintObject* print_object = object.object(); print_object)
  1715. sorted.emplace_back(print_object, &object - object_layers.data());
  1716. std::sort(sorted.begin(), sorted.end());
  1717. if (! sorted.empty()) {
  1718. out.reserve(sorted.size());
  1719. for (const PrintInstance *instance : *ordering) {
  1720. const PrintObject &print_object = *instance->print_object;
  1721. std::pair<const PrintObject*, size_t> key(&print_object, 0);
  1722. auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
  1723. if (it != sorted.end() && it->first == &print_object)
  1724. // ObjectLayerToPrint for this PrintObject was found.
  1725. out.emplace_back(it->second, print_object, instance - print_object.instances().data());
  1726. }
  1727. }
  1728. }
  1729. return out;
  1730. }
  1731. namespace ProcessLayer
  1732. {
  1733. static std::string emit_custom_gcode_per_print_z(
  1734. GCodeGenerator &gcodegen,
  1735. const CustomGCode::Item *custom_gcode,
  1736. unsigned int current_extruder_id,
  1737. // ID of the first extruder printing this layer.
  1738. unsigned int first_extruder_id,
  1739. const PrintConfig &config)
  1740. {
  1741. std::string gcode;
  1742. bool single_extruder_printer = config.nozzle_diameter.size() == 1;
  1743. if (custom_gcode != nullptr) {
  1744. // Extruder switches are processed by LayerTools, they should be filtered out.
  1745. assert(custom_gcode->type != CustomGCode::ToolChange);
  1746. CustomGCode::Type gcode_type = custom_gcode->type;
  1747. bool color_change = gcode_type == CustomGCode::ColorChange;
  1748. bool tool_change = gcode_type == CustomGCode::ToolChange;
  1749. // Tool Change is applied as Color Change for a single extruder printer only.
  1750. assert(! tool_change || single_extruder_printer);
  1751. std::string pause_print_msg;
  1752. int m600_extruder_before_layer = -1;
  1753. if (color_change && custom_gcode->extruder > 0)
  1754. m600_extruder_before_layer = custom_gcode->extruder - 1;
  1755. else if (gcode_type == CustomGCode::PausePrint)
  1756. pause_print_msg = custom_gcode->extra;
  1757. // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count
  1758. if (color_change || tool_change)
  1759. {
  1760. assert(m600_extruder_before_layer >= 0);
  1761. // Color Change or Tool Change as Color Change.
  1762. // add tag for processor
  1763. gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Color_Change) + ",T" + std::to_string(m600_extruder_before_layer) + "," + custom_gcode->color + "\n";
  1764. if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer
  1765. // && !MMU1
  1766. ) {
  1767. //! FIXME_in_fw show message during print pause
  1768. // FIXME: Why is pause_print_gcode here? Why is it supplied "color_change_extruder"? Why is that not
  1769. // passed to color_change_gcode below?
  1770. DynamicConfig cfg;
  1771. cfg.set_key_value("color_change_extruder", new ConfigOptionInt(m600_extruder_before_layer));
  1772. gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id, &cfg);
  1773. gcode += "\n";
  1774. gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n";
  1775. }
  1776. else {
  1777. gcode += gcodegen.placeholder_parser_process("color_change_gcode", config.color_change_gcode, current_extruder_id);
  1778. gcode += "\n";
  1779. //FIXME Tell G-code writer that M600 filled the extruder, thus the G-code writer shall reset the extruder to unretracted state after
  1780. // return from M600. Thus the G-code generated by the following line is ignored.
  1781. // see GH issue #6362
  1782. gcodegen.writer().unretract();
  1783. }
  1784. }
  1785. else {
  1786. if (gcode_type == CustomGCode::PausePrint) // Pause print
  1787. {
  1788. // add tag for processor
  1789. gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Pause_Print) + "\n";
  1790. //! FIXME_in_fw show message during print pause
  1791. if (!pause_print_msg.empty())
  1792. gcode += "M117 " + pause_print_msg + "\n";
  1793. gcode += gcodegen.placeholder_parser_process("pause_print_gcode", config.pause_print_gcode, current_extruder_id);
  1794. }
  1795. else {
  1796. // add tag for processor
  1797. gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Custom_Code) + "\n";
  1798. if (gcode_type == CustomGCode::Template) // Template Custom Gcode
  1799. gcode += gcodegen.placeholder_parser_process("template_custom_gcode", config.template_custom_gcode, current_extruder_id);
  1800. else // custom Gcode
  1801. gcode += custom_gcode->extra;
  1802. }
  1803. gcode += "\n";
  1804. }
  1805. }
  1806. return gcode;
  1807. }
  1808. } // namespace ProcessLayer
  1809. namespace Skirt {
  1810. static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map<unsigned int, std::pair<size_t, size_t>> &skirt_loops_per_extruder_out)
  1811. {
  1812. // Prime all extruders printing over the 1st layer over the skirt lines.
  1813. size_t n_loops = print.skirt().entities.size();
  1814. size_t n_tools = layer_tools.extruders.size();
  1815. size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools;
  1816. for (size_t i = 0; i < n_loops; i += lines_per_extruder)
  1817. skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair<size_t, size_t>(i, std::min(i + lines_per_extruder, n_loops));
  1818. }
  1819. static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_1st_layer(
  1820. const Print &print,
  1821. const LayerTools &layer_tools,
  1822. // Heights (print_z) at which the skirt has already been extruded.
  1823. std::vector<coordf_t> &skirt_done)
  1824. {
  1825. // Extrude skirt at the print_z of the raft layers and normal object layers
  1826. // not at the print_z of the interlaced support material layers.
  1827. std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
  1828. //For sequential print, the following test may fail when extruding the 2nd and other objects.
  1829. // assert(skirt_done.empty());
  1830. if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) {
  1831. skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
  1832. skirt_done.emplace_back(layer_tools.print_z);
  1833. }
  1834. return skirt_loops_per_extruder_out;
  1835. }
  1836. static std::map<unsigned int, std::pair<size_t, size_t>> make_skirt_loops_per_extruder_other_layers(
  1837. const Print &print,
  1838. const LayerTools &layer_tools,
  1839. // Heights (print_z) at which the skirt has already been extruded.
  1840. std::vector<coordf_t> &skirt_done)
  1841. {
  1842. // Extrude skirt at the print_z of the raft layers and normal object layers
  1843. // not at the print_z of the interlaced support material layers.
  1844. std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
  1845. if (print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt &&
  1846. // Not enough skirt layers printed yet.
  1847. //FIXME infinite or high skirt does not make sense for sequential print!
  1848. (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt())) {
  1849. bool valid = ! skirt_done.empty() && skirt_done.back() < layer_tools.print_z - EPSILON;
  1850. assert(valid);
  1851. // This print_z has not been extruded yet (sequential print)
  1852. // FIXME: The skirt_done should not be empty at this point. The check is a workaround
  1853. // of https://github.com/prusa3d/PrusaSlicer/issues/5652, but it deserves a real fix.
  1854. if (valid) {
  1855. #if 0
  1856. // Prime just the first printing extruder. This is original Slic3r's implementation.
  1857. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair<size_t, size_t>(0, print.config().skirts.value);
  1858. #else
  1859. // Prime all extruders planned for this layer, see
  1860. // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619
  1861. skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
  1862. #endif
  1863. assert(!skirt_done.empty());
  1864. skirt_done.emplace_back(layer_tools.print_z);
  1865. }
  1866. }
  1867. return skirt_loops_per_extruder_out;
  1868. }
  1869. } // namespace Skirt
  1870. bool GCodeGenerator::line_distancer_is_required(const std::vector<unsigned int>& extruder_ids) {
  1871. for (const unsigned id : extruder_ids) {
  1872. const double travel_slope{this->m_config.travel_slope.get_at(id)};
  1873. if (
  1874. this->m_config.travel_lift_before_obstacle.get_at(id)
  1875. && this->m_config.travel_max_lift.get_at(id) > 0
  1876. && travel_slope > 0
  1877. && travel_slope < 90
  1878. ) {
  1879. return true;
  1880. }
  1881. }
  1882. return false;
  1883. }
  1884. std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) {
  1885. const Polyline xy_path{
  1886. this->gcode_to_point(from.head<2>()),
  1887. this->gcode_to_point(to.head<2>())
  1888. };
  1889. using namespace GCode::Impl::Travels;
  1890. ElevatedTravelParams elevation_params{
  1891. get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)};
  1892. const double initial_elevation = from.z();
  1893. const double z_change = to.z() - from.z();
  1894. elevation_params.lift_height = std::max(z_change, elevation_params.lift_height);
  1895. const double path_length = unscaled(xy_path.length());
  1896. const double lift_at_travel_end =
  1897. (elevation_params.lift_height / elevation_params.slope_end * path_length);
  1898. if (lift_at_travel_end < z_change) {
  1899. elevation_params.lift_height = z_change;
  1900. elevation_params.slope_end = path_length;
  1901. }
  1902. const std::vector<double> ensure_points_at_distances = linspace(
  1903. elevation_params.slope_end - elevation_params.blend_width / 2.0,
  1904. elevation_params.slope_end + elevation_params.blend_width / 2.0,
  1905. elevation_params.parabola_points_count
  1906. );
  1907. Points3 travel{generate_elevated_travel(
  1908. xy_path.points, ensure_points_at_distances, initial_elevation,
  1909. ElevatedTravelFormula{elevation_params}
  1910. )};
  1911. std::string travel_gcode;
  1912. Vec3d previous_point{this->point_to_gcode(travel.front())};
  1913. for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
  1914. const Vec3d gcode_point{this->point_to_gcode(point)};
  1915. travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change");
  1916. previous_point = gcode_point;
  1917. }
  1918. return travel_gcode;
  1919. }
  1920. // In sequential mode, process_layer is called once per each object and its copy,
  1921. // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
  1922. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
  1923. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
  1924. // and performing the extruder specific extrusions together.
  1925. LayerResult GCodeGenerator::process_layer(
  1926. const Print &print,
  1927. // Set of object & print layers of the same PrintObject and with the same print_z.
  1928. const ObjectsLayerToPrint &layers,
  1929. const LayerTools &layer_tools,
  1930. const GCode::SmoothPathCaches &smooth_path_caches,
  1931. const bool last_layer,
  1932. // Pairs of PrintObject index and its instance index.
  1933. const std::vector<const PrintInstance*> *ordering,
  1934. // If set to size_t(-1), then print all copies of all objects.
  1935. // Otherwise print a single copy of a single object.
  1936. const size_t single_object_instance_idx)
  1937. {
  1938. assert(! layers.empty());
  1939. // Either printing all copies of all objects, or just a single copy of a single object.
  1940. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
  1941. // First object, support and raft layer, if available.
  1942. const Layer *object_layer = nullptr;
  1943. const SupportLayer *support_layer = nullptr;
  1944. const SupportLayer *raft_layer = nullptr;
  1945. for (const ObjectLayerToPrint &l : layers) {
  1946. if (l.object_layer && ! object_layer)
  1947. object_layer = l.object_layer;
  1948. if (l.support_layer) {
  1949. if (! support_layer)
  1950. support_layer = l.support_layer;
  1951. if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers())
  1952. raft_layer = support_layer;
  1953. }
  1954. }
  1955. const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
  1956. LayerResult result { {}, layer.id(), false, last_layer, false};
  1957. if (layer_tools.extruders.empty())
  1958. // Nothing to extrude.
  1959. return result;
  1960. // Extract 1st object_layer and support_layer of this set of layers with an equal print_z.
  1961. coordf_t print_z = layer.print_z;
  1962. bool first_layer = layer.id() == 0;
  1963. unsigned int first_extruder_id = layer_tools.extruders.front();
  1964. // Initialize config with the 1st object to be printed at this layer.
  1965. m_config.apply(layer.object()->config(), true);
  1966. // Check whether it is possible to apply the spiral vase logic for this layer.
  1967. // Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
  1968. m_enable_loop_clipping = true;
  1969. if (m_spiral_vase && layers.size() == 1 && support_layer == nullptr) {
  1970. bool enable = (layer.id() > 0 || !print.has_brim()) && (layer.id() >= (size_t)print.config().skirt_height.value && ! print.has_infinite_skirt());
  1971. if (enable) {
  1972. for (const LayerRegion *layer_region : layer.regions())
  1973. if (size_t(layer_region->region().config().bottom_solid_layers.value) > layer.id() ||
  1974. layer_region->perimeters().items_count() > 1u ||
  1975. layer_region->fills().items_count() > 0) {
  1976. enable = false;
  1977. break;
  1978. }
  1979. }
  1980. result.spiral_vase_enable = enable;
  1981. // If we're going to apply spiralvase to this layer, disable loop clipping.
  1982. m_enable_loop_clipping = !enable;
  1983. }
  1984. std::string gcode;
  1985. assert(is_decimal_separator_point()); // for the sprintfs
  1986. // add tag for processor
  1987. gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change) + "\n";
  1988. // export layer z
  1989. gcode += std::string(";Z:") + float_to_string_decimal_point(print_z) + "\n";
  1990. // export layer height
  1991. float height = first_layer ? static_cast<float>(print_z) : static_cast<float>(print_z) - m_last_layer_z;
  1992. gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height)
  1993. + float_to_string_decimal_point(height) + "\n";
  1994. // update caches
  1995. const coordf_t previous_layer_z{m_last_layer_z};
  1996. m_last_layer_z = static_cast<float>(print_z);
  1997. m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z);
  1998. m_last_height = height;
  1999. m_current_layer_first_position = std::nullopt;
  2000. // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled.
  2001. if (!first_layer && ! print.config().before_layer_gcode.value.empty()) {
  2002. DynamicConfig config;
  2003. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1));
  2004. config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
  2005. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  2006. gcode += this->placeholder_parser_process("before_layer_gcode",
  2007. print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config)
  2008. + "\n";
  2009. }
  2010. gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index
  2011. m_layer = &layer;
  2012. if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr)
  2013. m_travel_obstacle_tracker.init_layer(layer, layers);
  2014. m_object_layer_over_raft = false;
  2015. if (!first_layer && ! print.config().layer_gcode.value.empty()) {
  2016. DynamicConfig config;
  2017. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  2018. config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
  2019. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  2020. gcode += this->placeholder_parser_process("layer_gcode",
  2021. print.config().layer_gcode.value, m_writer.extruder()->id(), &config)
  2022. + "\n";
  2023. }
  2024. if (! first_layer && ! m_second_layer_things_done) {
  2025. // Transition from 1st to 2nd layer. Adjust nozzle temperatures as prescribed by the nozzle dependent
  2026. // first_layer_temperature vs. temperature settings.
  2027. for (const Extruder &extruder : m_writer.extruders()) {
  2028. if (print.config().single_extruder_multi_material.value || m_ooze_prevention.enable) {
  2029. // In single extruder multi material mode, set the temperature for the current extruder only.
  2030. // The same applies when ooze prevention is enabled.
  2031. if (extruder.id() != m_writer.extruder()->id())
  2032. continue;
  2033. }
  2034. int temperature = print.config().temperature.get_at(extruder.id());
  2035. if (temperature > 0 && (temperature != print.config().first_layer_temperature.get_at(extruder.id())))
  2036. gcode += m_writer.set_temperature(temperature, false, extruder.id());
  2037. }
  2038. gcode += m_writer.set_bed_temperature(print.config().bed_temperature.get_at(first_extruder_id));
  2039. // Mark the temperature transition from 1st to 2nd layer to be finished.
  2040. m_second_layer_things_done = true;
  2041. }
  2042. // Map from extruder ID to <begin, end> index of skirt loops to be extruded with that extruder.
  2043. std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder;
  2044. if (single_object_instance_idx == size_t(-1)) {
  2045. // Normal (non-sequential) print.
  2046. gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config());
  2047. }
  2048. // Extrude skirt at the print_z of the raft layers and normal object layers
  2049. // not at the print_z of the interlaced support material layers.
  2050. skirt_loops_per_extruder = first_layer ?
  2051. Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) :
  2052. Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done);
  2053. if (this->config().avoid_crossing_curled_overhangs) {
  2054. m_avoid_crossing_curled_overhangs.clear();
  2055. for (const ObjectLayerToPrint &layer_to_print : layers) {
  2056. if (layer_to_print.object() == nullptr)
  2057. continue;
  2058. for (const auto &instance : layer_to_print.object()->instances()) {
  2059. m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.object_layer, instance.shift);
  2060. m_avoid_crossing_curled_overhangs.add_obstacles(layer_to_print.support_layer, instance.shift);
  2061. }
  2062. }
  2063. }
  2064. // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders.
  2065. for (unsigned int extruder_id : layer_tools.extruders)
  2066. {
  2067. gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ?
  2068. m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) :
  2069. this->set_extruder(extruder_id, print_z);
  2070. // let analyzer tag generator aware of a role type change
  2071. if (layer_tools.has_wipe_tower && m_wipe_tower)
  2072. m_last_processor_extrusion_role = GCodeExtrusionRole::WipeTower;
  2073. if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
  2074. const std::pair<size_t, size_t> loops = loops_it->second;
  2075. this->set_origin(0., 0.);
  2076. m_avoid_crossing_perimeters.use_external_mp();
  2077. Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])));
  2078. double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
  2079. for (size_t i = loops.first; i < loops.second; ++i) {
  2080. // Adjust flow according to this layer's layer height.
  2081. //FIXME using the support_material_speed of the 1st object printed.
  2082. gcode += this->extrude_skirt(dynamic_cast<const ExtrusionLoop&>(*print.skirt().entities[i]),
  2083. // Override of skirt extrusion parameters. extrude_skirt() will fill in the extrusion width.
  2084. ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() },
  2085. smooth_path_caches.global(), "skirt"sv, m_config.support_material_speed.value);
  2086. }
  2087. m_avoid_crossing_perimeters.use_external_mp(false);
  2088. // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
  2089. if (first_layer && loops.first == 0)
  2090. m_avoid_crossing_perimeters.disable_once();
  2091. }
  2092. // Extrude brim with the extruder of the 1st region.
  2093. if (! m_brim_done) {
  2094. this->set_origin(0., 0.);
  2095. m_avoid_crossing_perimeters.use_external_mp();
  2096. for (const ExtrusionEntity *ee : print.brim().entities)
  2097. gcode += this->extrude_entity({ *ee, false }, smooth_path_caches.global(), "brim"sv, m_config.support_material_speed.value);
  2098. m_brim_done = true;
  2099. m_avoid_crossing_perimeters.use_external_mp(false);
  2100. // Allow a straight travel move to the first object point.
  2101. m_avoid_crossing_perimeters.disable_once();
  2102. }
  2103. std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(layers, ordering, single_object_instance_idx);
  2104. // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
  2105. bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden();
  2106. if (is_anything_overridden) {
  2107. // Extrude wipes.
  2108. size_t gcode_size_old = gcode.size();
  2109. for (const InstanceToPrint &instance : instances_to_print)
  2110. this->process_layer_single_object(
  2111. gcode, extruder_id, instance,
  2112. layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(),
  2113. is_anything_overridden, true /* print_wipe_extrusions */);
  2114. if (gcode_size_old < gcode.size())
  2115. gcode+="; PURGING FINISHED\n";
  2116. }
  2117. // Extrude normal extrusions.
  2118. for (const InstanceToPrint &instance : instances_to_print)
  2119. this->process_layer_single_object(
  2120. gcode, extruder_id, instance,
  2121. layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(),
  2122. is_anything_overridden, false /* print_wipe_extrusions */);
  2123. }
  2124. // During layer change the starting position of next layer is now known.
  2125. // The solution is thus to emplace a temporary tag to the gcode, cache the postion and
  2126. // replace the tag later. The tag is Layer_Change_Travel, the cached position is
  2127. // m_current_layer_first_position and it is replaced here.
  2128. const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
  2129. std::string layer_change_gcode;
  2130. const bool do_ramping_layer_change = (
  2131. m_previous_layer_last_position
  2132. && m_current_layer_first_position
  2133. && m_layer_change_extruder_id
  2134. && !result.spiral_vase_enable
  2135. && print_z > previous_layer_z
  2136. && EXTRUDER_CONFIG(travel_ramping_lift)
  2137. && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90
  2138. );
  2139. if (first_layer) {
  2140. layer_change_gcode = ""; // Explicit for readability.
  2141. } else if (do_ramping_layer_change) {
  2142. layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id);
  2143. } else {
  2144. layer_change_gcode = this->writer().get_travel_to_z_gcode(m_config.z_offset.value + print_z, "simple layer change");
  2145. }
  2146. boost::algorithm::replace_first(gcode, tag, layer_change_gcode);
  2147. BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z <<
  2148. log_memory_info();
  2149. result.gcode = std::move(gcode);
  2150. result.cooling_buffer_flush = object_layer || raft_layer || last_layer;
  2151. return result;
  2152. }
  2153. static const auto comment_perimeter = "perimeter"sv;
  2154. // Comparing string_view pointer & length for speed.
  2155. static inline bool comment_is_perimeter(const std::string_view comment) {
  2156. return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size();
  2157. }
  2158. void GCodeGenerator::process_layer_single_object(
  2159. // output
  2160. std::string &gcode,
  2161. // Index of the extruder currently active.
  2162. const unsigned int extruder_id,
  2163. // What object and instance is going to be printed.
  2164. const InstanceToPrint &print_instance,
  2165. // and the object & support layer of the above.
  2166. const ObjectLayerToPrint &layer_to_print,
  2167. // Container for extruder overrides (when wiping into object or infill).
  2168. const LayerTools &layer_tools,
  2169. // Optional smooth path interpolating extrusion polylines.
  2170. const GCode::SmoothPathCache &smooth_path_cache,
  2171. // Is any extrusion possibly marked as wiping extrusion?
  2172. const bool is_anything_overridden,
  2173. // Round 1 (wiping into object or infill) or round 2 (normal extrusions).
  2174. const bool print_wipe_extrusions)
  2175. {
  2176. bool first = true;
  2177. // Delay layer initialization as many layers may not print with all extruders.
  2178. auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first, &gcode]() {
  2179. if (first) {
  2180. first = false;
  2181. const PrintObject &print_object = print_instance.print_object;
  2182. const Print &print = *print_object.print();
  2183. m_config.apply(print_object.config(), true);
  2184. m_layer = layer_to_print.layer();
  2185. if (print.config().avoid_crossing_perimeters)
  2186. m_avoid_crossing_perimeters.init_layer(*m_layer);
  2187. // When starting a new object, use the external motion planner for the first travel move.
  2188. const Point &offset = print_object.instances()[print_instance.instance_id].shift;
  2189. GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)};
  2190. if (m_current_instance != next_instance)
  2191. m_avoid_crossing_perimeters.use_external_mp_once();
  2192. m_current_instance = next_instance;
  2193. this->set_origin(unscale(offset));
  2194. gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No);
  2195. }
  2196. };
  2197. const PrintObject &print_object = print_instance.print_object;
  2198. const Print &print = *print_object.print();
  2199. if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr)
  2200. if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) {
  2201. ExtrusionRole role = support_layer.support_fills.role();
  2202. bool has_support = role.is_mixed() || role.is_support_base();
  2203. bool has_interface = role.is_mixed() || role.is_support_interface();
  2204. // Extruder ID of the support base. -1 if "don't care".
  2205. unsigned int support_extruder = print_object.config().support_material_extruder.value - 1;
  2206. // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
  2207. bool support_dontcare = support_extruder == std::numeric_limits<unsigned int>::max();
  2208. // Extruder ID of the support interface. -1 if "don't care".
  2209. unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1;
  2210. // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes?
  2211. bool interface_dontcare = interface_extruder == std::numeric_limits<unsigned int>::max();
  2212. if (support_dontcare || interface_dontcare) {
  2213. // Some support will be printed with "don't care" material, preferably non-soluble.
  2214. // Is the current extruder assigned a soluble filament?
  2215. auto it_nonsoluble = std::find_if(layer_tools.extruders.begin(), layer_tools.extruders.end(),
  2216. [&soluble = std::as_const(print.config().filament_soluble)](unsigned int extruder_id) { return ! soluble.get_at(extruder_id); });
  2217. // There should be a non-soluble extruder available.
  2218. assert(it_nonsoluble != layer_tools.extruders.end());
  2219. unsigned int dontcare_extruder = it_nonsoluble == layer_tools.extruders.end() ? layer_tools.extruders.front() : *it_nonsoluble;
  2220. if (support_dontcare)
  2221. support_extruder = dontcare_extruder;
  2222. if (interface_dontcare)
  2223. interface_extruder = dontcare_extruder;
  2224. }
  2225. bool extrude_support = has_support && support_extruder == extruder_id;
  2226. bool extrude_interface = has_interface && interface_extruder == extruder_id;
  2227. if (extrude_support || extrude_interface) {
  2228. init_layer_delayed();
  2229. m_layer = layer_to_print.support_layer;
  2230. m_object_layer_over_raft = false;
  2231. ExtrusionEntitiesPtr entities_cache;
  2232. const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? support_layer.support_fills.entities : entities_cache;
  2233. if (! extrude_support || ! extrude_interface) {
  2234. auto role = extrude_support ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface;
  2235. entities_cache.reserve(support_layer.support_fills.entities.size());
  2236. for (ExtrusionEntity *ee : support_layer.support_fills.entities)
  2237. if (ee->role() == role)
  2238. entities_cache.emplace_back(ee);
  2239. }
  2240. gcode += this->extrude_support(chain_extrusion_references(entities), smooth_path_cache);
  2241. }
  2242. }
  2243. m_layer = layer_to_print.layer();
  2244. // To control print speed of the 1st object layer printed over raft interface.
  2245. m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 &&
  2246. print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id();
  2247. // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions
  2248. // (wipe extrusions are printed before regular extrusions).
  2249. auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion &region) -> bool {
  2250. assert(eec != nullptr);
  2251. if (eec->entities.empty())
  2252. // This shouldn't happen. FIXME why? but first_point() would fail.
  2253. return false;
  2254. // This extrusion is part of certain Region, which tells us which extruder should be used for it:
  2255. int correct_extruder_id = layer_tools.extruder(*eec, region);
  2256. if (! layer_tools.has_extruder(correct_extruder_id)) {
  2257. // this entity is not overridden, but its extruder is not in layer_tools - we'll print it
  2258. // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools)
  2259. correct_extruder_id = layer_tools.extruders.back();
  2260. }
  2261. int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1;
  2262. return print_wipe_extrusions ?
  2263. extruder_override_id == int(extruder_id) :
  2264. extruder_override_id < 0 && int(extruder_id) == correct_extruder_id;
  2265. };
  2266. ExtrusionEntitiesPtr temp_fill_extrusions;
  2267. if (const Layer *layer = layer_to_print.object_layer; layer)
  2268. for (size_t idx : layer->lslice_indices_sorted_by_print_order) {
  2269. const LayerSlice &lslice = layer->lslices_ex[idx];
  2270. auto extrude_infill_range = [&](
  2271. const LayerRegion &layerm, const ExtrusionEntityCollection &fills,
  2272. LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) {
  2273. // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
  2274. // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
  2275. const PrintRegion &region = print.get_print_region(layerm.region().print_region_id());
  2276. temp_fill_extrusions.clear();
  2277. for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) {
  2278. assert(it_fill_range->region() == it_fill_ranges_begin->region());
  2279. for (uint32_t fill_id : *it_fill_range) {
  2280. assert(dynamic_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]));
  2281. if (auto *eec = static_cast<ExtrusionEntityCollection*>(fills.entities[fill_id]);
  2282. (eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) {
  2283. if (eec->can_reverse())
  2284. // Flatten the infill collection for better path planning.
  2285. for (auto *ee : eec->entities)
  2286. temp_fill_extrusions.emplace_back(ee);
  2287. else
  2288. temp_fill_extrusions.emplace_back(eec);
  2289. }
  2290. }
  2291. }
  2292. if (! temp_fill_extrusions.empty()) {
  2293. init_layer_delayed();
  2294. m_config.apply(region.config());
  2295. const auto extrusion_name = ironing ? "ironing"sv : "infill"sv;
  2296. const Point* start_near = this->last_position ? &(*(this->last_position)) : nullptr;
  2297. for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, start_near))
  2298. if (auto *eec = dynamic_cast<const ExtrusionEntityCollection*>(&fill.extrusion_entity()); eec) {
  2299. for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped()))
  2300. gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name);
  2301. } else
  2302. gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name);
  2303. }
  2304. };
  2305. //FIXME order islands?
  2306. // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511)
  2307. for (const LayerIsland &island : lslice.islands) {
  2308. auto process_perimeters = [&]() {
  2309. const LayerRegion &layerm = *layer->get_region(island.perimeters.region());
  2310. // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not
  2311. // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion.
  2312. const PrintRegion &region = print.get_print_region(layerm.region().print_region_id());
  2313. bool first = true;
  2314. for (uint32_t perimeter_id : island.perimeters) {
  2315. // Extrusions inside islands are expected to be ordered already.
  2316. // Don't reorder them.
  2317. assert(dynamic_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]));
  2318. if (const auto *eec = static_cast<const ExtrusionEntityCollection*>(layerm.perimeters().entities[perimeter_id]);
  2319. shall_print_this_extrusion_collection(eec, region)) {
  2320. // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well?
  2321. // assert(! eec->can_reverse());
  2322. if (first) {
  2323. first = false;
  2324. init_layer_delayed();
  2325. m_config.apply(region.config());
  2326. }
  2327. for (const ExtrusionEntity *ee : *eec) {
  2328. // Don't reorder, don't flip.
  2329. gcode += this->extrude_entity({*ee, false}, smooth_path_cache, comment_perimeter, -1.);
  2330. m_travel_obstacle_tracker.mark_extruded(ee, print_instance.object_layer_to_print_id, print_instance.instance_id);
  2331. }
  2332. }
  2333. }
  2334. };
  2335. auto process_infill = [&]() {
  2336. for (auto it = island.fills.begin(); it != island.fills.end();) {
  2337. // Gather range of fill ranges with the same region.
  2338. auto it_end = it;
  2339. for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ;
  2340. const LayerRegion &layerm = *layer->get_region(it->region());
  2341. extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */);
  2342. it = it_end;
  2343. }
  2344. };
  2345. if (print.config().infill_first) {
  2346. process_infill();
  2347. process_perimeters();
  2348. } else {
  2349. process_perimeters();
  2350. process_infill();
  2351. }
  2352. }
  2353. // ironing
  2354. //FIXME move ironing into the loop above over LayerIslands?
  2355. // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions.
  2356. // Ironing in a second phase is safer, but it may be less efficient.
  2357. for (const LayerIsland &island : lslice.islands) {
  2358. for (auto it = island.fills.begin(); it != island.fills.end();) {
  2359. // Gather range of fill ranges with the same region.
  2360. auto it_end = it;
  2361. for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ;
  2362. const LayerRegion &layerm = *layer->get_region(it->region());
  2363. extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */);
  2364. it = it_end;
  2365. }
  2366. }
  2367. }
  2368. if (! first)
  2369. gcode += m_label_objects.stop_object(print_instance.print_object.instances()[print_instance.instance_id]);
  2370. }
  2371. void GCodeGenerator::apply_print_config(const PrintConfig &print_config)
  2372. {
  2373. m_writer.apply_print_config(print_config);
  2374. m_config.apply(print_config);
  2375. m_scaled_resolution = scaled<double>(print_config.gcode_resolution.value);
  2376. }
  2377. void GCodeGenerator::append_full_config(const Print &print, std::string &str)
  2378. {
  2379. std::vector<std::pair<std::string, std::string>> config;
  2380. encode_full_config(print, config);
  2381. for (const auto& [key, value] : config) {
  2382. str += "; " + key + " = " + value + "\n";
  2383. }
  2384. }
  2385. void GCodeGenerator::encode_full_config(const Print& print, std::vector<std::pair<std::string, std::string>>& config)
  2386. {
  2387. const DynamicPrintConfig& cfg = print.full_print_config();
  2388. // Sorted list of config keys, which shall not be stored into the G-code. Initializer list.
  2389. static constexpr auto banned_keys = {
  2390. "compatible_printers"sv,
  2391. "compatible_prints"sv,
  2392. //FIXME The print host keys should not be exported to full_print_config anymore. The following keys may likely be removed.
  2393. "print_host"sv,
  2394. "printhost_apikey"sv,
  2395. "printhost_cafile"sv
  2396. };
  2397. assert(std::is_sorted(banned_keys.begin(), banned_keys.end()));
  2398. auto is_banned = [](const std::string& key) {
  2399. return std::binary_search(banned_keys.begin(), banned_keys.end(), key);
  2400. };
  2401. config.reserve(config.size() + cfg.keys().size());
  2402. for (const std::string& key : cfg.keys()) {
  2403. if (!is_banned(key) && !cfg.option(key)->is_nil())
  2404. config.emplace_back(key, cfg.opt_serialize(key));
  2405. }
  2406. config.shrink_to_fit();
  2407. }
  2408. void GCodeGenerator::set_extruders(const std::vector<unsigned int> &extruder_ids)
  2409. {
  2410. m_writer.set_extruders(extruder_ids);
  2411. m_wipe.init(this->config(), extruder_ids);
  2412. }
  2413. void GCodeGenerator::set_origin(const Vec2d &pointf)
  2414. {
  2415. // if origin increases (goes towards right), last_pos decreases because it goes towards left
  2416. const auto offset = Point::new_scale(m_origin - pointf);
  2417. if (last_position.has_value())
  2418. *(this->last_position) += offset;
  2419. m_wipe.offset_path(offset);
  2420. m_origin = pointf;
  2421. }
  2422. std::string GCodeGenerator::preamble()
  2423. {
  2424. std::string gcode = m_writer.preamble();
  2425. /* Perform a *silent* move to z_offset: we need this to initialize the Z
  2426. position of our writer object so that any initial lift taking place
  2427. before the first layer change will raise the extruder from the correct
  2428. initial Z instead of 0. */
  2429. m_writer.travel_to_z(m_config.z_offset.value);
  2430. return gcode;
  2431. }
  2432. // called by GCodeGenerator::process_layer()
  2433. std::string GCodeGenerator::change_layer(
  2434. coordf_t previous_layer_z,
  2435. coordf_t print_z
  2436. ) {
  2437. std::string gcode;
  2438. if (m_layer_count > 0)
  2439. // Increment a progress bar indicator.
  2440. gcode += m_writer.update_progress(++ m_layer_index, m_layer_count);
  2441. if (EXTRUDER_CONFIG(retract_layer_change))
  2442. gcode += this->retract_and_wipe();
  2443. Vec3d new_position = this->writer().get_position();
  2444. new_position.z() = print_z + m_config.z_offset.value;
  2445. this->writer().update_position(new_position);
  2446. m_previous_layer_last_position = this->last_position ?
  2447. std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} :
  2448. std::nullopt;
  2449. gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel);
  2450. this->m_layer_change_extruder_id = m_writer.extruder()->id();
  2451. // forget last wiping path as wiping after raising Z is pointless
  2452. m_wipe.reset_path();
  2453. return gcode;
  2454. }
  2455. #ifndef NDEBUG
  2456. static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop)
  2457. {
  2458. for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) {
  2459. assert(it->path.size() >= 2);
  2460. assert(std::prev(it)->path.back().point == it->path.front().point);
  2461. }
  2462. assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point);
  2463. return true;
  2464. }
  2465. #endif //NDEBUG
  2466. static constexpr const double min_gcode_segment_length = 0.002;
  2467. std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
  2468. {
  2469. // Extrude all loops CCW.
  2470. bool is_hole = loop_src.is_clockwise();
  2471. Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero();
  2472. if (!m_config.spiral_vase && comment_is_perimeter(description)) {
  2473. assert(m_layer != nullptr);
  2474. seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, seam_point);
  2475. }
  2476. // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
  2477. // thus empty path segments will not be produced by G-code export.
  2478. GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(
  2479. loop_src, is_hole, m_scaled_resolution, seam_point, scaled<double>(0.0015));
  2480. // Clip the path to avoid the extruder to get exactly on the first point of the loop;
  2481. // if polyline was shorter than the clipping distance we'd get a null polyline, so
  2482. // we discard it in that case.
  2483. if (m_enable_loop_clipping)
  2484. clip_end(smooth_path, scaled<double>(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled<double>(min_gcode_segment_length));
  2485. if (smooth_path.empty())
  2486. return {};
  2487. assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
  2488. // Apply the small perimeter speed.
  2489. if (loop_src.paths.front().role().is_perimeter() && loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1)
  2490. speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed);
  2491. // Extrude along the smooth path.
  2492. std::string gcode;
  2493. for (const GCode::SmoothPathElement &el : smooth_path)
  2494. gcode += this->_extrude(el.path_attributes, el.path, description, speed);
  2495. // reset acceleration
  2496. gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
  2497. if (m_wipe.enabled()) {
  2498. // Wipe will hide the seam.
  2499. m_wipe.set_path(std::move(smooth_path), false);
  2500. } else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) {
  2501. // Only wipe inside if the wipe along the perimeter is disabled.
  2502. // Make a little move inwards before leaving loop.
  2503. if (std::optional<Point> pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) {
  2504. // Generate the seam hiding travel move.
  2505. gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel");
  2506. this->last_position = *pt;
  2507. }
  2508. }
  2509. return gcode;
  2510. }
  2511. std::string GCodeGenerator::extrude_skirt(
  2512. const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override,
  2513. const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
  2514. {
  2515. assert(loop_src.is_counter_clockwise());
  2516. GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(
  2517. loop_src, false, m_scaled_resolution, *this->last_position, scaled<double>(0.0015));
  2518. // Clip the path to avoid the extruder to get exactly on the first point of the loop;
  2519. // if polyline was shorter than the clipping distance we'd get a null polyline, so
  2520. // we discard it in that case.
  2521. if (m_enable_loop_clipping)
  2522. clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled<double>(min_gcode_segment_length));
  2523. if (smooth_path.empty())
  2524. return {};
  2525. assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping));
  2526. // Extrude along the smooth path.
  2527. std::string gcode;
  2528. for (GCode::SmoothPathElement &el : smooth_path) {
  2529. // Override extrusion parameters.
  2530. el.path_attributes.mm3_per_mm = extrusion_flow_override.mm3_per_mm;
  2531. el.path_attributes.height = extrusion_flow_override.height;
  2532. gcode += this->_extrude(el.path_attributes, el.path, description, speed);
  2533. }
  2534. // reset acceleration
  2535. gcode += m_writer.set_print_acceleration(fast_round_up<unsigned int>(m_config.default_acceleration.value));
  2536. if (m_wipe.enabled())
  2537. // Wipe will hide the seam.
  2538. m_wipe.set_path(std::move(smooth_path), false);
  2539. return gcode;
  2540. }
  2541. std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
  2542. {
  2543. #ifndef NDEBUG
  2544. for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) {
  2545. assert(it->polyline.points.size() >= 2);
  2546. assert(std::prev(it)->polyline.last_point() == it->polyline.first_point());
  2547. }
  2548. #endif // NDEBUG
  2549. GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution);
  2550. // extrude along the path
  2551. std::string gcode;
  2552. for (GCode::SmoothPathElement &el : smooth_path)
  2553. gcode += this->_extrude(el.path_attributes, el.path, description, speed);
  2554. m_wipe.set_path(std::move(smooth_path), true);
  2555. // reset acceleration
  2556. gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
  2557. return gcode;
  2558. }
  2559. std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed)
  2560. {
  2561. if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(&entity.extrusion_entity()))
  2562. return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed);
  2563. else if (const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath*>(&entity.extrusion_entity()))
  2564. return this->extrude_multi_path(*multipath, entity.flipped(), smooth_path_cache, description, speed);
  2565. else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(&entity.extrusion_entity()))
  2566. return this->extrude_loop(*loop, smooth_path_cache, description, speed);
  2567. else
  2568. throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()");
  2569. return {};
  2570. }
  2571. std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, std::string_view description, double speed)
  2572. {
  2573. Geometry::ArcWelder::Path smooth_path = smooth_path_cache.resolve_or_fit(path, reverse, m_scaled_resolution);
  2574. std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed);
  2575. Geometry::ArcWelder::reverse(smooth_path);
  2576. m_wipe.set_path(std::move(smooth_path));
  2577. // reset acceleration
  2578. gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5));
  2579. return gcode;
  2580. }
  2581. std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache)
  2582. {
  2583. static constexpr const auto support_label = "support material"sv;
  2584. static constexpr const auto support_interface_label = "support material interface"sv;
  2585. std::string gcode;
  2586. if (! support_fills.empty()) {
  2587. const double support_speed = m_config.support_material_speed.value;
  2588. const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed);
  2589. for (const ExtrusionEntityReference &eref : support_fills) {
  2590. ExtrusionRole role = eref.extrusion_entity().role();
  2591. assert(role == ExtrusionRole::SupportMaterial || role == ExtrusionRole::SupportMaterialInterface);
  2592. const auto label = (role == ExtrusionRole::SupportMaterial) ? support_label : support_interface_label;
  2593. const double speed = (role == ExtrusionRole::SupportMaterial) ? support_speed : support_interface_speed;
  2594. const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(&eref.extrusion_entity());
  2595. if (path)
  2596. gcode += this->extrude_path(*path, eref.flipped(), smooth_path_cache, label, speed);
  2597. else if (const ExtrusionMultiPath *multipath = dynamic_cast<const ExtrusionMultiPath*>(&eref.extrusion_entity()); multipath)
  2598. gcode += this->extrude_multi_path(*multipath, eref.flipped(), smooth_path_cache, label, speed);
  2599. else {
  2600. const ExtrusionEntityCollection *eec = dynamic_cast<const ExtrusionEntityCollection*>(&eref.extrusion_entity());
  2601. assert(eec);
  2602. if (eec) {
  2603. //FIXME maybe order the support here?
  2604. ExtrusionEntityReferences refs;
  2605. refs.reserve(eec->entities.size());
  2606. std::transform(eec->entities.begin(), eec->entities.end(), std::back_inserter(refs),
  2607. [flipped = eref.flipped()](const ExtrusionEntity *ee) { return ExtrusionEntityReference{ *ee, flipped }; });
  2608. gcode += this->extrude_support(refs, smooth_path_cache);
  2609. }
  2610. }
  2611. }
  2612. }
  2613. return gcode;
  2614. }
  2615. bool GCodeGenerator::GCodeOutputStream::is_error() const
  2616. {
  2617. return ::ferror(this->f);
  2618. }
  2619. void GCodeGenerator::GCodeOutputStream::flush()
  2620. {
  2621. ::fflush(this->f);
  2622. }
  2623. void GCodeGenerator::GCodeOutputStream::close()
  2624. {
  2625. if (this->f) {
  2626. ::fclose(this->f);
  2627. this->f = nullptr;
  2628. }
  2629. }
  2630. void GCodeGenerator::GCodeOutputStream::write(const char *what)
  2631. {
  2632. if (what != nullptr) {
  2633. //FIXME don't allocate a string, maybe process a batch of lines?
  2634. std::string gcode(m_find_replace ? m_find_replace->process_layer(what) : what);
  2635. // writes string to file
  2636. fwrite(gcode.c_str(), 1, gcode.size(), this->f);
  2637. m_processor.process_buffer(gcode);
  2638. }
  2639. }
  2640. void GCodeGenerator::GCodeOutputStream::writeln(const std::string &what)
  2641. {
  2642. if (! what.empty())
  2643. this->write(what.back() == '\n' ? what : what + '\n');
  2644. }
  2645. void GCodeGenerator::GCodeOutputStream::write_format(const char* format, ...)
  2646. {
  2647. va_list args;
  2648. va_start(args, format);
  2649. int buflen;
  2650. {
  2651. va_list args2;
  2652. va_copy(args2, args);
  2653. buflen =
  2654. #ifdef _MSC_VER
  2655. ::_vscprintf(format, args2)
  2656. #else
  2657. ::vsnprintf(nullptr, 0, format, args2)
  2658. #endif
  2659. + 1;
  2660. va_end(args2);
  2661. }
  2662. char buffer[1024];
  2663. bool buffer_dynamic = buflen > 1024;
  2664. char *bufptr = buffer_dynamic ? (char*)malloc(buflen) : buffer;
  2665. int res = ::vsnprintf(bufptr, buflen, format, args);
  2666. if (res > 0)
  2667. this->write(bufptr);
  2668. if (buffer_dynamic)
  2669. free(bufptr);
  2670. va_end(args);
  2671. }
  2672. std::string GCodeGenerator::_extrude(
  2673. const ExtrusionAttributes &path_attr,
  2674. const Geometry::ArcWelder::Path &path,
  2675. const std::string_view description,
  2676. double speed)
  2677. {
  2678. std::string gcode;
  2679. const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv;
  2680. if (!m_current_layer_first_position) {
  2681. const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z + this->m_config.z_offset.value));
  2682. const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z()));
  2683. if (!this->last_position) {
  2684. double lift{
  2685. EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) :
  2686. EXTRUDER_CONFIG(retract_lift)};
  2687. const double upper_limit = EXTRUDER_CONFIG(retract_lift_below);
  2688. const double lower_limit = EXTRUDER_CONFIG(retract_lift_above);
  2689. if ((lower_limit > 0 && gcode_point.z() < lower_limit) ||
  2690. (upper_limit > 0 && gcode_point.z() > upper_limit)) {
  2691. lift = 0.0;
  2692. }
  2693. gcode += this->writer().get_travel_to_z_gcode(gcode_point.z() + lift, "lift");
  2694. }
  2695. this->last_position = path.front().point;
  2696. this->writer().update_position(gcode_point);
  2697. gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point");
  2698. gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point");
  2699. m_current_layer_first_position = gcode_point;
  2700. } else {
  2701. // go to first point of extrusion path
  2702. if (!this->last_position) {
  2703. const double z = this->m_last_layer_z + this->m_config.z_offset.value;
  2704. const std::string comment{"move to print after unknown position"};
  2705. gcode += this->retract_and_wipe();
  2706. gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment);
  2707. gcode += this->m_writer.get_travel_to_z_gcode(z, comment);
  2708. } else if ( this->last_position != path.front().point) {
  2709. std::string comment = "move to first ";
  2710. comment += description;
  2711. comment += description_bridge;
  2712. comment += " point";
  2713. const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)};
  2714. gcode += travel_gcode;
  2715. }
  2716. }
  2717. // compensate retraction
  2718. gcode += this->unretract();
  2719. // adjust acceleration
  2720. if (m_config.default_acceleration.value > 0) {
  2721. double acceleration;
  2722. if (this->on_first_layer() && m_config.first_layer_acceleration.value > 0) {
  2723. acceleration = m_config.first_layer_acceleration.value;
  2724. } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
  2725. acceleration = m_config.first_layer_acceleration_over_raft.value;
  2726. } else if (m_config.bridge_acceleration.value > 0 && path_attr.role.is_bridge()) {
  2727. acceleration = m_config.bridge_acceleration.value;
  2728. } else if (m_config.top_solid_infill_acceleration > 0 && path_attr.role == ExtrusionRole::TopSolidInfill) {
  2729. acceleration = m_config.top_solid_infill_acceleration.value;
  2730. } else if (m_config.solid_infill_acceleration > 0 && path_attr.role.is_solid_infill()) {
  2731. acceleration = m_config.solid_infill_acceleration.value;
  2732. } else if (m_config.infill_acceleration.value > 0 && path_attr.role.is_infill()) {
  2733. acceleration = m_config.infill_acceleration.value;
  2734. } else if (m_config.external_perimeter_acceleration > 0 && path_attr.role.is_external_perimeter()) {
  2735. acceleration = m_config.external_perimeter_acceleration.value;
  2736. } else if (m_config.perimeter_acceleration.value > 0 && path_attr.role.is_perimeter()) {
  2737. acceleration = m_config.perimeter_acceleration.value;
  2738. } else {
  2739. acceleration = m_config.default_acceleration.value;
  2740. }
  2741. gcode += m_writer.set_print_acceleration((unsigned int)floor(acceleration + 0.5));
  2742. }
  2743. // calculate extrusion length per distance unit
  2744. double e_per_mm = m_writer.extruder()->e_per_mm3() * path_attr.mm3_per_mm;
  2745. if (m_writer.extrusion_axis().empty())
  2746. // gcfNoExtrusion
  2747. e_per_mm = 0;
  2748. // set speed
  2749. if (speed == -1) {
  2750. if (path_attr.role == ExtrusionRole::Perimeter) {
  2751. speed = m_config.get_abs_value("perimeter_speed");
  2752. } else if (path_attr.role == ExtrusionRole::ExternalPerimeter) {
  2753. speed = m_config.get_abs_value("external_perimeter_speed");
  2754. } else if (path_attr.role.is_bridge()) {
  2755. assert(path_attr.role.is_perimeter() || path_attr.role == ExtrusionRole::BridgeInfill);
  2756. speed = m_config.get_abs_value("bridge_speed");
  2757. } else if (path_attr.role == ExtrusionRole::InternalInfill) {
  2758. speed = m_config.get_abs_value("infill_speed");
  2759. } else if (path_attr.role == ExtrusionRole::SolidInfill) {
  2760. speed = m_config.get_abs_value("solid_infill_speed");
  2761. } else if (path_attr.role == ExtrusionRole::TopSolidInfill) {
  2762. speed = m_config.get_abs_value("top_solid_infill_speed");
  2763. } else if (path_attr.role == ExtrusionRole::Ironing) {
  2764. speed = m_config.get_abs_value("ironing_speed");
  2765. } else if (path_attr.role == ExtrusionRole::GapFill) {
  2766. speed = m_config.get_abs_value("gap_fill_speed");
  2767. } else {
  2768. throw Slic3r::InvalidArgument("Invalid speed");
  2769. }
  2770. }
  2771. if (m_volumetric_speed != 0. && speed == 0)
  2772. speed = m_volumetric_speed / path_attr.mm3_per_mm;
  2773. if (this->on_first_layer())
  2774. speed = m_config.get_abs_value("first_layer_speed", speed);
  2775. else if (this->object_layer_over_raft())
  2776. speed = m_config.get_abs_value("first_layer_speed_over_raft", speed);
  2777. if (m_config.max_volumetric_speed.value > 0) {
  2778. // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
  2779. speed = std::min(
  2780. speed,
  2781. m_config.max_volumetric_speed.value / path_attr.mm3_per_mm
  2782. );
  2783. }
  2784. if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
  2785. // cap speed with max_volumetric_speed anyway (even if user is not using autospeed)
  2786. speed = std::min(
  2787. speed,
  2788. EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm
  2789. );
  2790. }
  2791. std::pair<float, float> dynamic_speed_and_fan_speed{-1, -1};
  2792. if (path_attr.overhang_attributes.has_value()) {
  2793. double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed");
  2794. if (external_perim_reference_speed == 0)
  2795. external_perim_reference_speed = m_volumetric_speed / path_attr.mm3_per_mm;
  2796. if (m_config.max_volumetric_speed.value > 0)
  2797. external_perim_reference_speed = std::min(external_perim_reference_speed,
  2798. m_config.max_volumetric_speed.value / path_attr.mm3_per_mm);
  2799. if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) {
  2800. external_perim_reference_speed = std::min(external_perim_reference_speed,
  2801. EXTRUDER_CONFIG(filament_max_volumetric_speed) / path_attr.mm3_per_mm);
  2802. }
  2803. dynamic_speed_and_fan_speed = ExtrusionProcessor::calculate_overhang_speed(path_attr, this->m_config, m_writer.extruder()->id(),
  2804. external_perim_reference_speed, speed);
  2805. }
  2806. if (dynamic_speed_and_fan_speed.first > -1) {
  2807. speed = dynamic_speed_and_fan_speed.first;
  2808. }
  2809. double F = speed * 60; // convert mm/sec to mm/min
  2810. // extrude arc or line
  2811. if (m_enable_extrusion_role_markers)
  2812. {
  2813. if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path_attr.role); role != m_last_extrusion_role)
  2814. {
  2815. m_last_extrusion_role = role;
  2816. if (m_enable_extrusion_role_markers)
  2817. {
  2818. char buf[32];
  2819. sprintf(buf, ";_EXTRUSION_ROLE:%d\n", int(m_last_extrusion_role));
  2820. gcode += buf;
  2821. }
  2822. }
  2823. }
  2824. // adds processor tags and updates processor tracking data
  2825. // PrusaMultiMaterial::Writer may generate GCodeProcessor::Height_Tag lines without updating m_last_height
  2826. // so, if the last role was GCodeExtrusionRole::WipeTower we force export of GCodeProcessor::Height_Tag lines
  2827. bool last_was_wipe_tower = (m_last_processor_extrusion_role == GCodeExtrusionRole::WipeTower);
  2828. assert(is_decimal_separator_point());
  2829. if (GCodeExtrusionRole role = extrusion_role_to_gcode_extrusion_role(path_attr.role); role != m_last_processor_extrusion_role) {
  2830. m_last_processor_extrusion_role = role;
  2831. char buf[64];
  2832. sprintf(buf, ";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), gcode_extrusion_role_to_string(m_last_processor_extrusion_role).c_str());
  2833. gcode += buf;
  2834. }
  2835. if (last_was_wipe_tower || m_last_width != path_attr.width) {
  2836. m_last_width = path_attr.width;
  2837. gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Width)
  2838. + float_to_string_decimal_point(m_last_width) + "\n";
  2839. }
  2840. #if ENABLE_GCODE_VIEWER_DATA_CHECKING
  2841. if (last_was_wipe_tower || (m_last_mm3_per_mm != path_attr.mm3_per_mm)) {
  2842. m_last_mm3_per_mm = path_attr.mm3_per_mm;
  2843. gcode += std::string(";") + GCodeProcessor::Mm3_Per_Mm_Tag
  2844. + float_to_string_decimal_point(m_last_mm3_per_mm) + "\n";
  2845. }
  2846. #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
  2847. if (last_was_wipe_tower || std::abs(m_last_height - path_attr.height) > EPSILON) {
  2848. m_last_height = path_attr.height;
  2849. gcode += std::string(";") + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height)
  2850. + float_to_string_decimal_point(m_last_height) + "\n";
  2851. }
  2852. std::string cooling_marker_setspeed_comments;
  2853. if (m_enable_cooling_markers) {
  2854. if (path_attr.role.is_bridge())
  2855. gcode += ";_BRIDGE_FAN_START\n";
  2856. else
  2857. cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED";
  2858. if (path_attr.role == ExtrusionRole::ExternalPerimeter)
  2859. cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER";
  2860. }
  2861. // F is mm per minute.
  2862. gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments);
  2863. if (dynamic_speed_and_fan_speed.second >= 0)
  2864. gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_speed_and_fan_speed.second)) + "\n";
  2865. std::string comment;
  2866. if (m_config.gcode_comments) {
  2867. comment = description;
  2868. comment += description_bridge;
  2869. }
  2870. Vec2d prev_exact = this->point_to_gcode(path.front().point);
  2871. Vec2d prev = GCodeFormatter::quantize(prev_exact);
  2872. auto it = path.begin();
  2873. auto end = path.end();
  2874. for (++ it; it != end; ++ it) {
  2875. Vec2d p_exact = this->point_to_gcode(it->point);
  2876. Vec2d p = GCodeFormatter::quantize(p_exact);
  2877. assert(p != prev);
  2878. if (p != prev) {
  2879. // Center of the radius to be emitted into the G-code: Either by radius or by center offset.
  2880. double radius = 0;
  2881. Vec2d ij;
  2882. if (it->radius != 0) {
  2883. // Extrude an arc.
  2884. assert(m_config.arc_fitting == ArcFittingType::EmitCenter);
  2885. radius = unscaled<double>(it->radius);
  2886. {
  2887. // Calculate quantized IJ circle center offset.
  2888. ij = GCodeFormatter::quantize(Vec2d(
  2889. Geometry::ArcWelder::arc_center(prev_exact.cast<double>(), p_exact.cast<double>(), double(radius), it->ccw())
  2890. - prev));
  2891. if (ij == Vec2d::Zero())
  2892. // Don't extrude a degenerated circle.
  2893. radius = 0;
  2894. }
  2895. }
  2896. if (radius == 0) {
  2897. // Extrude line segment.
  2898. if (const double line_length = (p - prev).norm(); line_length > 0)
  2899. gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment);
  2900. } else {
  2901. double angle = Geometry::ArcWelder::arc_angle(prev.cast<double>(), p.cast<double>(), double(radius));
  2902. assert(angle > 0);
  2903. const double line_length = angle * std::abs(radius);
  2904. const double dE = e_per_mm * line_length;
  2905. assert(dE > 0);
  2906. gcode += m_writer.extrude_to_xy_G2G3IJ(p, ij, it->ccw(), dE, comment);
  2907. }
  2908. prev = p;
  2909. prev_exact = p_exact;
  2910. }
  2911. }
  2912. if (m_enable_cooling_markers)
  2913. gcode += path_attr.role.is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
  2914. if (dynamic_speed_and_fan_speed.second >= 0)
  2915. gcode += ";_RESET_FAN_SPEED\n";
  2916. this->last_position = path.back().point;
  2917. return gcode;
  2918. }
  2919. std::string GCodeGenerator::generate_travel_gcode(
  2920. const Points3& travel,
  2921. const std::string& comment
  2922. ) {
  2923. std::string gcode;
  2924. const unsigned acceleration =(unsigned)(m_config.travel_acceleration.value + 0.5);
  2925. if (travel.empty()) {
  2926. return "";
  2927. }
  2928. // generate G-code for the travel move
  2929. // use G1 because we rely on paths being straight (G0 may make round paths)
  2930. gcode += this->m_writer.set_travel_acceleration(acceleration);
  2931. Vec3d previous_point{this->point_to_gcode(travel.front())};
  2932. for (const Vec3crd& point : tcb::span{travel}.subspan(1)) {
  2933. const Vec3d gcode_point{this->point_to_gcode(point)};
  2934. gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment);
  2935. this->last_position = point.head<2>();
  2936. previous_point = gcode_point;
  2937. }
  2938. if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) {
  2939. // In case that this flavor does not support separate print and travel acceleration,
  2940. // reset acceleration to default.
  2941. gcode += this->m_writer.set_travel_acceleration(acceleration);
  2942. }
  2943. return gcode;
  2944. }
  2945. bool GCodeGenerator::needs_retraction(const Polyline &travel, ExtrusionRole role)
  2946. {
  2947. if (! m_writer.extruder() || travel.length() < scale_(EXTRUDER_CONFIG(retract_before_travel))) {
  2948. // skip retraction if the move is shorter than the configured threshold
  2949. return false;
  2950. }
  2951. if (role == ExtrusionRole::SupportMaterial)
  2952. if (const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(m_layer);
  2953. support_layer != nullptr && ! support_layer->support_islands_bboxes.empty()) {
  2954. BoundingBox bbox_travel = get_extents(travel);
  2955. Polylines trimmed;
  2956. bool trimmed_initialized = false;
  2957. for (const BoundingBox &bbox : support_layer->support_islands_bboxes)
  2958. if (bbox.overlap(bbox_travel)) {
  2959. const auto &island = support_layer->support_islands[&bbox - support_layer->support_islands_bboxes.data()];
  2960. trimmed = trimmed_initialized ? diff_pl(trimmed, island) : diff_pl(travel, island);
  2961. trimmed_initialized = true;
  2962. if (trimmed.empty())
  2963. // skip retraction if this is a travel move inside a support material island
  2964. //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
  2965. // at the end of the extrusion path!
  2966. return false;
  2967. // Not sure whether updating the boudning box isn't too expensive.
  2968. //bbox_travel = get_extents(trimmed);
  2969. }
  2970. }
  2971. if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr &&
  2972. m_config.fill_density.value > 0 && m_retract_when_crossing_perimeters.travel_inside_internal_regions(*m_layer, travel))
  2973. // Skip retraction if travel is contained in an internal slice *and*
  2974. // internal infill is enabled (so that stringing is entirely not visible).
  2975. //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
  2976. return false;
  2977. // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
  2978. return true;
  2979. }
  2980. Polyline GCodeGenerator::generate_travel_xy_path(
  2981. const Point& start_point,
  2982. const Point& end_point,
  2983. const bool needs_retraction,
  2984. bool& could_be_wipe_disabled
  2985. ) {
  2986. const Point scaled_origin{scaled(this->origin())};
  2987. const bool avoid_crossing_perimeters = (
  2988. this->m_config.avoid_crossing_perimeters
  2989. && !this->m_avoid_crossing_perimeters.disabled_once()
  2990. );
  2991. Polyline xy_path{start_point, end_point};
  2992. if (m_config.avoid_crossing_curled_overhangs) {
  2993. if (avoid_crossing_perimeters) {
  2994. BOOST_LOG_TRIVIAL(warning)
  2995. << "Option >avoid crossing curled overhangs< is not compatible with avoid crossing perimeters and it will be ignored!";
  2996. } else {
  2997. xy_path = this->m_avoid_crossing_curled_overhangs.find_path(
  2998. start_point + scaled_origin,
  2999. end_point + scaled_origin
  3000. );
  3001. xy_path.translate(-scaled_origin);
  3002. }
  3003. }
  3004. // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
  3005. // multi-hop travel path inside the configuration space
  3006. if (
  3007. needs_retraction
  3008. && avoid_crossing_perimeters
  3009. ) {
  3010. xy_path = this->m_avoid_crossing_perimeters.travel_to(*this, end_point, &could_be_wipe_disabled);
  3011. }
  3012. return xy_path;
  3013. }
  3014. // This method accepts &point in print coordinates.
  3015. std::string GCodeGenerator::travel_to(
  3016. const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment
  3017. ) {
  3018. // check whether a straight travel move would need retraction
  3019. bool could_be_wipe_disabled {false};
  3020. bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role);
  3021. Polyline xy_path{generate_travel_xy_path(
  3022. start_point, end_point, needs_retraction, could_be_wipe_disabled
  3023. )};
  3024. needs_retraction = this->needs_retraction(xy_path, role);
  3025. std::string wipe_retract_gcode{};
  3026. if (needs_retraction) {
  3027. if (could_be_wipe_disabled) {
  3028. m_wipe.reset_path();
  3029. }
  3030. Point position_before_wipe{*this->last_position};
  3031. wipe_retract_gcode = this->retract_and_wipe();
  3032. if (*this->last_position != position_before_wipe) {
  3033. xy_path = generate_travel_xy_path(
  3034. *this->last_position, end_point, needs_retraction, could_be_wipe_disabled
  3035. );
  3036. }
  3037. } else {
  3038. m_wipe.reset_path();
  3039. }
  3040. this->m_avoid_crossing_perimeters.reset_once_modifiers();
  3041. const unsigned extruder_id = this->m_writer.extruder()->id();
  3042. const double retract_length = this->m_config.retract_length.get_at(extruder_id);
  3043. bool can_be_flat{!needs_retraction || retract_length == 0};
  3044. const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value;
  3045. const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id);
  3046. const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id);
  3047. if ((lower_limit > 0 && initial_elevation < lower_limit) ||
  3048. (upper_limit > 0 && initial_elevation > upper_limit)) {
  3049. can_be_flat = true;
  3050. }
  3051. const Points3 travel = (
  3052. can_be_flat ?
  3053. GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) :
  3054. GCode::Impl::Travels::generate_travel_to_extrusion(
  3055. xy_path,
  3056. m_config,
  3057. extruder_id,
  3058. initial_elevation,
  3059. m_travel_obstacle_tracker,
  3060. scaled(m_origin)
  3061. )
  3062. );
  3063. return wipe_retract_gcode + generate_travel_gcode(travel, comment);
  3064. }
  3065. std::string GCodeGenerator::retract_and_wipe(bool toolchange)
  3066. {
  3067. std::string gcode;
  3068. if (m_writer.extruder() == nullptr)
  3069. return gcode;
  3070. // wipe (if it's enabled for this extruder and we have a stored wipe path)
  3071. if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) {
  3072. gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true);
  3073. gcode += m_wipe.wipe(*this, toolchange);
  3074. }
  3075. /* The parent class will decide whether we need to perform an actual retraction
  3076. (the extruder might be already retracted fully or partially). We call these
  3077. methods even if we performed wipe, since this will ensure the entire retraction
  3078. length is honored in case wipe path was too short. */
  3079. gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract();
  3080. gcode += m_writer.reset_e();
  3081. return gcode;
  3082. }
  3083. std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_z)
  3084. {
  3085. if (!m_writer.need_toolchange(extruder_id))
  3086. return "";
  3087. // if we are running a single-extruder setup, just set the extruder and return nothing
  3088. if (!m_writer.multiple_extruders) {
  3089. this->placeholder_parser().set("current_extruder", extruder_id);
  3090. std::string gcode;
  3091. // Append the filament start G-code.
  3092. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
  3093. if (! start_filament_gcode.empty()) {
  3094. // Process the start_filament_gcode for the filament.
  3095. DynamicConfig config;
  3096. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  3097. config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value));
  3098. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  3099. config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
  3100. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
  3101. check_add_eol(gcode);
  3102. }
  3103. gcode += m_writer.toolchange(extruder_id);
  3104. return gcode;
  3105. }
  3106. // prepend retraction on the current extruder
  3107. std::string gcode = this->retract_and_wipe(true);
  3108. // Always reset the extrusion path, even if the tool change retract is set to zero.
  3109. m_wipe.reset_path();
  3110. if (m_writer.extruder() != nullptr) {
  3111. // Process the custom end_filament_gcode.
  3112. unsigned int old_extruder_id = m_writer.extruder()->id();
  3113. const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id);
  3114. if (! end_filament_gcode.empty()) {
  3115. DynamicConfig config;
  3116. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  3117. config.set_key_value("layer_z", new ConfigOptionFloat(m_writer.get_position().z() - m_config.z_offset.value));
  3118. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  3119. config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(old_extruder_id)));
  3120. gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id, &config);
  3121. check_add_eol(gcode);
  3122. }
  3123. }
  3124. // If ooze prevention is enabled, set current extruder to the standby temperature.
  3125. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr)
  3126. gcode += m_ooze_prevention.pre_toolchange(*this);
  3127. const std::string& toolchange_gcode = m_config.toolchange_gcode.value;
  3128. std::string toolchange_gcode_parsed;
  3129. // Process the custom toolchange_gcode. If it is empty, insert just a Tn command.
  3130. if (!toolchange_gcode.empty()) {
  3131. DynamicConfig config;
  3132. config.set_key_value("previous_extruder", new ConfigOptionInt((int)(m_writer.extruder() != nullptr ? m_writer.extruder()->id() : -1 )));
  3133. config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id));
  3134. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  3135. config.set_key_value("layer_z", new ConfigOptionFloat(print_z));
  3136. config.set_key_value("toolchange_z", new ConfigOptionFloat(print_z));
  3137. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  3138. toolchange_gcode_parsed = placeholder_parser_process("toolchange_gcode", toolchange_gcode, extruder_id, &config);
  3139. gcode += toolchange_gcode_parsed;
  3140. check_add_eol(gcode);
  3141. }
  3142. // We inform the writer about what is happening, but we may not use the resulting gcode.
  3143. std::string toolchange_command = m_writer.toolchange(extruder_id);
  3144. if (! custom_gcode_changes_tool(toolchange_gcode_parsed, m_writer.toolchange_prefix(), extruder_id))
  3145. gcode += toolchange_command;
  3146. else {
  3147. // user provided his own toolchange gcode, no need to do anything
  3148. }
  3149. // Set the temperature if the wipe tower didn't (not needed for non-single extruder MM)
  3150. if (m_config.single_extruder_multi_material && !m_config.wipe_tower) {
  3151. int temp = (m_layer_index <= 0 ? m_config.first_layer_temperature.get_at(extruder_id) :
  3152. m_config.temperature.get_at(extruder_id));
  3153. gcode += m_writer.set_temperature(temp, false);
  3154. }
  3155. this->placeholder_parser().set("current_extruder", extruder_id);
  3156. // Append the filament start G-code.
  3157. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
  3158. if (! start_filament_gcode.empty()) {
  3159. // Process the start_filament_gcode for the new filament.
  3160. DynamicConfig config;
  3161. config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index));
  3162. config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value));
  3163. config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z));
  3164. config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(extruder_id)));
  3165. gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id, &config);
  3166. check_add_eol(gcode);
  3167. }
  3168. // Set the new extruder to the operating temperature.
  3169. if (m_ooze_prevention.enable)
  3170. gcode += m_ooze_prevention.post_toolchange(*this);
  3171. // The position is now known after the tool change.
  3172. this->last_position = std::nullopt;
  3173. return gcode;
  3174. }
  3175. // convert a model-space scaled point into G-code coordinates
  3176. Point GCodeGenerator::gcode_to_point(const Vec2d &point) const
  3177. {
  3178. Vec2d pt = point - m_origin;
  3179. if (const Extruder *extruder = m_writer.extruder(); extruder)
  3180. // This function may be called at the very start from toolchange G-code when the extruder is not assigned yet.
  3181. pt += m_config.extruder_offset.get_at(extruder->id());
  3182. return scaled<coord_t>(pt);
  3183. }
  3184. } // namespace Slic3r