Flow.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. ///|/ Copyright (c) Prusa Research 2016 - 2023 Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros
  2. ///|/ Copyright (c) Slic3r 2014 - 2015 Alessandro Ranellucci @alranel
  3. ///|/ Copyright (c) 2014 Petr Ledvina @ledvinap
  4. ///|/
  5. ///|/ ported from lib/Slic3r/Flow.pm:
  6. ///|/ Copyright (c) Prusa Research 2022 Vojtěch Bubník @bubnikv
  7. ///|/ Copyright (c) Slic3r 2012 - 2014 Alessandro Ranellucci @alranel
  8. ///|/
  9. ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
  10. ///|/
  11. #include "Flow.hpp"
  12. #include "I18N.hpp"
  13. #include "Print.hpp"
  14. #include "Layer.hpp"
  15. #include "Layer.hpp"
  16. #include <cmath>
  17. #include <assert.h>
  18. #include <boost/algorithm/string/predicate.hpp>
  19. #include <boost/algorithm/string/replace.hpp>
  20. namespace Slic3r {
  21. FlowErrorNegativeSpacing::FlowErrorNegativeSpacing() :
  22. FlowError("Flow::spacing() produced negative spacing. Did you set some extrusion width too small?") {}
  23. FlowErrorNegativeFlow::FlowErrorNegativeFlow() :
  24. FlowError("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?") {}
  25. // This static method returns a sane extrusion width default.
  26. float Flow::auto_extrusion_width(FlowRole role, float nozzle_diameter)
  27. {
  28. switch (role) {
  29. case frSupportMaterial:
  30. case frSupportMaterialInterface:
  31. case frTopSolidInfill:
  32. case frExternalPerimeter:
  33. return 1.05f * nozzle_diameter;
  34. default:
  35. case frPerimeter:
  36. case frSolidInfill:
  37. case frInfill:
  38. return 1.125f * nozzle_diameter;
  39. }
  40. }
  41. // Used by the Flow::extrusion_width() funtion to provide hints to the user on default extrusion width values,
  42. // and to provide reasonable values to the PlaceholderParser.
  43. static inline FlowRole opt_key_to_flow_role(const std::string &opt_key)
  44. {
  45. if (opt_key == "perimeter_extrusion_width" ||
  46. // or almost all the defaults:
  47. opt_key == "extrusion_width" || opt_key == "first_layer_extrusion_width")
  48. return frPerimeter;
  49. else if (opt_key == "external_perimeter_extrusion_width")
  50. return frExternalPerimeter;
  51. else if (opt_key == "infill_extrusion_width")
  52. return frInfill;
  53. else if (opt_key == "solid_infill_extrusion_width"
  54. // or the first layer infill:
  55. || opt_key == "first_layer_infill_extrusion_width")
  56. return frSolidInfill;
  57. else if (opt_key == "top_infill_extrusion_width")
  58. return frTopSolidInfill;
  59. else if (opt_key == "support_material_extrusion_width")
  60. return frSupportMaterial;
  61. else
  62. throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument");
  63. };
  64. static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key)
  65. {
  66. throw FlowErrorMissingVariable((boost::format(_u8L("Cannot calculate extrusion width for %1%: Variable \"%2%\" not accessible.")) % opt_key % dependent_opt_key).str());
  67. }
  68. // Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
  69. double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionFloatOrPercent* opt, const ConfigOptionResolver& config, const unsigned int first_printing_extruder)
  70. {
  71. assert(opt != nullptr);
  72. bool first_layer = boost::starts_with(opt_key, "first_layer_") || boost::starts_with(opt_key, "brim_");
  73. if (!first_layer && boost::starts_with(opt_key, "skirt_")) {
  74. const ConfigOptionInt* optInt = config.option<ConfigOptionInt>("skirt_height");
  75. const ConfigOptionBool* optBool = config.option<ConfigOptionBool>("draft_shield");
  76. first_layer = (optBool && optInt && optInt->value == 1 && !optBool->value);
  77. }
  78. if (opt->value == 0.) {
  79. // The role specific extrusion width value was set to zero, get a not-0 one (if possible)
  80. opt = extrusion_width_option(opt_key, config);
  81. }
  82. if (opt->percent) {
  83. // first_layer_height depends on first_printing_extruder
  84. auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
  85. if (opt_nozzle_diameters == nullptr)
  86. throw_on_missing_variable(opt_key, "nozzle_diameter");
  87. return opt->get_abs_value(float(opt_nozzle_diameters->get_at(first_printing_extruder)));
  88. }
  89. if (opt->value == 0.) {
  90. // If user left option to 0, calculate a sane default width.
  91. auto opt_nozzle_diameters = config.option<ConfigOptionFloats>("nozzle_diameter");
  92. if (opt_nozzle_diameters == nullptr)
  93. throw_on_missing_variable(opt_key, "nozzle_diameter");
  94. return auto_extrusion_width(opt_key_to_flow_role(opt_key), float(opt_nozzle_diameters->get_at(first_printing_extruder)));
  95. }
  96. return opt->value;
  97. }
  98. //used to get brim & skirt extrusion config
  99. const ConfigOptionFloatOrPercent* Flow::extrusion_width_option(std::string opt_key, const ConfigOptionResolver& config)
  100. {
  101. if (!boost::ends_with(opt_key, "_extrusion_width")) {
  102. opt_key += "_extrusion_width";
  103. }
  104. const ConfigOptionFloatOrPercent* opt = config.option<ConfigOptionFloatOrPercent>(opt_key);
  105. //brim is first_layer_extrusion_width then perimeter_extrusion_width
  106. if (!opt && boost::starts_with(opt_key, "brim")) {
  107. const ConfigOptionFloatOrPercent* optTest = config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
  108. opt = optTest;
  109. if (opt == nullptr)
  110. throw_on_missing_variable(opt_key, "first_layer_extrusion_width");
  111. if (opt->value == 0) {
  112. opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
  113. if (opt == nullptr)
  114. throw_on_missing_variable(opt_key, "perimeter_extrusion_width");
  115. }
  116. }
  117. if (opt == nullptr)
  118. throw_on_missing_variable(opt_key, opt_key.c_str());
  119. // This is the logic used for skit / brim, but not for the rest of the 1st layer.
  120. if (opt->value == 0. && boost::starts_with(opt_key, "skirt")) {
  121. // The "skirt_extrusion_width" was set to zero, try a substitute.
  122. const ConfigOptionFloatOrPercent* opt_first_layer_extrusion_width = config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
  123. const ConfigOptionInt* opt_skirt_height = config.option<ConfigOptionInt>("skirt_height");
  124. const ConfigOptionEnum<DraftShield>* opt_draft_shield = config.option<ConfigOptionEnum<DraftShield>>("draft_shield");
  125. if (opt_first_layer_extrusion_width == nullptr)
  126. throw_on_missing_variable(opt_key, "first_layer_extrusion_width");
  127. if (opt_draft_shield == nullptr)
  128. throw_on_missing_variable(opt_key, "draft_shield");
  129. if (opt_skirt_height == nullptr)
  130. throw_on_missing_variable(opt_key, "skirt_height");
  131. // The "first_layer_extrusion_width" was set to zero, try a substitute.
  132. if (opt_first_layer_extrusion_width && opt_draft_shield && opt_skirt_height && opt_first_layer_extrusion_width->value > 0 && opt_skirt_height->value == 1 && opt_draft_shield->value != DraftShield::dsDisabled)
  133. opt = opt_first_layer_extrusion_width;
  134. if (opt->value == 0) {
  135. opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
  136. if (opt == nullptr)
  137. throw_on_missing_variable(opt_key, "perimeter_extrusion_width");
  138. }
  139. }
  140. // external_perimeter_extrusion_width default is perimeter_extrusion_width
  141. //if (opt->value == 0. && boost::starts_with(opt_key, "external_perimeter_extrusion_width")) {
  142. // // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  143. // opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
  144. // if (opt == nullptr)
  145. // throw_on_missing_variable(opt_key, "perimeter_extrusion_width");
  146. //}
  147. // top_infill_extrusion_width default is solid_infill_extrusion_width
  148. //if (opt->value == 0. && boost::starts_with(opt_key, "top_infill_extrusion_width")) {
  149. // // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  150. // opt = config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
  151. // if (opt == nullptr)
  152. // throw_on_missing_variable(opt_key, "solid_infill_extrusion_width");
  153. //}
  154. if (opt->value == 0.) {
  155. // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  156. opt = config.option<ConfigOptionFloatOrPercent>("extrusion_width");
  157. if (opt == nullptr)
  158. throw_on_missing_variable(opt_key, "extrusion_width");
  159. }
  160. return opt;
  161. }
  162. //used to get brim & skirt extrusion config
  163. const ConfigOptionFloatOrPercent* Flow::extrusion_spacing_option(std::string opt_key, const ConfigOptionResolver& config)
  164. {
  165. std::string opt_key_width;
  166. if (boost::starts_with(opt_key, "skirt")) {
  167. //skirt have only width setting
  168. if (!boost::ends_with(opt_key, "_extrusion_width")) {
  169. opt_key += "_extrusion_width";
  170. }
  171. opt_key_width = opt_key;
  172. } else {//brim
  173. if (boost::ends_with(opt_key, "_extrusion_width")) {
  174. boost::replace_first(opt_key, "_width", "_spacing");
  175. }
  176. if (!boost::ends_with(opt_key, "_extrusion_spacing")) {
  177. opt_key_width = opt_key + "_extrusion_width";
  178. opt_key += "_extrusion_spacing";
  179. } else {
  180. opt_key_width = opt_key;
  181. boost::replace_first(opt_key_width, "_spacing", "_width");
  182. }
  183. }
  184. const ConfigOptionFloatOrPercent* opt = config.option<ConfigOptionFloatOrPercent>(opt_key);
  185. //brim is first_layer_extrusion_spacing then perimeter_extrusion_spacing
  186. if (!opt && boost::starts_with(opt_key, "brim_")) {
  187. const ConfigOptionFloatOrPercent* optTest = config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_spacing");
  188. opt = optTest;
  189. if (opt == nullptr)
  190. throw_on_missing_variable(opt_key, "first_layer_extrusion_spacing");
  191. if (opt->value == 0) {
  192. opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_spacing");
  193. if (opt == nullptr)
  194. throw_on_missing_variable(opt_key, "perimeter_extrusion_spacing");
  195. }
  196. }
  197. if (opt == nullptr) {
  198. opt_key = opt_key_width;
  199. opt = config.option<ConfigOptionFloatOrPercent>(opt_key_width);
  200. }
  201. if (opt == nullptr)
  202. throw_on_missing_variable(opt_key, opt_key.c_str());
  203. // This is the logic used for skit / brim, but not for the rest of the 1st layer.
  204. if (opt->value == 0. && boost::starts_with(opt_key, "skirt")) {
  205. // The "skirt_extrusion_spacing" was set to zero, try a substitute.
  206. const ConfigOptionFloatOrPercent* opt_first_layer_extrusion_spacing = config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_spacing");
  207. const ConfigOptionInt* opt_skirt_height = config.option<ConfigOptionInt>("skirt_height");
  208. const ConfigOptionEnum<DraftShield>* opt_draft_shield = config.option<ConfigOptionEnum<DraftShield>>("draft_shield");
  209. if (opt_first_layer_extrusion_spacing == nullptr)
  210. throw_on_missing_variable(opt_key, "first_layer_extrusion_spacing");
  211. if (opt_draft_shield == nullptr)
  212. throw_on_missing_variable(opt_key, "draft_shield");
  213. if (opt_skirt_height == nullptr)
  214. throw_on_missing_variable(opt_key, "skirt_height");
  215. // The "first_layer_extrusion_spacing" was set to zero, try a substitute.
  216. if (opt_first_layer_extrusion_spacing && opt_draft_shield && opt_skirt_height && opt_first_layer_extrusion_spacing->value > 0 && opt_skirt_height->value == 1 && opt_draft_shield->value != DraftShield::dsDisabled)
  217. opt = opt_first_layer_extrusion_spacing;
  218. if (opt->value == 0) {
  219. opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_spacing");
  220. if (opt == nullptr)
  221. throw_on_missing_variable(opt_key, "perimeter_extrusion_spacing");
  222. }
  223. }
  224. // external_perimeter_extrusion_spacing default is perimeter_extrusion_spacing
  225. //if (opt->value == 0. && boost::starts_with(opt_key, "external_perimeter_extrusion_spacing")) {
  226. // // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  227. // opt = config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_spacing");
  228. // if (opt == nullptr)
  229. // throw_on_missing_variable(opt_key, "perimeter_extrusion_spacing");
  230. //}
  231. // top_infill_extrusion_spacing default is solid_infill_extrusion_spacing
  232. //if (opt->value == 0. && boost::starts_with(opt_key, "top_infill_extrusion_spacing")) {
  233. // // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  234. // opt = config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_spacing");
  235. // if (opt == nullptr)
  236. // throw_on_missing_variable(opt_key, "solid_infill_extrusion_spacing");
  237. //}
  238. if (opt->value == 0.) {
  239. // The role specific extrusion width value was set to zero, try the role non-specific extrusion width.
  240. opt = config.option<ConfigOptionFloatOrPercent>("extrusion_spacing");
  241. if (opt == nullptr)
  242. throw_on_missing_variable(opt_key, "extrusion_spacing");
  243. }
  244. return opt;
  245. }
  246. // Used to provide hints to the user on default extrusion width values, and to provide reasonable values to the PlaceholderParser.
  247. double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder)
  248. {
  249. return extrusion_width(opt_key, config.option<ConfigOptionFloatOrPercent>(opt_key), config, first_printing_extruder);
  250. }
  251. Flow Flow::new_from_config(FlowRole role, const DynamicConfig& print_config, float nozzle_diameter, float layer_height, float filament_max_overlap, bool first_layer) {
  252. ConfigOptionFloatOrPercent config_width;
  253. ConfigOptionFloatOrPercent config_spacing;
  254. // Get extrusion width from configuration.
  255. float overlap = 1.f;
  256. // (might be an absolute value, or a percent value, or zero for auto)
  257. if (role == frExternalPerimeter) {
  258. config_width = print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width");
  259. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_spacing");
  260. // external peri spacing is only half spacing -> transform it into a full spacing
  261. if (!config_spacing.is_phony() && !config_spacing.value == 0) {
  262. double raw_spacing = config_spacing.get_abs_value(nozzle_diameter);
  263. config_spacing.percent = false;
  264. config_spacing.value = rounded_rectangle_extrusion_spacing(
  265. rounded_rectangle_extrusion_width_from_spacing(raw_spacing, layer_height, 0.5f),
  266. layer_height, 1.f);
  267. }
  268. overlap = (float)print_config.get_abs_value("external_perimeter_overlap", 1.0);
  269. } else if (role == frPerimeter) {
  270. config_width = print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
  271. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_spacing");
  272. overlap = (float)print_config.get_abs_value("perimeter_overlap", 1.);
  273. } else if (role == frInfill) {
  274. config_width = print_config.opt<ConfigOptionFloatOrPercent>("infill_extrusion_width");
  275. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("infill_extrusion_spacing");
  276. } else if (role == frSolidInfill) {
  277. config_width = print_config.opt<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
  278. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("solid_infill_extrusion_spacing");
  279. overlap = (float)print_config.get_abs_value("solid_infill_overlap", 1.);
  280. } else if (role == frTopSolidInfill) {
  281. config_width = print_config.opt<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
  282. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("top_infill_extrusion_spacing");
  283. overlap = (float)print_config.get_abs_value("top_solid_infill_overlap", 1.);
  284. } else {
  285. throw Slic3r::InvalidArgument("Unknown role");
  286. }
  287. if (first_layer) {
  288. auto opt_fl_width = print_config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
  289. auto opt_fli_width = print_config.opt<ConfigOptionFloatOrPercent>("first_layer_infill_extrusion_width");
  290. if ((role == frInfill || role == frSolidInfill || role == frTopSolidInfill) && opt_fli_width->is_enabled()) {
  291. config_width = opt_fli_width;
  292. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("first_layer_infill_extrusion_spacing");
  293. } else if (opt_fl_width->is_enabled()) {
  294. config_width = opt_fl_width;
  295. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("first_layer_extrusion_spacing");
  296. }
  297. }
  298. if (config_width.value == 0) {
  299. config_width = print_config.opt<ConfigOptionFloatOrPercent>("extrusion_width");
  300. config_spacing = print_config.opt<ConfigOptionFloatOrPercent>("extrusion_spacing");
  301. }
  302. // Get the configured nozzle_diameter for the extruder associated to the flow role requested.
  303. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
  304. return Flow::new_from_config_width(role, config_width, config_spacing, nozzle_diameter, layer_height,
  305. std::min(role == frTopSolidInfill ? 1.f : overlap, filament_max_overlap));
  306. //bridge ? (float)m_config.bridge_flow_ratio.get_abs_value(1) : 0.0f);
  307. }
  308. // This constructor builds a Flow object from an extrusion width config setting
  309. // and other context properties.
  310. Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent& width, const ConfigOptionFloatOrPercent& spacing, float nozzle_diameter, float height, float spacing_ratio, float bridge_flow_ratio)
  311. {
  312. if (height <= 0)
  313. throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
  314. float w = 0.f;
  315. if (bridge_flow_ratio > 0) {
  316. // If bridge flow was requested, calculate the bridge width.
  317. height = w = (bridge_flow_ratio == 1.) ?
  318. // optimization to avoid sqrt()
  319. nozzle_diameter :
  320. sqrt(bridge_flow_ratio) * nozzle_diameter;
  321. } else {
  322. if (!width.is_phony()) {
  323. if (!width.percent && width.value <= 0.) {
  324. // If user left option to 0, calculate a sane default width.
  325. w = auto_extrusion_width(role, nozzle_diameter);
  326. } else {
  327. // If user set a manual value, use it.
  328. w = float(width.get_abs_value(nozzle_diameter));
  329. }
  330. } else {
  331. if (!spacing.percent && spacing.value == 0.) {
  332. // If user left option to 0, calculate a sane default width.
  333. w = auto_extrusion_width(role, nozzle_diameter);
  334. } else {
  335. // If user set a manual value, use it.
  336. return new_from_spacing(float(spacing.get_abs_value(nozzle_diameter)), nozzle_diameter, height, spacing_ratio, false);
  337. }
  338. }
  339. }
  340. return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height, spacing_ratio), nozzle_diameter, spacing_ratio, bridge_flow_ratio > 0);
  341. }
  342. // This constructor builds a Flow object from an extrusion width config setting
  343. // and other context properties.
  344. Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, const ConfigOptionFloatOrPercent& spacing, float nozzle_diameter, float height, float spacing_ratio)
  345. {
  346. if (height <= 0)
  347. throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
  348. float w;
  349. if (!width.is_phony()) {
  350. if (!width.percent && width.value == 0.) {
  351. // If user left option to 0, calculate a sane default width.
  352. w = auto_extrusion_width(role, nozzle_diameter);
  353. } else {
  354. // If user set a manual value, use it.
  355. w = float(width.get_abs_value(nozzle_diameter));
  356. }
  357. } else {
  358. if (!spacing.percent && spacing.value == 0.) {
  359. // If user left option to 0, calculate a sane default width.
  360. w = auto_extrusion_width(role, nozzle_diameter);
  361. } else {
  362. // If user set a manual value, use it.
  363. return new_from_spacing(float(spacing.get_abs_value(nozzle_diameter)), nozzle_diameter, height, spacing_ratio, false);
  364. }
  365. }
  366. return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height, spacing_ratio), nozzle_diameter, spacing_ratio, false);
  367. }
  368. // This constructor builds a Flow object from a given centerline spacing.
  369. Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, float spacing_ratio, bool bridge)
  370. {
  371. // we need layer height unless it's a bridge
  372. if (height <= 0 && !bridge)
  373. throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
  374. // Calculate width from spacing.
  375. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
  376. // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
  377. float width = float(bridge ?
  378. (spacing /*- BRIDGE_EXTRA_SPACING_MULT (0.125) * nozzle_diameter*/) :
  379. rounded_rectangle_extrusion_width_from_spacing(spacing, height, spacing_ratio));
  380. return Flow(width, bridge ? width : height, spacing, nozzle_diameter, bridge ? 0 : spacing_ratio, bridge);
  381. }
  382. // This constructor builds a Flow object from a given centerline spacing.
  383. Flow Flow::new_from_width(float width, float nozzle_diameter, float height, float spacing_ratio, bool bridge)
  384. {
  385. // we need layer height unless it's a bridge
  386. if (height <= 0 && !bridge)
  387. throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_width()");
  388. float spacing = float(bridge ?
  389. (width /*- BRIDGE_EXTRA_SPACING_MULT (0.125) * nozzle_diameter*/) :
  390. rounded_rectangle_extrusion_spacing(width, height, spacing_ratio));
  391. return Flow(width, bridge ? width : height, spacing, nozzle_diameter, bridge ? 0 : spacing_ratio, bridge);
  392. }
  393. // Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
  394. Flow Flow::with_spacing(float new_spacing) const
  395. {
  396. Flow out = *this;
  397. if (m_bridge) {
  398. // Diameter of the rounded extrusion.
  399. assert(m_width == m_height);
  400. float gap = m_spacing - m_width;
  401. auto new_diameter = new_spacing - gap;
  402. out.m_width = out.m_height = new_diameter;
  403. } else {
  404. assert(m_width >= m_height);
  405. out.m_width += new_spacing - m_spacing;
  406. if (out.m_width < out.m_height)
  407. throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()");
  408. }
  409. out.m_spacing = new_spacing;
  410. return out;
  411. }
  412. Flow Flow::with_spacing_ratio_from_width(float new_spacing_ratio) const
  413. {
  414. return Flow::new_from_width(m_width, m_nozzle_diameter, m_height, new_spacing_ratio, m_bridge);
  415. }
  416. // This method returns the centerline spacing between two adjacent extrusions
  417. // having the same extrusion width (and other properties).
  418. float Flow::spacing() const
  419. {
  420. #ifdef HAS_PERIMETER_LINE_OVERLAP
  421. if (this->bridge)
  422. return this->width + BRIDGE_EXTRA_SPACING;
  423. // rectangle with semicircles at the ends
  424. float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI) * spacing_ratio;
  425. float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing);
  426. #else
  427. float res = float(this->bridge() ? (this->width() /*+ BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter*/) : (this->width() - this->height() * (1. - 0.25 * PI) * m_spacing_ratio));
  428. #endif
  429. // assert(res > 0.f);
  430. if (res <= 0.f)
  431. throw FlowErrorNegativeSpacing();
  432. return res;
  433. }
  434. // Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
  435. Flow Flow::with_cross_section(float area_new) const
  436. {
  437. assert(! m_bridge);
  438. assert(m_width >= m_height);
  439. // Adjust for bridge_flow_ratio, maintain the extrusion spacing.
  440. float area = this->mm3_per_mm();
  441. if (area_new > area + EPSILON) {
  442. // Increasing the flow rate.
  443. float new_full_spacing = area_new / m_height;
  444. if (new_full_spacing > m_spacing) {
  445. // Filling up the spacing without an air gap. Grow the extrusion in height.
  446. float height = area_new / m_spacing;
  447. return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height, m_spacing_ratio), height, m_spacing, m_nozzle_diameter, m_spacing_ratio, false);
  448. } else {
  449. return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height, m_spacing_ratio));
  450. }
  451. } else if (area_new < area - EPSILON) {
  452. // Decreasing the flow rate.
  453. float width_new = m_width - (area - area_new) / m_height;
  454. assert(width_new > 0);
  455. if (width_new > m_height) {
  456. // Shrink the extrusion width.
  457. return this->with_width(width_new);
  458. } else {
  459. // Create a rounded extrusion.
  460. auto dmr = 2.0 * float(sqrt(area_new / M_PI));
  461. return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, m_spacing_ratio, false);
  462. }
  463. } else
  464. return *this;
  465. }
  466. // This method returns the centerline spacing between an extrusion using this
  467. // flow and another one using another flow.
  468. // this->spacing(other) shall return the same value as other.spacing(*this)
  469. float Flow::spacing(const Flow &other) const
  470. {
  471. assert(this->height() == other.height());
  472. assert(this->bridge() == other.bridge());
  473. float res = float(this->bridge() || other.bridge() ?
  474. 0.5 * this->width() + 0.5 * other.width() :
  475. 0.5 * this->spacing() + 0.5 * other.spacing());
  476. // assert(res > 0.f);
  477. if (res <= 0.f)
  478. throw FlowErrorNegativeSpacing();
  479. return res;
  480. }
  481. float Flow::rounded_rectangle_extrusion_spacing(float width, float height, float m_spacing_ratio)
  482. {
  483. #ifdef HAS_PERIMETER_LINE_OVERLAP
  484. return (spacing - PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI) * spacing_ratio);
  485. #else
  486. if (width == height && width == 0)
  487. return 0.f;
  488. float out = width - height * float(1. - 0.25 * PI) * m_spacing_ratio;
  489. if (out <= 0.f)
  490. throw FlowErrorNegativeSpacing();
  491. return out;
  492. #endif
  493. }
  494. float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height, float spacing_ratio)
  495. {
  496. #ifdef HAS_PERIMETER_LINE_OVERLAP
  497. return (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI) * spacing_ratio);
  498. #else
  499. return float(spacing + height * (1. - 0.25 * PI) * spacing_ratio);
  500. #endif
  501. }
  502. float Flow::bridge_extrusion_spacing(float dmr)
  503. {
  504. return dmr;// +BRIDGE_EXTRA_SPACING;
  505. }
  506. // This method returns extrusion volume per head move unit.
  507. double Flow::mm3_per_mm() const
  508. {
  509. float res = m_bridge ?
  510. // Area of a circle with dmr of this->width.
  511. float((m_width * m_width) * 0.25 * PI) :
  512. // Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
  513. float(m_height * (m_width - m_height * (1. - 0.25 * PI)));
  514. //assert(res > 0.);
  515. if (res <= 0.)
  516. throw FlowErrorNegativeFlow();
  517. return res;
  518. }
  519. Flow support_material_flow(const PrintObject* object, float layer_height)
  520. {
  521. int extruder_id = object->config().support_material_extruder.value - 1;
  522. if (extruder_id < 0) {
  523. if (!object->layers().empty()) {
  524. extruder_id = object->layers().front()->get_region(0)->region().config().infill_extruder - 1;
  525. } else {
  526. extruder_id = object->default_region_config(object->print()->default_region_config()).infill_extruder - 1;
  527. }
  528. }
  529. double nzd = object->print()->config().nozzle_diameter.get_at(extruder_id);
  530. const ConfigOptionFloatOrPercent& width = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width;
  531. const ConfigOptionFloatOrPercent& spacing = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_spacing;
  532. float max_height = 0.f;
  533. if (!width.percent && width.value <= 0.) {
  534. // If user left option to 0, calculate a sane default width.
  535. max_height = Flow::auto_extrusion_width(frSupportMaterialInterface, nzd);
  536. } else {
  537. // If user set a manual value, use it.
  538. max_height = float(width.get_abs_value(nzd));
  539. }
  540. if (layer_height <= 0) { // get default layer height for material interface
  541. layer_height = object->config().support_material_layer_height.get_abs_value(nzd);
  542. if (layer_height == 0) {
  543. layer_height = object->print()->config().max_layer_height.get_abs_value(extruder_id, nzd);
  544. if (layer_height == 0 || !object->print()->config().max_layer_height.is_enabled()) {
  545. layer_height = nzd * 0.75;
  546. }
  547. }
  548. }
  549. layer_height = std::min(layer_height, max_height);
  550. return Flow::new_from_config_width(
  551. frSupportMaterial,
  552. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
  553. width,
  554. spacing,
  555. // if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
  556. float(nzd),
  557. layer_height,
  558. extruder_id < 0 ? 1 : object->config().get_computed_value("filament_max_overlap", extruder_id), //if can get an extruder, then use its param, or use full overlap if we don't know the extruder id.
  559. // bridge_flow_ratio
  560. 0.f);
  561. }
  562. Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
  563. {
  564. const PrintConfig &print_config = object->print()->config();
  565. const auto& width = object->config().first_layer_extrusion_width.is_enabled() ? object->config().first_layer_extrusion_width : object->config().support_material_extrusion_width;
  566. const auto& spacing = object->config().first_layer_extrusion_width.is_enabled() ? object->config().first_layer_extrusion_spacing : object->config().support_material_extrusion_width;
  567. float slice_height = layer_height;
  568. if (layer_height <= 0.f && !object->print()->config().nozzle_diameter.empty()){
  569. slice_height = (float)object->get_first_layer_height();
  570. }
  571. int extruder_id = object->config().support_material_extruder.value -1;
  572. if (extruder_id < 0) {
  573. if (!object->layers().empty()) {
  574. extruder_id = object->layers().front()->get_region(0)->region().config().infill_extruder - 1;
  575. } else {
  576. extruder_id = object->default_region_config(object->print()->default_region_config()).infill_extruder - 1;
  577. }
  578. }
  579. return Flow::new_from_config_width(
  580. frSupportMaterial,
  581. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
  582. (width.value > 0) ? width : object->config().extrusion_width,
  583. (spacing.value > 0) ? spacing : object->config().extrusion_spacing, // can be used if first_layer_extrusion_width is phony
  584. float(print_config.nozzle_diameter.get_at(extruder_id)),
  585. slice_height,
  586. extruder_id < 0 ? 1 : object->config().get_computed_value("filament_max_overlap", extruder_id), //if can get an extruder, then use its param, or use full overlap if we don't know the extruder id.
  587. // bridge_flow_ratio
  588. 0.f);
  589. }
  590. Flow support_material_interface_flow(const PrintObject* object, float layer_height)
  591. {
  592. int extruder_id = object->config().support_material_interface_extruder.value - 1;
  593. if (extruder_id < 0) {
  594. assert(!object->layers().empty() || object->num_printing_regions() > 0);
  595. if (!object->layers().empty()) {
  596. extruder_id = object->layers().front()->get_region(0)->region().config().infill_extruder - 1;
  597. } else {
  598. extruder_id = object->default_region_config(object->print()->default_region_config()).infill_extruder - 1;
  599. }
  600. }
  601. double nzd = object->print()->config().nozzle_diameter.get_at(extruder_id);
  602. const ConfigOptionFloatOrPercent& width = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width;
  603. const ConfigOptionFloatOrPercent& spacing = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_spacing;
  604. float max_height = 0.f;
  605. if (!width.percent && width.value <= 0.) {
  606. // If user left option to 0, calculate a sane default width.
  607. max_height = Flow::auto_extrusion_width(frSupportMaterialInterface, nzd);
  608. } else {
  609. // If user set a manual value, use it.
  610. max_height = float(width.get_abs_value(nzd));
  611. }
  612. if (layer_height <= 0) { // get default layer height for material interface
  613. layer_height = object->config().support_material_interface_layer_height.get_abs_value(nzd);
  614. if (layer_height == 0) {
  615. layer_height = object->print()->config().max_layer_height.get_abs_value(extruder_id, nzd);
  616. if (layer_height == 0 || !object->print()->config().max_layer_height.is_enabled()) {
  617. layer_height = nzd * 0.75;
  618. }
  619. }
  620. }
  621. layer_height = std::min(layer_height, max_height);
  622. return Flow::new_from_config_width(
  623. frSupportMaterialInterface,
  624. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
  625. width,
  626. spacing,
  627. // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
  628. float(nzd),
  629. layer_height,
  630. extruder_id < 0 ? 1 : object->config().get_computed_value("filament_max_overlap", extruder_id), //if can get an extruder, then use its param, or use full overlap if we don't know the extruder id.
  631. // bridge_flow_ratio
  632. 0.f);
  633. }
  634. Flow raft_flow(const PrintObject* object, float layer_height)
  635. {
  636. int extruder_id = object->config().support_material_interface_extruder.value - 1;
  637. if (extruder_id < 0) {
  638. extruder_id = object->layers().front()->get_region(0)->region().config().perimeter_extruder - 1;
  639. }
  640. double nzd = object->print()->config().nozzle_diameter.get_at(extruder_id);
  641. const ConfigOptionFloatOrPercent& width = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width;
  642. const ConfigOptionFloatOrPercent& spacing = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_spacing;
  643. float max_height = 0.f;
  644. if (!width.percent && width.value <= 0.) {
  645. // If user left option to 0, calculate a sane default width.
  646. max_height = Flow::auto_extrusion_width(frSupportMaterial, nzd);
  647. } else {
  648. // If user set a manual value, use it.
  649. max_height = float(width.get_abs_value(nzd));
  650. }
  651. if (layer_height <= 0) { // get default layer height for material interface
  652. layer_height = object->config().raft_interface_layer_height.get_abs_value(nzd);
  653. if (layer_height == 0) {
  654. layer_height = object->print()->config().max_layer_height.get_abs_value(extruder_id, nzd);
  655. if (layer_height == 0 || !object->print()->config().max_layer_height.is_enabled()) {
  656. layer_height = nzd * 0.75;
  657. }
  658. }
  659. }
  660. layer_height = std::min(layer_height, max_height);
  661. return Flow::new_from_config_width(
  662. frSupportMaterial,
  663. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
  664. width,
  665. spacing,
  666. // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
  667. float(nzd),
  668. layer_height,
  669. extruder_id < 0 ? 1 : object->config().get_computed_value("filament_max_overlap", extruder_id), //if can get an extruder, then use its param, or use full overlap if we don't know the extruder id.
  670. // bridge_flow_ratio
  671. 0.f);
  672. }
  673. Flow raft_interface_flow(const PrintObject* object, float layer_height)
  674. {
  675. int extruder_id = object->config().support_material_interface_extruder.value - 1;
  676. if (extruder_id < 0) {
  677. extruder_id = object->layers().front()->get_region(0)->region().config().infill_extruder - 1;
  678. }
  679. double nzd = object->print()->config().nozzle_diameter.get_at(extruder_id);
  680. const ConfigOptionFloatOrPercent& width = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width;
  681. const ConfigOptionFloatOrPercent& spacing = (object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_spacing;
  682. float max_height = 0.f;
  683. if (!width.percent && width.value <= 0.) {
  684. // If user left option to 0, calculate a sane default width.
  685. max_height = Flow::auto_extrusion_width(frSupportMaterialInterface, nzd);
  686. } else {
  687. // If user set a manual value, use it.
  688. max_height = float(width.get_abs_value(nzd));
  689. }
  690. if (layer_height <= 0) { // get default layer height for material interface
  691. layer_height = object->config().raft_interface_layer_height.get_abs_value(nzd);
  692. if (layer_height == 0) {
  693. layer_height = object->print()->config().max_layer_height.get_abs_value(extruder_id, nzd);
  694. if (layer_height == 0 || !object->print()->config().max_layer_height.is_enabled()) {
  695. layer_height = nzd * 0.75;
  696. }
  697. }
  698. }
  699. layer_height = std::min(layer_height, max_height);
  700. return Flow::new_from_config_width(
  701. frSupportMaterialInterface,
  702. // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
  703. width,
  704. spacing,
  705. // if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
  706. float(nzd),
  707. layer_height,
  708. extruder_id < 0 ? 1 : object->config().get_computed_value("filament_max_overlap", extruder_id), //if can get an extruder, then use its param, or use full overlap if we don't know the extruder id.
  709. // bridge_flow_ratio
  710. 0.f);
  711. }
  712. }