GCodeViewer.cpp 205 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301
  1. #include "libslic3r/libslic3r.h"
  2. #include "GCodeViewer.hpp"
  3. #include "libslic3r/BuildVolume.hpp"
  4. #include "libslic3r/Print.hpp"
  5. #include "libslic3r/Geometry.hpp"
  6. #include "libslic3r/Model.hpp"
  7. #include "libslic3r/Utils.hpp"
  8. #include "libslic3r/LocalesUtils.hpp"
  9. #include "libslic3r/PresetBundle.hpp"
  10. #include "slic3r/GUI/format.hpp"
  11. #include "GUI_App.hpp"
  12. #include "MainFrame.hpp"
  13. #include "Plater.hpp"
  14. #include "Camera.hpp"
  15. #include "I18N.hpp"
  16. #include "GUI_Utils.hpp"
  17. #include "GUI.hpp"
  18. #include "DoubleSlider.hpp"
  19. #include "GLCanvas3D.hpp"
  20. #include "GLToolbar.hpp"
  21. #include "GUI_Preview.hpp"
  22. #include "GUI_ObjectManipulation.hpp"
  23. #include <imgui/imgui_internal.h>
  24. #include <GL/glew.h>
  25. #include <boost/log/trivial.hpp>
  26. #include <boost/algorithm/string/split.hpp>
  27. #include <boost/nowide/cstdio.hpp>
  28. #include <boost/nowide/fstream.hpp>
  29. #include <wx/progdlg.h>
  30. #include <wx/numformatter.h>
  31. #include <array>
  32. #include <algorithm>
  33. #include <chrono>
  34. namespace Slic3r {
  35. namespace GUI {
  36. static unsigned char buffer_id(EMoveType type) {
  37. return static_cast<unsigned char>(type) - static_cast<unsigned char>(EMoveType::Retract);
  38. }
  39. static EMoveType buffer_type(unsigned char id) {
  40. return static_cast<EMoveType>(static_cast<unsigned char>(EMoveType::Retract) + id);
  41. }
  42. // Round to a bin with minimum two digits resolution.
  43. // Equivalent to conversion to string with sprintf(buf, "%.2g", value) and conversion back to float, but faster.
  44. static float round_to_bin(const float value)
  45. {
  46. // assert(value >= 0);
  47. constexpr float const scale [5] = { 100.f, 1000.f, 10000.f, 100000.f, 1000000.f };
  48. constexpr float const invscale [5] = { 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f };
  49. constexpr float const threshold[5] = { 0.095f, 0.0095f, 0.00095f, 0.000095f, 0.0000095f };
  50. // Scaling factor, pointer to the tables above.
  51. int i = 0;
  52. // While the scaling factor is not yet large enough to get two integer digits after scaling and rounding:
  53. for (; value < threshold[i] && i < 4; ++ i) ;
  54. // At least on MSVC std::round() calls a complex function, which is pretty expensive.
  55. // our fast_round_up is much cheaper and it could be inlined.
  56. // return std::round(value * scale[i]) * invscale[i];
  57. double a = value * scale[i];
  58. assert(std::abs(a) < double(std::numeric_limits<int64_t>::max()));
  59. return fast_round_up<int64_t>(a) * invscale[i];
  60. }
  61. void GCodeViewer::VBuffer::reset()
  62. {
  63. // release gpu memory
  64. if (!vbos.empty()) {
  65. glsafe(::glDeleteBuffers(static_cast<GLsizei>(vbos.size()), static_cast<const GLuint*>(vbos.data())));
  66. vbos.clear();
  67. }
  68. #if ENABLE_GL_CORE_PROFILE
  69. if (!vaos.empty()) {
  70. glsafe(::glDeleteVertexArrays(static_cast<GLsizei>(vaos.size()), static_cast<const GLuint*>(vaos.data())));
  71. vaos.clear();
  72. }
  73. #endif // ENABLE_GL_CORE_PROFILE
  74. sizes.clear();
  75. count = 0;
  76. }
  77. void GCodeViewer::InstanceVBuffer::Ranges::reset()
  78. {
  79. for (Range& range : ranges) {
  80. // release gpu memory
  81. if (range.vbo > 0)
  82. glsafe(::glDeleteBuffers(1, &range.vbo));
  83. }
  84. ranges.clear();
  85. }
  86. void GCodeViewer::InstanceVBuffer::reset()
  87. {
  88. s_ids.clear();
  89. buffer.clear();
  90. render_ranges.reset();
  91. }
  92. void GCodeViewer::IBuffer::reset()
  93. {
  94. // release gpu memory
  95. if (ibo > 0) {
  96. glsafe(::glDeleteBuffers(1, &ibo));
  97. ibo = 0;
  98. }
  99. vbo = 0;
  100. count = 0;
  101. }
  102. bool GCodeViewer::Path::matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const
  103. {
  104. auto matches_percent = [](float value1, float value2, float max_percent) {
  105. return std::abs(value2 - value1) / value1 <= max_percent;
  106. };
  107. switch (move.type)
  108. {
  109. case EMoveType::Tool_change:
  110. case EMoveType::Color_change:
  111. case EMoveType::Pause_Print:
  112. case EMoveType::Custom_GCode:
  113. case EMoveType::Retract:
  114. case EMoveType::Unretract:
  115. case EMoveType::Seam:
  116. case EMoveType::Extrude: {
  117. // use rounding to reduce the number of generated paths
  118. if (account_for_volumetric_rate)
  119. return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
  120. move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
  121. height == round_to_bin(move.height) && width == round_to_bin(move.width) &&
  122. matches_percent(volumetric_rate, move.volumetric_rate(), 0.001f);
  123. else
  124. return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role &&
  125. move.position.z() <= sub_paths.front().first.position.z() && feedrate == move.feedrate && fan_speed == move.fan_speed &&
  126. height == round_to_bin(move.height) && width == round_to_bin(move.width);
  127. }
  128. case EMoveType::Travel: {
  129. return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id;
  130. }
  131. default: { return false; }
  132. }
  133. }
  134. void GCodeViewer::TBuffer::Model::reset()
  135. {
  136. instances.reset();
  137. }
  138. void GCodeViewer::TBuffer::reset()
  139. {
  140. vertices.reset();
  141. for (IBuffer& buffer : indices) {
  142. buffer.reset();
  143. }
  144. indices.clear();
  145. paths.clear();
  146. render_paths.clear();
  147. model.reset();
  148. }
  149. void GCodeViewer::TBuffer::add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id)
  150. {
  151. Path::Endpoint endpoint = { b_id, i_id, s_id, move.position };
  152. // use rounding to reduce the number of generated paths
  153. paths.push_back({ move.type, move.extrusion_role, move.delta_extruder,
  154. round_to_bin(move.height), round_to_bin(move.width),
  155. move.feedrate, move.fan_speed, move.temperature,
  156. move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } });
  157. }
  158. void GCodeViewer::COG::render()
  159. {
  160. if (!m_visible)
  161. return;
  162. init();
  163. GLShaderProgram* shader = wxGetApp().get_shader("toolpaths_cog");
  164. if (shader == nullptr)
  165. return;
  166. shader->start_using();
  167. glsafe(::glDisable(GL_DEPTH_TEST));
  168. const Camera& camera = wxGetApp().plater()->get_camera();
  169. Transform3d model_matrix = Geometry::translation_transform(cog());
  170. if (m_fixed_size) {
  171. const double inv_zoom = camera.get_inv_zoom();
  172. model_matrix = model_matrix * Geometry::scale_transform(inv_zoom);
  173. }
  174. const Transform3d& view_matrix = camera.get_view_matrix();
  175. shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
  176. shader->set_uniform("projection_matrix", camera.get_projection_matrix());
  177. const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
  178. shader->set_uniform("view_normal_matrix", view_normal_matrix);
  179. m_model.render();
  180. shader->stop_using();
  181. ////Show ImGui window
  182. //static float last_window_width = 0.0f;
  183. //static size_t last_text_length = 0;
  184. //ImGuiWrapper& imgui = *wxGetApp().imgui();
  185. //const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
  186. //imgui.set_next_window_pos(0.5f * static_cast<float>(cnv_size.get_width()), 0.0f, ImGuiCond_Always, 0.5f, 0.0f);
  187. //ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
  188. //ImGui::SetNextWindowBgAlpha(0.25f);
  189. //imgui.begin(std::string("COG"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
  190. //imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Center of mass") + ":");
  191. //ImGui::SameLine();
  192. //char buf[1024];
  193. //const Vec3d position = cog();
  194. //sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z());
  195. //imgui.text(std::string(buf));
  196. //// force extra frame to automatically update window size
  197. //const float width = ImGui::GetWindowWidth();
  198. //const size_t length = strlen(buf);
  199. //if (width != last_window_width || length != last_text_length) {
  200. // last_window_width = width;
  201. // last_text_length = length;
  202. // imgui.set_requires_extra_frame();
  203. //}
  204. //imgui.end();
  205. //ImGui::PopStyleVar();
  206. }
  207. float GCodeViewer::Extrusions::Range::step_size(EType type) const
  208. {
  209. switch (type)
  210. {
  211. default:
  212. case EType::Linear: { return (max > min) ? (max - min) / (static_cast<float>(Range_Colors.size()) - 1.0f) : 0.0f; }
  213. case EType::Logarithmic: { return (max > min && min > 0.0f) ? ::log(max / min) / (static_cast<float>(Range_Colors.size()) - 1.0f) : 0.0f; }
  214. }
  215. }
  216. ColorRGBA GCodeViewer::Extrusions::Range::get_color_at(float value, EType type) const
  217. {
  218. // Input value scaled to the colors range
  219. float global_t = 0.0f;
  220. const float step = step_size(type);
  221. if (step > 0.0f) {
  222. switch (type)
  223. {
  224. default:
  225. case EType::Linear: { global_t = (value > min) ? (value - min) / step : 0.0f; break; }
  226. case EType::Logarithmic: { global_t = (value > min && min > 0.0f) ? ::log(value / min) / step : 0.0f; break; }
  227. }
  228. }
  229. const size_t color_max_idx = Range_Colors.size() - 1;
  230. // Compute the two colors just below (low) and above (high) the input value
  231. const size_t color_low_idx = std::clamp<size_t>(static_cast<size_t>(global_t), 0, color_max_idx);
  232. const size_t color_high_idx = std::clamp<size_t>(color_low_idx + 1, 0, color_max_idx);
  233. // Interpolate between the low and high colors to find exactly which color the input value should get
  234. return lerp(Range_Colors[color_low_idx], Range_Colors[color_high_idx], global_t - static_cast<float>(color_low_idx));
  235. }
  236. GCodeViewer::SequentialRangeCap::~SequentialRangeCap() {
  237. if (ibo > 0)
  238. glsafe(::glDeleteBuffers(1, &ibo));
  239. }
  240. void GCodeViewer::SequentialRangeCap::reset() {
  241. if (ibo > 0)
  242. glsafe(::glDeleteBuffers(1, &ibo));
  243. buffer = nullptr;
  244. ibo = 0;
  245. #if ENABLE_GL_CORE_PROFILE
  246. vao = 0;
  247. #endif // ENABLE_GL_CORE_PROFILE
  248. vbo = 0;
  249. color = { 0.0f, 0.0f, 0.0f, 1.0f };
  250. }
  251. void GCodeViewer::SequentialView::Marker::init()
  252. {
  253. m_model.init_from(stilized_arrow(16, 2.0f, 4.0f, 1.0f, 8.0f));
  254. m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f });
  255. }
  256. void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position)
  257. {
  258. m_world_position = position;
  259. m_world_transform = (Geometry::translation_transform((position + m_z_offset * Vec3f::UnitZ()).cast<double>()) *
  260. Geometry::translation_transform(m_model.get_bounding_box().size().z() * Vec3d::UnitZ()) * Geometry::rotation_transform({ M_PI, 0.0, 0.0 })).cast<float>();
  261. }
  262. void GCodeViewer::SequentialView::Marker::render()
  263. {
  264. if (!m_visible)
  265. return;
  266. GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
  267. if (shader == nullptr)
  268. return;
  269. glsafe(::glEnable(GL_BLEND));
  270. glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
  271. shader->start_using();
  272. shader->set_uniform("emission_factor", 0.0f);
  273. const Camera& camera = wxGetApp().plater()->get_camera();
  274. const Transform3d& view_matrix = camera.get_view_matrix();
  275. const Transform3d model_matrix = m_world_transform.cast<double>();
  276. shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
  277. shader->set_uniform("projection_matrix", camera.get_projection_matrix());
  278. const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
  279. shader->set_uniform("view_normal_matrix", view_normal_matrix);
  280. m_model.render();
  281. shader->stop_using();
  282. glsafe(::glDisable(GL_BLEND));
  283. static float last_window_width = 0.0f;
  284. static size_t last_text_length = 0;
  285. ImGuiWrapper& imgui = *wxGetApp().imgui();
  286. const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
  287. imgui.set_next_window_pos(0.5f * static_cast<float>(cnv_size.get_width()), static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 0.5f, 1.0f);
  288. ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
  289. ImGui::SetNextWindowBgAlpha(0.25f);
  290. imgui.begin(std::string("ToolPosition"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
  291. imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Tool position") + ":");
  292. ImGui::SameLine();
  293. char buf[1024];
  294. const Vec3f position = m_world_position + m_world_offset;
  295. sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", position.x(), position.y(), position.z());
  296. imgui.text(std::string(buf));
  297. // force extra frame to automatically update window size
  298. const float width = ImGui::GetWindowWidth();
  299. const size_t length = strlen(buf);
  300. if (width != last_window_width || length != last_text_length) {
  301. last_window_width = width;
  302. last_text_length = length;
  303. imgui.set_requires_extra_frame();
  304. }
  305. imgui.end();
  306. ImGui::PopStyleVar();
  307. }
  308. void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector<size_t>& lines_ends)
  309. {
  310. assert(! m_file.is_open());
  311. if (m_file.is_open())
  312. return;
  313. m_filename = filename;
  314. m_lines_ends = lines_ends;
  315. m_selected_line_id = 0;
  316. m_last_lines_size = 0;
  317. try
  318. {
  319. m_file.open(boost::filesystem::path(m_filename));
  320. }
  321. catch (...)
  322. {
  323. BOOST_LOG_TRIVIAL(error) << "Unable to map file " << m_filename << ". Cannot show G-code window.";
  324. reset();
  325. }
  326. }
  327. void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, uint64_t curr_line_id) const
  328. {
  329. auto update_lines = [this](uint64_t start_id, uint64_t end_id) {
  330. std::vector<Line> ret;
  331. ret.reserve(end_id - start_id + 1);
  332. for (uint64_t id = start_id; id <= end_id; ++id) {
  333. // read line from file
  334. const size_t start = id == 1 ? 0 : m_lines_ends[id - 2];
  335. const size_t len = m_lines_ends[id - 1] - start;
  336. std::string gline(m_file.data() + start, len);
  337. std::string command;
  338. std::string parameters;
  339. std::string comment;
  340. // extract comment
  341. std::vector<std::string> tokens;
  342. boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on);
  343. command = tokens.front();
  344. if (tokens.size() > 1)
  345. comment = ";" + tokens.back();
  346. // extract gcode command and parameters
  347. if (!command.empty()) {
  348. boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
  349. command = tokens.front();
  350. if (tokens.size() > 1) {
  351. for (size_t i = 1; i < tokens.size(); ++i) {
  352. parameters += " " + tokens[i];
  353. }
  354. }
  355. }
  356. ret.push_back({ command, parameters, comment });
  357. }
  358. return ret;
  359. };
  360. static const ImVec4 LINE_NUMBER_COLOR = ImGuiWrapper::COL_ORANGE_LIGHT;
  361. static const ImVec4 SELECTION_RECT_COLOR = ImGuiWrapper::COL_ORANGE_DARK;
  362. static const ImVec4 COMMAND_COLOR = { 0.8f, 0.8f, 0.0f, 1.0f };
  363. static const ImVec4 PARAMETERS_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f };
  364. static const ImVec4 COMMENT_COLOR = { 0.7f, 0.7f, 0.7f, 1.0f };
  365. static const ImVec4 ELLIPSIS_COLOR = { 0.0f, 0.7f, 0.0f, 1.0f };
  366. if (!m_visible || m_filename.empty() || m_lines_ends.empty() || curr_line_id == 0)
  367. return;
  368. // window height
  369. const float wnd_height = bottom - top;
  370. // number of visible lines
  371. const float text_height = ImGui::CalcTextSize("0").y;
  372. const ImGuiStyle& style = ImGui::GetStyle();
  373. const uint64_t lines_count = static_cast<uint64_t>((wnd_height - 2.0f * style.WindowPadding.y + style.ItemSpacing.y) / (text_height + style.ItemSpacing.y));
  374. if (lines_count == 0)
  375. return;
  376. // visible range
  377. const uint64_t half_lines_count = lines_count / 2;
  378. uint64_t start_id = (curr_line_id >= half_lines_count) ? curr_line_id - half_lines_count : 0;
  379. uint64_t end_id = start_id + lines_count - 1;
  380. if (end_id >= static_cast<uint64_t>(m_lines_ends.size())) {
  381. end_id = static_cast<uint64_t>(m_lines_ends.size()) - 1;
  382. start_id = end_id - lines_count + 1;
  383. }
  384. // updates list of lines to show, if needed
  385. if (m_selected_line_id != curr_line_id || m_last_lines_size != end_id - start_id + 1) {
  386. try
  387. {
  388. *const_cast<std::vector<Line>*>(&m_lines) = update_lines(start_id, end_id);
  389. }
  390. catch (...)
  391. {
  392. BOOST_LOG_TRIVIAL(error) << "Error while loading from file " << m_filename << ". Cannot show G-code window.";
  393. return;
  394. }
  395. *const_cast<uint64_t*>(&m_selected_line_id) = curr_line_id;
  396. *const_cast<size_t*>(&m_last_lines_size) = m_lines.size();
  397. }
  398. // line number's column width
  399. const float id_width = ImGui::CalcTextSize(std::to_string(end_id).c_str()).x;
  400. ImGuiWrapper& imgui = *wxGetApp().imgui();
  401. auto add_item_to_line = [&imgui](const std::string& txt, const ImVec4& color, float spacing, size_t& current_length) {
  402. static const size_t LENGTH_THRESHOLD = 60;
  403. if (txt.empty())
  404. return false;
  405. std::string out_text = txt;
  406. bool reduced = false;
  407. if (current_length + out_text.length() > LENGTH_THRESHOLD) {
  408. out_text = out_text.substr(0, LENGTH_THRESHOLD - current_length);
  409. reduced = true;
  410. }
  411. current_length += out_text.length();
  412. ImGui::SameLine(0.0f, spacing);
  413. ImGui::PushStyleColor(ImGuiCol_Text, color);
  414. imgui.text(out_text);
  415. ImGui::PopStyleColor();
  416. if (reduced) {
  417. ImGui::SameLine(0.0f, 0.0f);
  418. ImGui::PushStyleColor(ImGuiCol_Text, ELLIPSIS_COLOR);
  419. imgui.text("...");
  420. ImGui::PopStyleColor();
  421. }
  422. return reduced;
  423. };
  424. imgui.set_next_window_pos(0.0f, top, ImGuiCond_Always, 0.0f, 0.0f);
  425. imgui.set_next_window_size(0.0f, wnd_height, ImGuiCond_Always);
  426. ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
  427. ImGui::SetNextWindowBgAlpha(0.6f);
  428. imgui.begin(std::string("G-code"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
  429. // center the text in the window by pushing down the first line
  430. const float f_lines_count = static_cast<float>(lines_count);
  431. ImGui::SetCursorPosY(0.5f * (wnd_height - f_lines_count * text_height - (f_lines_count - 1.0f) * style.ItemSpacing.y));
  432. // render text lines
  433. for (uint64_t id = start_id; id <= end_id; ++id) {
  434. const Line& line = m_lines[id - start_id];
  435. // rect around the current selected line
  436. if (id == curr_line_id) {
  437. const float pos_y = ImGui::GetCursorScreenPos().y;
  438. const float half_ItemSpacing_y = 0.5f * style.ItemSpacing.y;
  439. const float half_padding_x = 0.5f * style.WindowPadding.x;
  440. ImGui::GetWindowDrawList()->AddRect({ half_padding_x, pos_y - half_ItemSpacing_y },
  441. { ImGui::GetCurrentWindow()->Size.x - half_padding_x, pos_y + text_height + half_ItemSpacing_y },
  442. ImGui::GetColorU32(SELECTION_RECT_COLOR));
  443. }
  444. const std::string id_str = std::to_string(id);
  445. // spacer to right align text
  446. ImGui::Dummy({ id_width - ImGui::CalcTextSize(id_str.c_str()).x, text_height });
  447. size_t line_length = 0;
  448. // render line number
  449. bool stop_adding = add_item_to_line(id_str, LINE_NUMBER_COLOR, 0.0f, line_length);
  450. if (!stop_adding && !line.command.empty())
  451. // render command
  452. stop_adding = add_item_to_line(line.command, COMMAND_COLOR, -1.0f, line_length);
  453. if (!stop_adding && !line.parameters.empty())
  454. // render parameters
  455. stop_adding = add_item_to_line(line.parameters, PARAMETERS_COLOR, 0.0f, line_length);
  456. if (!stop_adding && !line.comment.empty())
  457. // render comment
  458. stop_adding = add_item_to_line(line.comment, COMMENT_COLOR, line.command.empty() ? -1.0f : 0.0f, line_length);
  459. }
  460. imgui.end();
  461. ImGui::PopStyleVar();
  462. }
  463. void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file()
  464. {
  465. if (m_file.is_open())
  466. m_file.close();
  467. }
  468. void GCodeViewer::SequentialView::render(float legend_height)
  469. {
  470. marker.render();
  471. float bottom = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_height();
  472. if (wxGetApp().is_editor())
  473. bottom -= wxGetApp().plater()->get_view_toolbar().get_height();
  474. gcode_window.render(legend_height, bottom, static_cast<uint64_t>(gcode_ids[current.last]));
  475. }
  476. const std::array<ColorRGBA, static_cast<size_t>(GCodeExtrusionRole::Count)> GCodeViewer::Extrusion_Role_Colors{ {
  477. { 0.90f, 0.70f, 0.70f, 1.0f }, // GCodeExtrusionRole::None
  478. { 1.00f, 0.90f, 0.30f, 1.0f }, // GCodeExtrusionRole::Perimeter
  479. { 1.00f, 0.49f, 0.22f, 1.0f }, // GCodeExtrusionRole::ExternalPerimeter
  480. { 0.12f, 0.12f, 1.00f, 1.0f }, // GCodeExtrusionRole::OverhangPerimeter
  481. { 0.69f, 0.19f, 0.16f, 1.0f }, // GCodeExtrusionRole::InternalInfill
  482. { 0.59f, 0.33f, 0.80f, 1.0f }, // GCodeExtrusionRole::SolidInfill
  483. { 0.94f, 0.25f, 0.25f, 1.0f }, // GCodeExtrusionRole::TopSolidInfill
  484. { 1.00f, 0.55f, 0.41f, 1.0f }, // GCodeExtrusionRole::Ironing
  485. { 0.30f, 0.50f, 0.73f, 1.0f }, // GCodeExtrusionRole::BridgeInfill
  486. { 1.00f, 1.00f, 1.00f, 1.0f }, // GCodeExtrusionRole::GapFill
  487. { 0.00f, 0.53f, 0.43f, 1.0f }, // GCodeExtrusionRole::Skirt
  488. { 0.00f, 1.00f, 0.00f, 1.0f }, // GCodeExtrusionRole::SupportMaterial
  489. { 0.00f, 0.50f, 0.00f, 1.0f }, // GCodeExtrusionRole::SupportMaterialInterface
  490. { 0.70f, 0.89f, 0.67f, 1.0f }, // GCodeExtrusionRole::WipeTower
  491. { 0.37f, 0.82f, 0.58f, 1.0f }, // GCodeExtrusionRole::Custom
  492. }};
  493. const std::vector<ColorRGBA> GCodeViewer::Options_Colors{ {
  494. { 0.803f, 0.135f, 0.839f, 1.0f }, // Retractions
  495. { 0.287f, 0.679f, 0.810f, 1.0f }, // Unretractions
  496. { 0.900f, 0.900f, 0.900f, 1.0f }, // Seams
  497. { 0.758f, 0.744f, 0.389f, 1.0f }, // ToolChanges
  498. { 0.856f, 0.582f, 0.546f, 1.0f }, // ColorChanges
  499. { 0.322f, 0.942f, 0.512f, 1.0f }, // PausePrints
  500. { 0.886f, 0.825f, 0.262f, 1.0f } // CustomGCodes
  501. }};
  502. const std::vector<ColorRGBA> GCodeViewer::Travel_Colors{ {
  503. { 0.219f, 0.282f, 0.609f, 1.0f }, // Move
  504. { 0.112f, 0.422f, 0.103f, 1.0f }, // Extrude
  505. { 0.505f, 0.064f, 0.028f, 1.0f } // Retract
  506. }};
  507. #if 1
  508. // Normal ranges
  509. const std::vector<ColorRGBA> GCodeViewer::Range_Colors{ {
  510. { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish
  511. { 0.075f, 0.349f, 0.522f, 1.0f },
  512. { 0.110f, 0.533f, 0.569f, 1.0f },
  513. { 0.016f, 0.839f, 0.059f, 1.0f },
  514. { 0.667f, 0.949f, 0.000f, 1.0f },
  515. { 0.988f, 0.975f, 0.012f, 1.0f },
  516. { 0.961f, 0.808f, 0.039f, 1.0f },
  517. { 0.890f, 0.533f, 0.125f, 1.0f },
  518. { 0.820f, 0.408f, 0.188f, 1.0f },
  519. { 0.761f, 0.322f, 0.235f, 1.0f },
  520. { 0.581f, 0.149f, 0.087f, 1.0f } // reddish
  521. }};
  522. #else
  523. // Detailed ranges
  524. const std::vector<ColorRGBA> GCodeViewer::Range_Colors{ {
  525. { 0.043f, 0.173f, 0.478f, 1.0f }, // bluish
  526. { 0.5f * (0.043f + 0.075f), 0.5f * (0.173f + 0.349f), 0.5f * (0.478f + 0.522f), 1.0f },
  527. { 0.075f, 0.349f, 0.522f, 1.0f },
  528. { 0.5f * (0.075f + 0.110f), 0.5f * (0.349f + 0.533f), 0.5f * (0.522f + 0.569f), 1.0f },
  529. { 0.110f, 0.533f, 0.569f, 1.0f },
  530. { 0.5f * (0.110f + 0.016f), 0.5f * (0.533f + 0.839f), 0.5f * (0.569f + 0.059f), 1.0f },
  531. { 0.016f, 0.839f, 0.059f, 1.0f },
  532. { 0.5f * (0.016f + 0.667f), 0.5f * (0.839f + 0.949f), 0.5f * (0.059f + 0.000f), 1.0f },
  533. { 0.667f, 0.949f, 0.000f, 1.0f },
  534. { 0.5f * (0.667f + 0.988f), 0.5f * (0.949f + 0.975f), 0.5f * (0.000f + 0.012f), 1.0f },
  535. { 0.988f, 0.975f, 0.012f, 1.0f },
  536. { 0.5f * (0.988f + 0.961f), 0.5f * (0.975f + 0.808f), 0.5f * (0.012f + 0.039f), 1.0f },
  537. { 0.961f, 0.808f, 0.039f, 1.0f },
  538. { 0.5f * (0.961f + 0.890f), 0.5f * (0.808f + 0.533f), 0.5f * (0.039f + 0.125f), 1.0f },
  539. { 0.890f, 0.533f, 0.125f, 1.0f },
  540. { 0.5f * (0.890f + 0.820f), 0.5f * (0.533f + 0.408f), 0.5f * (0.125f + 0.188f), 1.0f },
  541. { 0.820f, 0.408f, 0.188f, 1.0f },
  542. { 0.5f * (0.820f + 0.761f), 0.5f * (0.408f + 0.322f), 0.5f * (0.188f + 0.235f), 1.0f },
  543. { 0.761f, 0.322f, 0.235f, 1.0f },
  544. { 0.5f * (0.761f + 0.581f), 0.5f * (0.322f + 0.149f), 0.5f * (0.235f + 0.087f), 1.0f },
  545. { 0.581f, 0.149f, 0.087f, 1.0f } // reddishgit
  546. } };
  547. #endif
  548. const ColorRGBA GCodeViewer::Wipe_Color = ColorRGBA::YELLOW();
  549. const ColorRGBA GCodeViewer::Neutral_Color = ColorRGBA::DARK_GRAY();
  550. GCodeViewer::GCodeViewer()
  551. {
  552. m_extrusions.reset_role_visibility_flags();
  553. m_shells.volumes.set_use_raycasters(false);
  554. // m_sequential_view.skip_invisible_moves = true;
  555. }
  556. void GCodeViewer::init()
  557. {
  558. if (m_gl_data_initialized)
  559. return;
  560. // initializes opengl data of TBuffers
  561. for (size_t i = 0; i < m_buffers.size(); ++i) {
  562. TBuffer& buffer = m_buffers[i];
  563. EMoveType type = buffer_type(i);
  564. switch (type)
  565. {
  566. default: { break; }
  567. case EMoveType::Tool_change:
  568. case EMoveType::Color_change:
  569. case EMoveType::Pause_Print:
  570. case EMoveType::Custom_GCode:
  571. case EMoveType::Retract:
  572. case EMoveType::Unretract:
  573. case EMoveType::Seam: {
  574. #if !DISABLE_GCODEVIEWER_INSTANCED_MODELS
  575. if (wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
  576. buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::InstancedModel;
  577. buffer.shader = "gouraud_light_instanced";
  578. buffer.model.model.init_from(diamond(16));
  579. buffer.model.color = option_color(type);
  580. buffer.model.instances.format = InstanceVBuffer::EFormat::InstancedModel;
  581. }
  582. else {
  583. #endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS
  584. buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::BatchedModel;
  585. buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
  586. buffer.shader = "gouraud_light";
  587. buffer.model.data = diamond(16);
  588. buffer.model.color = option_color(type);
  589. buffer.model.instances.format = InstanceVBuffer::EFormat::BatchedModel;
  590. #if !DISABLE_GCODEVIEWER_INSTANCED_MODELS
  591. }
  592. #endif // !DISABLE_GCODEVIEWER_INSTANCED_MODELS
  593. break;
  594. }
  595. case EMoveType::Wipe:
  596. case EMoveType::Extrude: {
  597. buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle;
  598. buffer.vertices.format = VBuffer::EFormat::PositionNormal3;
  599. buffer.shader = "gouraud_light";
  600. break;
  601. }
  602. case EMoveType::Travel: {
  603. buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line;
  604. buffer.vertices.format = VBuffer::EFormat::Position;
  605. #if ENABLE_GL_CORE_PROFILE
  606. // on MAC using the geometry shader of dashed_thick_lines is too slow
  607. buffer.shader = "flat";
  608. // buffer.shader = OpenGLManager::get_gl_info().is_core_profile() ? "dashed_thick_lines" : "flat";
  609. #else
  610. buffer.shader = "flat";
  611. #endif // ENABLE_GL_CORE_PROFILE
  612. break;
  613. }
  614. }
  615. set_toolpath_move_type_visible(EMoveType::Extrude, true);
  616. }
  617. // initializes tool marker
  618. m_sequential_view.marker.init();
  619. m_gl_data_initialized = true;
  620. }
  621. void GCodeViewer::load(const GCodeProcessorResult& gcode_result, const Print& print)
  622. {
  623. // avoid processing if called with the same gcode_result
  624. if (m_last_result_id == gcode_result.id &&
  625. (m_last_view_type == m_view_type || (m_last_view_type != EViewType::VolumetricRate && m_view_type != EViewType::VolumetricRate)))
  626. return;
  627. m_last_result_id = gcode_result.id;
  628. m_last_view_type = m_view_type;
  629. // release gpu memory, if used
  630. reset();
  631. m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends);
  632. if (wxGetApp().is_gcode_viewer())
  633. m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z;
  634. m_max_print_height = gcode_result.max_print_height;
  635. load_toolpaths(gcode_result);
  636. if (m_layers.empty())
  637. return;
  638. m_settings_ids = gcode_result.settings_ids;
  639. m_filament_diameters = gcode_result.filament_diameters;
  640. m_filament_densities = gcode_result.filament_densities;
  641. if (!wxGetApp().is_editor()) {
  642. Pointfs bed_shape;
  643. std::string texture;
  644. std::string model;
  645. if (!gcode_result.bed_shape.empty()) {
  646. // bed shape detected in the gcode
  647. bed_shape = gcode_result.bed_shape;
  648. const auto bundle = wxGetApp().preset_bundle;
  649. if (bundle != nullptr && !m_settings_ids.printer.empty()) {
  650. const Preset* preset = bundle->printers.find_preset(m_settings_ids.printer);
  651. if (preset != nullptr) {
  652. model = PresetUtils::system_printer_bed_model(*preset);
  653. texture = PresetUtils::system_printer_bed_texture(*preset);
  654. }
  655. }
  656. }
  657. else {
  658. // adjust printbed size in dependence of toolpaths bbox
  659. const double margin = 10.0;
  660. const Vec2d min(m_paths_bounding_box.min.x() - margin, m_paths_bounding_box.min.y() - margin);
  661. const Vec2d max(m_paths_bounding_box.max.x() + margin, m_paths_bounding_box.max.y() + margin);
  662. const Vec2d size = max - min;
  663. bed_shape = {
  664. { min.x(), min.y() },
  665. { max.x(), min.y() },
  666. { max.x(), min.y() + 0.442265 * size.y()},
  667. { max.x() - 10.0, min.y() + 0.4711325 * size.y()},
  668. { max.x() + 10.0, min.y() + 0.5288675 * size.y()},
  669. { max.x(), min.y() + 0.557735 * size.y()},
  670. { max.x(), max.y() },
  671. { min.x() + 0.557735 * size.x(), max.y()},
  672. { min.x() + 0.5288675 * size.x(), max.y() - 10.0},
  673. { min.x() + 0.4711325 * size.x(), max.y() + 10.0},
  674. { min.x() + 0.442265 * size.x(), max.y()},
  675. { min.x(), max.y() } };
  676. }
  677. wxGetApp().plater()->set_bed_shape(bed_shape, gcode_result.max_print_height, texture, model, gcode_result.bed_shape.empty());
  678. }
  679. m_print_statistics = gcode_result.print_statistics;
  680. if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) {
  681. const float time = m_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)].time;
  682. if (time == 0.0f ||
  683. short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].time)))
  684. m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal;
  685. }
  686. m_conflict_result = gcode_result.conflict_result;
  687. if (m_conflict_result.has_value()) { m_conflict_result->layer = m_layers.get_l_at(m_conflict_result->_height); }
  688. }
  689. void GCodeViewer::refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors)
  690. {
  691. #if ENABLE_GCODE_VIEWER_STATISTICS
  692. auto start_time = std::chrono::high_resolution_clock::now();
  693. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  694. if (m_moves_count == 0)
  695. return;
  696. wxBusyCursor busy;
  697. if (m_view_type == EViewType::Tool && !gcode_result.extruder_colors.empty())
  698. // update tool colors from config stored in the gcode
  699. decode_colors(gcode_result.extruder_colors, m_tool_colors);
  700. else
  701. // update tool colors
  702. decode_colors(str_tool_colors, m_tool_colors);
  703. ColorRGBA default_color;
  704. decode_color("#FF8000", default_color);
  705. // ensure there are enough colors defined
  706. while (m_tool_colors.size() < std::max(size_t(1), gcode_result.extruders_count))
  707. m_tool_colors.push_back(default_color);
  708. // update ranges for coloring / legend
  709. m_extrusions.reset_ranges();
  710. for (size_t i = 0; i < m_moves_count; ++i) {
  711. // skip first vertex
  712. if (i == 0)
  713. continue;
  714. const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
  715. switch (curr.type)
  716. {
  717. case EMoveType::Extrude:
  718. {
  719. m_extrusions.ranges.height.update_from(round_to_bin(curr.height));
  720. if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom))
  721. m_extrusions.ranges.width.update_from(round_to_bin(curr.width));
  722. m_extrusions.ranges.fan_speed.update_from(curr.fan_speed);
  723. m_extrusions.ranges.temperature.update_from(curr.temperature);
  724. if (curr.extrusion_role != GCodeExtrusionRole::Custom || is_visible(GCodeExtrusionRole::Custom))
  725. m_extrusions.ranges.volumetric_rate.update_from(round_to_bin(curr.volumetric_rate()));
  726. [[fallthrough]];
  727. }
  728. case EMoveType::Travel:
  729. {
  730. if (m_buffers[buffer_id(curr.type)].visible)
  731. m_extrusions.ranges.feedrate.update_from(curr.feedrate);
  732. break;
  733. }
  734. default: { break; }
  735. }
  736. }
  737. for (size_t i = 0; i < gcode_result.print_statistics.modes.size(); ++i) {
  738. m_layers_times[i] = gcode_result.print_statistics.modes[i].layers_times;
  739. }
  740. for (size_t i = 0; i < m_layers_times.size(); ++i) {
  741. for (float time : m_layers_times[i]) {
  742. m_extrusions.ranges.layer_time[i].update_from(time);
  743. }
  744. }
  745. #if ENABLE_GCODE_VIEWER_STATISTICS
  746. m_statistics.refresh_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
  747. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  748. // update buffers' render paths
  749. refresh_render_paths(false, false);
  750. log_memory_used("Refreshed G-code extrusion paths, ");
  751. }
  752. void GCodeViewer::update_shells_color_by_extruder(const DynamicPrintConfig* config)
  753. {
  754. if (config != nullptr)
  755. m_shells.volumes.update_colors_by_extruder(config);
  756. }
  757. void GCodeViewer::reset()
  758. {
  759. m_moves_count = 0;
  760. for (TBuffer& buffer : m_buffers) {
  761. buffer.reset();
  762. }
  763. m_paths_bounding_box.reset();
  764. m_max_bounding_box.reset();
  765. m_max_print_height = 0.0f;
  766. m_tool_colors = std::vector<ColorRGBA>();
  767. m_extruders_count = 0;
  768. m_extruder_ids = std::vector<unsigned char>();
  769. m_filament_diameters = std::vector<float>();
  770. m_filament_densities = std::vector<float>();
  771. m_extrusions.reset_ranges();
  772. m_layers.reset();
  773. m_layers_z_range = { 0, 0 };
  774. m_roles = std::vector<GCodeExtrusionRole>();
  775. m_print_statistics.reset();
  776. for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
  777. m_layers_times[i] = std::vector<float>();
  778. }
  779. m_custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
  780. m_sequential_view.gcode_window.reset();
  781. #if ENABLE_GCODE_VIEWER_STATISTICS
  782. m_statistics.reset_all();
  783. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  784. m_contained_in_bed = true;
  785. m_legend_resizer.reset();
  786. }
  787. void GCodeViewer::render()
  788. {
  789. #if ENABLE_GCODE_VIEWER_STATISTICS
  790. m_statistics.reset_opengl();
  791. m_statistics.total_instances_gpu_size = 0;
  792. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  793. glsafe(::glEnable(GL_DEPTH_TEST));
  794. render_shells();
  795. if (m_roles.empty())
  796. return;
  797. render_toolpaths();
  798. float legend_height = 0.0f;
  799. if (!m_layers.empty()) {
  800. render_legend(legend_height);
  801. if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
  802. m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
  803. m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
  804. m_sequential_view.render(legend_height);
  805. }
  806. }
  807. #if ENABLE_GCODE_VIEWER_STATISTICS
  808. render_statistics();
  809. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  810. }
  811. bool GCodeViewer::can_export_toolpaths() const
  812. {
  813. return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle;
  814. }
  815. void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last)
  816. {
  817. auto is_visible = [this](unsigned int id) {
  818. for (const TBuffer& buffer : m_buffers) {
  819. if (buffer.visible) {
  820. for (const Path& path : buffer.paths) {
  821. if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id)
  822. return true;
  823. }
  824. }
  825. }
  826. return false;
  827. };
  828. const int first_diff = static_cast<int>(first) - static_cast<int>(m_sequential_view.last_current.first);
  829. const int last_diff = static_cast<int>(last) - static_cast<int>(m_sequential_view.last_current.last);
  830. unsigned int new_first = first;
  831. unsigned int new_last = last;
  832. if (m_sequential_view.skip_invisible_moves) {
  833. while (!is_visible(new_first)) {
  834. if (first_diff > 0)
  835. ++new_first;
  836. else
  837. --new_first;
  838. }
  839. while (!is_visible(new_last)) {
  840. if (last_diff > 0)
  841. ++new_last;
  842. else
  843. --new_last;
  844. }
  845. }
  846. m_sequential_view.current.first = new_first;
  847. m_sequential_view.current.last = new_last;
  848. m_sequential_view.last_current = m_sequential_view.current;
  849. refresh_render_paths(true, true);
  850. if (new_first != first || new_last != last)
  851. wxGetApp().plater()->update_preview_moves_slider();
  852. }
  853. bool GCodeViewer::is_toolpath_move_type_visible(EMoveType type) const
  854. {
  855. size_t id = static_cast<size_t>(buffer_id(type));
  856. return (id < m_buffers.size()) ? m_buffers[id].visible : false;
  857. }
  858. void GCodeViewer::set_toolpath_move_type_visible(EMoveType type, bool visible)
  859. {
  860. size_t id = static_cast<size_t>(buffer_id(type));
  861. if (id < m_buffers.size())
  862. m_buffers[id].visible = visible;
  863. }
  864. unsigned int GCodeViewer::get_options_visibility_flags() const
  865. {
  866. auto set_flag = [](unsigned int flags, unsigned int flag, bool active) {
  867. return active ? (flags | (1 << flag)) : flags;
  868. };
  869. unsigned int flags = 0;
  870. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Travel), is_toolpath_move_type_visible(EMoveType::Travel));
  871. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Wipe), is_toolpath_move_type_visible(EMoveType::Wipe));
  872. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Retractions), is_toolpath_move_type_visible(EMoveType::Retract));
  873. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Unretractions), is_toolpath_move_type_visible(EMoveType::Unretract));
  874. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Seams), is_toolpath_move_type_visible(EMoveType::Seam));
  875. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolChanges), is_toolpath_move_type_visible(EMoveType::Tool_change));
  876. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ColorChanges), is_toolpath_move_type_visible(EMoveType::Color_change));
  877. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::PausePrints), is_toolpath_move_type_visible(EMoveType::Pause_Print));
  878. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CustomGCodes), is_toolpath_move_type_visible(EMoveType::Custom_GCode));
  879. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::CenterOfGravity), m_cog.is_visible());
  880. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::Shells), m_shells.visible);
  881. flags = set_flag(flags, static_cast<unsigned int>(Preview::OptionType::ToolMarker), m_sequential_view.marker.is_visible());
  882. return flags;
  883. }
  884. void GCodeViewer::set_options_visibility_from_flags(unsigned int flags)
  885. {
  886. auto is_flag_set = [flags](unsigned int flag) {
  887. return (flags & (1 << flag)) != 0;
  888. };
  889. set_toolpath_move_type_visible(EMoveType::Travel, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Travel)));
  890. set_toolpath_move_type_visible(EMoveType::Wipe, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Wipe)));
  891. set_toolpath_move_type_visible(EMoveType::Retract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Retractions)));
  892. set_toolpath_move_type_visible(EMoveType::Unretract, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Unretractions)));
  893. set_toolpath_move_type_visible(EMoveType::Seam, is_flag_set(static_cast<unsigned int>(Preview::OptionType::Seams)));
  894. set_toolpath_move_type_visible(EMoveType::Tool_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolChanges)));
  895. set_toolpath_move_type_visible(EMoveType::Color_change, is_flag_set(static_cast<unsigned int>(Preview::OptionType::ColorChanges)));
  896. set_toolpath_move_type_visible(EMoveType::Pause_Print, is_flag_set(static_cast<unsigned int>(Preview::OptionType::PausePrints)));
  897. set_toolpath_move_type_visible(EMoveType::Custom_GCode, is_flag_set(static_cast<unsigned int>(Preview::OptionType::CustomGCodes)));
  898. m_cog.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::CenterOfGravity)));
  899. m_shells.visible = is_flag_set(static_cast<unsigned int>(Preview::OptionType::Shells));
  900. m_sequential_view.marker.set_visible(is_flag_set(static_cast<unsigned int>(Preview::OptionType::ToolMarker)));
  901. }
  902. void GCodeViewer::set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range)
  903. {
  904. bool keep_sequential_current_first = layers_z_range[0] >= m_layers_z_range[0];
  905. bool keep_sequential_current_last = layers_z_range[1] <= m_layers_z_range[1];
  906. m_layers_z_range = layers_z_range;
  907. refresh_render_paths(keep_sequential_current_first, keep_sequential_current_last);
  908. wxGetApp().plater()->update_preview_moves_slider();
  909. }
  910. void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
  911. {
  912. if (filename == nullptr)
  913. return;
  914. if (!has_data())
  915. return;
  916. wxBusyCursor busy;
  917. // the data needed is contained into the Extrude TBuffer
  918. const TBuffer& t_buffer = m_buffers[buffer_id(EMoveType::Extrude)];
  919. if (!t_buffer.has_data())
  920. return;
  921. if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle)
  922. return;
  923. // collect color information to generate materials
  924. std::vector<ColorRGBA> colors;
  925. for (const RenderPath& path : t_buffer.render_paths) {
  926. colors.push_back(path.color);
  927. }
  928. sort_remove_duplicates(colors);
  929. // save materials file
  930. boost::filesystem::path mat_filename(filename);
  931. mat_filename.replace_extension("mtl");
  932. CNumericLocalesSetter locales_setter;
  933. FILE* fp = boost::nowide::fopen(mat_filename.string().c_str(), "w");
  934. if (fp == nullptr) {
  935. BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << mat_filename.string().c_str() << " for writing";
  936. return;
  937. }
  938. fprintf(fp, "# G-Code Toolpaths Materials\n");
  939. fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION);
  940. unsigned int colors_count = 1;
  941. for (const ColorRGBA& color : colors) {
  942. fprintf(fp, "\nnewmtl material_%d\n", colors_count++);
  943. fprintf(fp, "Ka 1 1 1\n");
  944. fprintf(fp, "Kd %g %g %g\n", color.r(), color.g(), color.b());
  945. fprintf(fp, "Ks 0 0 0\n");
  946. }
  947. fclose(fp);
  948. // save geometry file
  949. fp = boost::nowide::fopen(filename, "w");
  950. if (fp == nullptr) {
  951. BOOST_LOG_TRIVIAL(error) << "GCodeViewer::export_toolpaths_to_obj: Couldn't open " << filename << " for writing";
  952. return;
  953. }
  954. fprintf(fp, "# G-Code Toolpaths\n");
  955. fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION);
  956. fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str());
  957. const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats();
  958. std::vector<Vec3f> out_vertices;
  959. std::vector<Vec3f> out_normals;
  960. struct VerticesOffset
  961. {
  962. unsigned int vbo;
  963. size_t offset;
  964. };
  965. std::vector<VerticesOffset> vertices_offsets;
  966. vertices_offsets.push_back({ t_buffer.vertices.vbos.front(), 0 });
  967. // get vertices/normals data from vertex buffers on gpu
  968. for (size_t i = 0; i < t_buffer.vertices.vbos.size(); ++i) {
  969. const size_t floats_count = t_buffer.vertices.sizes[i] / sizeof(float);
  970. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.vbos[i]));
  971. #if ENABLE_OPENGL_ES
  972. const VertexBuffer vertices = *static_cast<VertexBuffer*>(::glMapBufferRange(GL_ARRAY_BUFFER, 0,
  973. static_cast<GLsizeiptr>(t_buffer.vertices.sizes[i]), GL_MAP_READ_BIT));
  974. glcheck();
  975. glsafe(::glUnmapBuffer(GL_ARRAY_BUFFER));
  976. #else
  977. VertexBuffer vertices(floats_count);
  978. glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(t_buffer.vertices.sizes[i]), static_cast<void*>(vertices.data())));
  979. #endif // ENABLE_OPENGL_ES
  980. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  981. const size_t vertices_count = floats_count / floats_per_vertex;
  982. for (size_t j = 0; j < vertices_count; ++j) {
  983. const size_t base = j * floats_per_vertex;
  984. out_vertices.push_back({ vertices[base + 0], vertices[base + 1], vertices[base + 2] });
  985. out_normals.push_back({ vertices[base + 3], vertices[base + 4], vertices[base + 5] });
  986. }
  987. if (i < t_buffer.vertices.vbos.size() - 1)
  988. vertices_offsets.push_back({ t_buffer.vertices.vbos[i + 1], vertices_offsets.back().offset + vertices_count });
  989. }
  990. // save vertices to file
  991. fprintf(fp, "\n# vertices\n");
  992. for (const Vec3f& v : out_vertices) {
  993. fprintf(fp, "v %g %g %g\n", v.x(), v.y(), v.z());
  994. }
  995. // save normals to file
  996. fprintf(fp, "\n# normals\n");
  997. for (const Vec3f& n : out_normals) {
  998. fprintf(fp, "vn %g %g %g\n", n.x(), n.y(), n.z());
  999. }
  1000. size_t i = 0;
  1001. for (const ColorRGBA& color : colors) {
  1002. // save material triangles to file
  1003. fprintf(fp, "\nusemtl material_%zu\n", i + 1);
  1004. fprintf(fp, "# triangles material %zu\n", i + 1);
  1005. for (const RenderPath& render_path : t_buffer.render_paths) {
  1006. if (render_path.color != color)
  1007. continue;
  1008. const IBuffer& ibuffer = t_buffer.indices[render_path.ibuffer_id];
  1009. size_t vertices_offset = 0;
  1010. for (size_t j = 0; j < vertices_offsets.size(); ++j) {
  1011. const VerticesOffset& offset = vertices_offsets[j];
  1012. if (offset.vbo == ibuffer.vbo) {
  1013. vertices_offset = offset.offset;
  1014. break;
  1015. }
  1016. }
  1017. // get indices data from index buffer on gpu
  1018. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.ibo));
  1019. for (size_t j = 0; j < render_path.sizes.size(); ++j) {
  1020. #if ENABLE_OPENGL_ES
  1021. const IndexBuffer indices = *static_cast<IndexBuffer*>(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER,
  1022. static_cast<GLintptr>(render_path.offsets[j]), static_cast<GLsizeiptr>(render_path.sizes[j] * sizeof(IBufferType)),
  1023. GL_MAP_READ_BIT));
  1024. glcheck();
  1025. glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER));
  1026. #else
  1027. IndexBuffer indices(render_path.sizes[j]);
  1028. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>(render_path.offsets[j]),
  1029. static_cast<GLsizeiptr>(render_path.sizes[j] * sizeof(IBufferType)), static_cast<void*>(indices.data())));
  1030. #endif // ENABLE_OPENGL_ES
  1031. const size_t triangles_count = render_path.sizes[j] / 3;
  1032. for (size_t k = 0; k < triangles_count; ++k) {
  1033. const size_t base = k * 3;
  1034. const size_t v1 = 1 + static_cast<size_t>(indices[base + 0]) + vertices_offset;
  1035. const size_t v2 = 1 + static_cast<size_t>(indices[base + 1]) + vertices_offset;
  1036. const size_t v3 = 1 + static_cast<size_t>(indices[base + 2]) + vertices_offset;
  1037. if (v1 != v2)
  1038. // do not export dummy triangles
  1039. fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", v1, v1, v2, v2, v3, v3);
  1040. }
  1041. }
  1042. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  1043. }
  1044. ++i;
  1045. }
  1046. fclose(fp);
  1047. }
  1048. void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
  1049. {
  1050. // max index buffer size, in bytes
  1051. static const size_t IBUFFER_THRESHOLD_BYTES = 64 * 1024 * 1024;
  1052. auto log_memory_usage = [this](const std::string& label, const std::vector<MultiVertexBuffer>& vertices, const std::vector<MultiIndexBuffer>& indices) {
  1053. int64_t vertices_size = 0;
  1054. for (const MultiVertexBuffer& buffers : vertices) {
  1055. for (const VertexBuffer& buffer : buffers) {
  1056. vertices_size += SLIC3R_STDVEC_MEMSIZE(buffer, float);
  1057. }
  1058. }
  1059. int64_t indices_size = 0;
  1060. for (const MultiIndexBuffer& buffers : indices) {
  1061. for (const IndexBuffer& buffer : buffers) {
  1062. indices_size += SLIC3R_STDVEC_MEMSIZE(buffer, IBufferType);
  1063. }
  1064. }
  1065. log_memory_used(label, vertices_size + indices_size);
  1066. };
  1067. // format data into the buffers to be rendered as lines
  1068. auto add_vertices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, VertexBuffer& vertices) {
  1069. auto add_vertex = [&vertices](const GCodeProcessorResult::MoveVertex& vertex) {
  1070. // add position
  1071. vertices.push_back(vertex.position.x());
  1072. vertices.push_back(vertex.position.y());
  1073. vertices.push_back(vertex.position.z());
  1074. };
  1075. // add previous vertex
  1076. add_vertex(prev);
  1077. // add current vertex
  1078. add_vertex(curr);
  1079. };
  1080. auto add_indices_as_line = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
  1081. unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) {
  1082. if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
  1083. // add starting index
  1084. indices.push_back(static_cast<IBufferType>(indices.size()));
  1085. buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1);
  1086. buffer.paths.back().sub_paths.front().first.position = prev.position;
  1087. }
  1088. Path& last_path = buffer.paths.back();
  1089. if (last_path.sub_paths.front().first.i_id != last_path.sub_paths.back().last.i_id) {
  1090. // add previous index
  1091. indices.push_back(static_cast<IBufferType>(indices.size()));
  1092. }
  1093. // add current index
  1094. indices.push_back(static_cast<IBufferType>(indices.size()));
  1095. last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position };
  1096. };
  1097. // format data into the buffers to be rendered as solid
  1098. auto add_vertices_as_solid = [](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr, TBuffer& buffer,
  1099. unsigned int vbuffer_id, VertexBuffer& vertices, size_t move_id, bool account_for_volumetric_rate) {
  1100. auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) {
  1101. // append position
  1102. vertices.push_back(position.x());
  1103. vertices.push_back(position.y());
  1104. vertices.push_back(position.z());
  1105. // append normal
  1106. vertices.push_back(normal.x());
  1107. vertices.push_back(normal.y());
  1108. vertices.push_back(normal.z());
  1109. };
  1110. if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
  1111. buffer.add_path(curr, vbuffer_id, vertices.size(), move_id - 1);
  1112. buffer.paths.back().sub_paths.back().first.position = prev.position;
  1113. }
  1114. Path& last_path = buffer.paths.back();
  1115. const Vec3f dir = (curr.position - prev.position).normalized();
  1116. const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized();
  1117. const Vec3f left = -right;
  1118. const Vec3f up = right.cross(dir);
  1119. const Vec3f down = -up;
  1120. const float half_width = 0.5f * last_path.width;
  1121. const float half_height = 0.5f * last_path.height;
  1122. const Vec3f prev_pos = prev.position - half_height * up;
  1123. const Vec3f curr_pos = curr.position - half_height * up;
  1124. const Vec3f d_up = half_height * up;
  1125. const Vec3f d_down = -half_height * up;
  1126. const Vec3f d_right = half_width * right;
  1127. const Vec3f d_left = -half_width * right;
  1128. // vertices 1st endpoint
  1129. if (last_path.vertices_count() == 1 || vertices.empty()) {
  1130. // 1st segment or restart into a new vertex buffer
  1131. // ===============================================
  1132. store_vertex(vertices, prev_pos + d_up, up);
  1133. store_vertex(vertices, prev_pos + d_right, right);
  1134. store_vertex(vertices, prev_pos + d_down, down);
  1135. store_vertex(vertices, prev_pos + d_left, left);
  1136. }
  1137. else {
  1138. // any other segment
  1139. // =================
  1140. store_vertex(vertices, prev_pos + d_right, right);
  1141. store_vertex(vertices, prev_pos + d_left, left);
  1142. }
  1143. // vertices 2nd endpoint
  1144. store_vertex(vertices, curr_pos + d_up, up);
  1145. store_vertex(vertices, curr_pos + d_right, right);
  1146. store_vertex(vertices, curr_pos + d_down, down);
  1147. store_vertex(vertices, curr_pos + d_left, left);
  1148. last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position };
  1149. };
  1150. auto add_indices_as_solid = [&](const GCodeProcessorResult::MoveVertex& prev, const GCodeProcessorResult::MoveVertex& curr,
  1151. const GCodeProcessorResult::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id,
  1152. IndexBuffer& indices, size_t move_id, bool account_for_volumetric_rate) {
  1153. static Vec3f prev_dir;
  1154. static Vec3f prev_up;
  1155. static float sq_prev_length;
  1156. auto store_triangle = [](IndexBuffer& indices, IBufferType i1, IBufferType i2, IBufferType i3) {
  1157. indices.push_back(i1);
  1158. indices.push_back(i2);
  1159. indices.push_back(i3);
  1160. };
  1161. auto append_dummy_cap = [store_triangle](IndexBuffer& indices, IBufferType id) {
  1162. store_triangle(indices, id, id, id);
  1163. store_triangle(indices, id, id, id);
  1164. };
  1165. auto convert_vertices_offset = [](size_t vbuffer_size, const std::array<int, 8>& v_offsets) {
  1166. std::array<IBufferType, 8> ret = {
  1167. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[0]),
  1168. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[1]),
  1169. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[2]),
  1170. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[3]),
  1171. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[4]),
  1172. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[5]),
  1173. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[6]),
  1174. static_cast<IBufferType>(static_cast<int>(vbuffer_size) + v_offsets[7])
  1175. };
  1176. return ret;
  1177. };
  1178. auto append_starting_cap_triangles = [&](IndexBuffer& indices, const std::array<IBufferType, 8>& v_offsets) {
  1179. store_triangle(indices, v_offsets[0], v_offsets[2], v_offsets[1]);
  1180. store_triangle(indices, v_offsets[0], v_offsets[3], v_offsets[2]);
  1181. };
  1182. auto append_stem_triangles = [&](IndexBuffer& indices, const std::array<IBufferType, 8>& v_offsets) {
  1183. store_triangle(indices, v_offsets[0], v_offsets[1], v_offsets[4]);
  1184. store_triangle(indices, v_offsets[1], v_offsets[5], v_offsets[4]);
  1185. store_triangle(indices, v_offsets[1], v_offsets[2], v_offsets[5]);
  1186. store_triangle(indices, v_offsets[2], v_offsets[6], v_offsets[5]);
  1187. store_triangle(indices, v_offsets[2], v_offsets[3], v_offsets[6]);
  1188. store_triangle(indices, v_offsets[3], v_offsets[7], v_offsets[6]);
  1189. store_triangle(indices, v_offsets[3], v_offsets[0], v_offsets[7]);
  1190. store_triangle(indices, v_offsets[0], v_offsets[4], v_offsets[7]);
  1191. };
  1192. auto append_ending_cap_triangles = [&](IndexBuffer& indices, const std::array<IBufferType, 8>& v_offsets) {
  1193. store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]);
  1194. store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]);
  1195. };
  1196. if (buffer.paths.empty() || prev.type != curr.type || !buffer.paths.back().matches(curr, account_for_volumetric_rate)) {
  1197. buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1);
  1198. buffer.paths.back().sub_paths.back().first.position = prev.position;
  1199. }
  1200. Path& last_path = buffer.paths.back();
  1201. const Vec3f dir = (curr.position - prev.position).normalized();
  1202. const Vec3f right = Vec3f(dir.y(), -dir.x(), 0.0f).normalized();
  1203. const Vec3f up = right.cross(dir);
  1204. const float sq_length = (curr.position - prev.position).squaredNorm();
  1205. const std::array<IBufferType, 8> first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 });
  1206. const std::array<IBufferType, 8> non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 });
  1207. const bool is_first_segment = (last_path.vertices_count() == 1);
  1208. if (is_first_segment || vbuffer_size == 0) {
  1209. // 1st segment or restart into a new vertex buffer
  1210. // ===============================================
  1211. if (is_first_segment)
  1212. // starting cap triangles
  1213. append_starting_cap_triangles(indices, first_seg_v_offsets);
  1214. // dummy triangles outer corner cap
  1215. append_dummy_cap(indices, vbuffer_size);
  1216. // stem triangles
  1217. append_stem_triangles(indices, first_seg_v_offsets);
  1218. vbuffer_size += 8;
  1219. }
  1220. else {
  1221. // any other segment
  1222. // =================
  1223. float displacement = 0.0f;
  1224. const float cos_dir = prev_dir.dot(dir);
  1225. if (cos_dir > -0.9998477f) {
  1226. // if the angle between adjacent segments is smaller than 179 degrees
  1227. const Vec3f med_dir = (prev_dir + dir).normalized();
  1228. const float half_width = 0.5f * last_path.width;
  1229. displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f)));
  1230. }
  1231. const float sq_displacement = sqr(displacement);
  1232. const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_length;
  1233. const bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f;
  1234. // whether the angle between adjacent segments is greater than 45 degrees
  1235. const bool is_sharp = cos_dir < 0.7071068f;
  1236. bool right_displaced = false;
  1237. bool left_displaced = false;
  1238. if (!is_sharp && can_displace) {
  1239. if (is_right_turn)
  1240. left_displaced = true;
  1241. else
  1242. right_displaced = true;
  1243. }
  1244. // triangles outer corner cap
  1245. if (is_right_turn) {
  1246. if (left_displaced)
  1247. // dummy triangles
  1248. append_dummy_cap(indices, vbuffer_size);
  1249. else {
  1250. store_triangle(indices, vbuffer_size - 4, vbuffer_size + 1, vbuffer_size - 1);
  1251. store_triangle(indices, vbuffer_size + 1, vbuffer_size - 2, vbuffer_size - 1);
  1252. }
  1253. }
  1254. else {
  1255. if (right_displaced)
  1256. // dummy triangles
  1257. append_dummy_cap(indices, vbuffer_size);
  1258. else {
  1259. store_triangle(indices, vbuffer_size - 4, vbuffer_size - 3, vbuffer_size + 0);
  1260. store_triangle(indices, vbuffer_size - 3, vbuffer_size - 2, vbuffer_size + 0);
  1261. }
  1262. }
  1263. // stem triangles
  1264. append_stem_triangles(indices, non_first_seg_v_offsets);
  1265. vbuffer_size += 6;
  1266. }
  1267. if (next != nullptr && (curr.type != next->type || !last_path.matches(*next, account_for_volumetric_rate)))
  1268. // ending cap triangles
  1269. append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets);
  1270. last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position };
  1271. prev_dir = dir;
  1272. prev_up = up;
  1273. sq_prev_length = sq_length;
  1274. };
  1275. // format data into the buffers to be rendered as instanced model
  1276. auto add_model_instance = [](const GCodeProcessorResult::MoveVertex& curr, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) {
  1277. // append position
  1278. instances.push_back(curr.position.x());
  1279. instances.push_back(curr.position.y());
  1280. instances.push_back(curr.position.z());
  1281. // append width
  1282. instances.push_back(curr.width);
  1283. // append height
  1284. instances.push_back(curr.height);
  1285. // append id
  1286. instances_ids.push_back(move_id);
  1287. };
  1288. // format data into the buffers to be rendered as batched model
  1289. auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) {
  1290. const double width = static_cast<double>(1.5f * curr.width);
  1291. const double height = static_cast<double>(1.5f * curr.height);
  1292. const Transform3d trafo = Geometry::translation_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast<double>()) *
  1293. Geometry::scale_transform({ width, width, height });
  1294. const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose();
  1295. // append vertices
  1296. const size_t vertices_count = data.vertices_count();
  1297. for (size_t i = 0; i < vertices_count; ++i) {
  1298. // append position
  1299. const Vec3d position = trafo * data.extract_position_3(i).cast<double>();
  1300. vertices.push_back(float(position.x()));
  1301. vertices.push_back(float(position.y()));
  1302. vertices.push_back(float(position.z()));
  1303. // append normal
  1304. const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast<double>();
  1305. vertices.push_back(float(normal.x()));
  1306. vertices.push_back(float(normal.y()));
  1307. vertices.push_back(float(normal.z()));
  1308. }
  1309. // append instance position
  1310. instances.push_back(curr.position.x());
  1311. instances.push_back(curr.position.y());
  1312. instances.push_back(curr.position.z());
  1313. // append instance id
  1314. instances_ids.push_back(move_id);
  1315. };
  1316. auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) {
  1317. const size_t indices_count = data.indices_count();
  1318. for (size_t i = 0; i < indices_count; ++i) {
  1319. indices.push_back(static_cast<IBufferType>(data.extract_index(i) + base_index));
  1320. }
  1321. };
  1322. #if ENABLE_GCODE_VIEWER_STATISTICS
  1323. auto start_time = std::chrono::high_resolution_clock::now();
  1324. m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessorResult::MoveVertex);
  1325. m_statistics.results_time = gcode_result.time;
  1326. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1327. m_max_bounding_box.reset();
  1328. m_moves_count = gcode_result.moves.size();
  1329. if (m_moves_count == 0)
  1330. return;
  1331. m_extruders_count = gcode_result.extruders_count;
  1332. unsigned int progress_count = 0;
  1333. static const unsigned int progress_threshold = 1000;
  1334. wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ?
  1335. new wxProgressDialog(_L("Generating toolpaths"), "...",
  1336. 100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
  1337. wxBusyCursor busy;
  1338. // extract approximate paths bounding box from result
  1339. for (const GCodeProcessorResult::MoveVertex& move : gcode_result.moves) {
  1340. if (wxGetApp().is_gcode_viewer())
  1341. // for the gcode viewer we need to take in account all moves to correctly size the printbed
  1342. m_paths_bounding_box.merge(move.position.cast<double>());
  1343. else {
  1344. if (move.type == EMoveType::Extrude && move.extrusion_role != GCodeExtrusionRole::Custom && move.width != 0.0f && move.height != 0.0f)
  1345. m_paths_bounding_box.merge(move.position.cast<double>());
  1346. }
  1347. }
  1348. if (wxGetApp().is_editor())
  1349. m_contained_in_bed = wxGetApp().plater()->build_volume().all_paths_inside(gcode_result, m_paths_bounding_box);
  1350. m_cog.reset();
  1351. m_sequential_view.gcode_ids.clear();
  1352. for (size_t i = 0; i < gcode_result.moves.size(); ++i) {
  1353. const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i];
  1354. if (move.type != EMoveType::Seam)
  1355. m_sequential_view.gcode_ids.push_back(move.gcode_id);
  1356. }
  1357. bool account_for_volumetric_rate = m_view_type == EViewType::VolumetricRate;
  1358. std::vector<MultiVertexBuffer> vertices(m_buffers.size());
  1359. std::vector<MultiIndexBuffer> indices(m_buffers.size());
  1360. std::vector<InstanceBuffer> instances(m_buffers.size());
  1361. std::vector<InstanceIdBuffer> instances_ids(m_buffers.size());
  1362. std::vector<InstancesOffsets> instances_offsets(m_buffers.size());
  1363. std::vector<float> options_zs;
  1364. std::vector<size_t> biased_seams_ids;
  1365. // toolpaths data -> extract vertices from result
  1366. for (size_t i = 0; i < m_moves_count; ++i) {
  1367. const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
  1368. if (curr.type == EMoveType::Seam)
  1369. biased_seams_ids.push_back(i - biased_seams_ids.size() - 1);
  1370. const size_t move_id = i - biased_seams_ids.size();
  1371. // skip first vertex
  1372. if (i == 0)
  1373. continue;
  1374. const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1];
  1375. if (curr.type == EMoveType::Extrude &&
  1376. curr.extrusion_role != GCodeExtrusionRole::Skirt &&
  1377. curr.extrusion_role != GCodeExtrusionRole::SupportMaterial &&
  1378. curr.extrusion_role != GCodeExtrusionRole::SupportMaterialInterface &&
  1379. curr.extrusion_role != GCodeExtrusionRole::WipeTower &&
  1380. curr.extrusion_role != GCodeExtrusionRole::Custom) {
  1381. const Vec3d curr_pos = curr.position.cast<double>();
  1382. const Vec3d prev_pos = prev.position.cast<double>();
  1383. m_cog.add_segment(curr_pos, prev_pos, curr.mm3_per_mm * (curr_pos - prev_pos).norm());
  1384. }
  1385. // update progress dialog
  1386. ++progress_count;
  1387. if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
  1388. progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))),
  1389. _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
  1390. progress_dialog->Fit();
  1391. progress_count = 0;
  1392. }
  1393. const unsigned char id = buffer_id(curr.type);
  1394. TBuffer& t_buffer = m_buffers[id];
  1395. MultiVertexBuffer& v_multibuffer = vertices[id];
  1396. InstanceBuffer& inst_buffer = instances[id];
  1397. InstanceIdBuffer& inst_id_buffer = instances_ids[id];
  1398. InstancesOffsets& inst_offsets = instances_offsets[id];
  1399. // ensure there is at least one vertex buffer
  1400. if (v_multibuffer.empty())
  1401. v_multibuffer.push_back(VertexBuffer());
  1402. // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
  1403. // add another vertex buffer
  1404. size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes();
  1405. if (v_multibuffer.back().size() * sizeof(float) > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) {
  1406. v_multibuffer.push_back(VertexBuffer());
  1407. if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
  1408. Path& last_path = t_buffer.paths.back();
  1409. if (prev.type == curr.type && last_path.matches(curr, account_for_volumetric_rate))
  1410. last_path.add_sub_path(prev, static_cast<unsigned int>(v_multibuffer.size()) - 1, 0, move_id - 1);
  1411. }
  1412. }
  1413. VertexBuffer& v_buffer = v_multibuffer.back();
  1414. switch (t_buffer.render_primitive_type)
  1415. {
  1416. case TBuffer::ERenderPrimitiveType::Line: { add_vertices_as_line(prev, curr, v_buffer); break; }
  1417. case TBuffer::ERenderPrimitiveType::Triangle: { add_vertices_as_solid(prev, curr, t_buffer, static_cast<unsigned int>(v_multibuffer.size()) - 1, v_buffer, move_id, account_for_volumetric_rate); break; }
  1418. case TBuffer::ERenderPrimitiveType::InstancedModel:
  1419. {
  1420. add_model_instance(curr, inst_buffer, inst_id_buffer, move_id);
  1421. inst_offsets.push_back(prev.position - curr.position);
  1422. #if ENABLE_GCODE_VIEWER_STATISTICS
  1423. ++m_statistics.instances_count;
  1424. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1425. break;
  1426. }
  1427. case TBuffer::ERenderPrimitiveType::BatchedModel:
  1428. {
  1429. add_vertices_as_model_batch(curr, t_buffer.model.data, v_buffer, inst_buffer, inst_id_buffer, move_id);
  1430. inst_offsets.push_back(prev.position - curr.position);
  1431. #if ENABLE_GCODE_VIEWER_STATISTICS
  1432. ++m_statistics.batched_count;
  1433. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1434. break;
  1435. }
  1436. }
  1437. // collect options zs for later use
  1438. if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) {
  1439. const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back();
  1440. if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2])
  1441. options_zs.emplace_back(curr.position[2]);
  1442. }
  1443. }
  1444. // smooth toolpaths corners for the given TBuffer using triangles
  1445. auto smooth_triangle_toolpaths_corners = [&gcode_result, &biased_seams_ids](const TBuffer& t_buffer, MultiVertexBuffer& v_multibuffer) {
  1446. auto extract_position_at = [](const VertexBuffer& vertices, size_t offset) {
  1447. return Vec3f(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]);
  1448. };
  1449. auto update_position_at = [](VertexBuffer& vertices, size_t offset, const Vec3f& position) {
  1450. vertices[offset + 0] = position.x();
  1451. vertices[offset + 1] = position.y();
  1452. vertices[offset + 2] = position.z();
  1453. };
  1454. auto match_right_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path,
  1455. size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) {
  1456. if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer
  1457. VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id];
  1458. // offset into the vertex buffer of the next segment 1st vertex
  1459. const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats;
  1460. // offset into the vertex buffer of the right vertex of the previous segment
  1461. const size_t prev_right_offset = prev_sub_path.last.i_id - next_1st_offset - 3 * vertex_size_floats;
  1462. // new position of the right vertices
  1463. const Vec3f shared_vertex = extract_position_at(vbuffer, prev_right_offset) + displacement_vec;
  1464. // update previous segment
  1465. update_position_at(vbuffer, prev_right_offset, shared_vertex);
  1466. // offset into the vertex buffer of the right vertex of the next segment
  1467. const size_t next_right_offset = next_sub_path.last.i_id - next_1st_offset;
  1468. // update next segment
  1469. update_position_at(vbuffer, next_right_offset, shared_vertex);
  1470. }
  1471. else { // previous and next segment are contained into different vertex buffers
  1472. VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id];
  1473. VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id];
  1474. // offset into the previous vertex buffer of the right vertex of the previous segment
  1475. const size_t prev_right_offset = prev_sub_path.last.i_id - 3 * vertex_size_floats;
  1476. // new position of the right vertices
  1477. const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_right_offset) + displacement_vec;
  1478. // update previous segment
  1479. update_position_at(prev_vbuffer, prev_right_offset, shared_vertex);
  1480. // offset into the next vertex buffer of the right vertex of the next segment
  1481. const size_t next_right_offset = next_sub_path.first.i_id + 1 * vertex_size_floats;
  1482. // update next segment
  1483. update_position_at(next_vbuffer, next_right_offset, shared_vertex);
  1484. }
  1485. };
  1486. auto match_left_vertices = [&](const Path::Sub_Path& prev_sub_path, const Path::Sub_Path& next_sub_path,
  1487. size_t curr_s_id, size_t vertex_size_floats, const Vec3f& displacement_vec) {
  1488. if (&prev_sub_path == &next_sub_path) { // previous and next segment are both contained into to the same vertex buffer
  1489. VertexBuffer& vbuffer = v_multibuffer[prev_sub_path.first.b_id];
  1490. // offset into the vertex buffer of the next segment 1st vertex
  1491. const size_t next_1st_offset = (prev_sub_path.last.s_id - curr_s_id) * 6 * vertex_size_floats;
  1492. // offset into the vertex buffer of the left vertex of the previous segment
  1493. const size_t prev_left_offset = prev_sub_path.last.i_id - next_1st_offset - 1 * vertex_size_floats;
  1494. // new position of the left vertices
  1495. const Vec3f shared_vertex = extract_position_at(vbuffer, prev_left_offset) + displacement_vec;
  1496. // update previous segment
  1497. update_position_at(vbuffer, prev_left_offset, shared_vertex);
  1498. // offset into the vertex buffer of the left vertex of the next segment
  1499. const size_t next_left_offset = next_sub_path.last.i_id - next_1st_offset + 1 * vertex_size_floats;
  1500. // update next segment
  1501. update_position_at(vbuffer, next_left_offset, shared_vertex);
  1502. }
  1503. else { // previous and next segment are contained into different vertex buffers
  1504. VertexBuffer& prev_vbuffer = v_multibuffer[prev_sub_path.first.b_id];
  1505. VertexBuffer& next_vbuffer = v_multibuffer[next_sub_path.first.b_id];
  1506. // offset into the previous vertex buffer of the left vertex of the previous segment
  1507. const size_t prev_left_offset = prev_sub_path.last.i_id - 1 * vertex_size_floats;
  1508. // new position of the left vertices
  1509. const Vec3f shared_vertex = extract_position_at(prev_vbuffer, prev_left_offset) + displacement_vec;
  1510. // update previous segment
  1511. update_position_at(prev_vbuffer, prev_left_offset, shared_vertex);
  1512. // offset into the next vertex buffer of the left vertex of the next segment
  1513. const size_t next_left_offset = next_sub_path.first.i_id + 3 * vertex_size_floats;
  1514. // update next segment
  1515. update_position_at(next_vbuffer, next_left_offset, shared_vertex);
  1516. }
  1517. };
  1518. auto extract_move_id = [&biased_seams_ids](size_t id) {
  1519. size_t new_id = size_t(-1);
  1520. auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id);
  1521. if (it == biased_seams_ids.end())
  1522. new_id = id + biased_seams_ids.size();
  1523. else {
  1524. if (it == biased_seams_ids.begin() && *it < id)
  1525. new_id = id;
  1526. else if (it != biased_seams_ids.begin())
  1527. new_id = id + std::distance(biased_seams_ids.begin(), it);
  1528. }
  1529. return (new_id == size_t(-1)) ? id : new_id;
  1530. };
  1531. const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats();
  1532. for (const Path& path : t_buffer.paths) {
  1533. // the two segments of the path sharing the current vertex may belong
  1534. // to two different vertex buffers
  1535. size_t prev_sub_path_id = 0;
  1536. size_t next_sub_path_id = 0;
  1537. const size_t path_vertices_count = path.vertices_count();
  1538. const float half_width = 0.5f * path.width;
  1539. for (size_t j = 1; j < path_vertices_count - 1; ++j) {
  1540. const size_t curr_s_id = path.sub_paths.front().first.s_id + j;
  1541. const size_t move_id = extract_move_id(curr_s_id);
  1542. const Vec3f& prev = gcode_result.moves[move_id - 1].position;
  1543. const Vec3f& curr = gcode_result.moves[move_id].position;
  1544. const Vec3f& next = gcode_result.moves[move_id + 1].position;
  1545. // select the subpaths which contains the previous/next segments
  1546. if (!path.sub_paths[prev_sub_path_id].contains(curr_s_id))
  1547. ++prev_sub_path_id;
  1548. if (!path.sub_paths[next_sub_path_id].contains(curr_s_id + 1))
  1549. ++next_sub_path_id;
  1550. const Path::Sub_Path& prev_sub_path = path.sub_paths[prev_sub_path_id];
  1551. const Path::Sub_Path& next_sub_path = path.sub_paths[next_sub_path_id];
  1552. const Vec3f prev_dir = (curr - prev).normalized();
  1553. const Vec3f prev_right = Vec3f(prev_dir.y(), -prev_dir.x(), 0.0f).normalized();
  1554. const Vec3f prev_up = prev_right.cross(prev_dir);
  1555. const Vec3f next_dir = (next - curr).normalized();
  1556. const bool is_right_turn = prev_up.dot(prev_dir.cross(next_dir)) <= 0.0f;
  1557. const float cos_dir = prev_dir.dot(next_dir);
  1558. // whether the angle between adjacent segments is greater than 45 degrees
  1559. const bool is_sharp = cos_dir < 0.7071068f;
  1560. float displacement = 0.0f;
  1561. if (cos_dir > -0.9998477f) {
  1562. // if the angle between adjacent segments is smaller than 179 degrees
  1563. const Vec3f med_dir = (prev_dir + next_dir).normalized();
  1564. displacement = half_width * ::tan(::acos(std::clamp(next_dir.dot(med_dir), -1.0f, 1.0f)));
  1565. }
  1566. const float sq_prev_length = (curr - prev).squaredNorm();
  1567. const float sq_next_length = (next - curr).squaredNorm();
  1568. const float sq_displacement = sqr(displacement);
  1569. const bool can_displace = displacement > 0.0f && sq_displacement < sq_prev_length && sq_displacement < sq_next_length;
  1570. if (can_displace) {
  1571. // displacement to apply to the vertices to match
  1572. const Vec3f displacement_vec = displacement * prev_dir;
  1573. // matches inner corner vertices
  1574. if (is_right_turn)
  1575. match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec);
  1576. else
  1577. match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, -displacement_vec);
  1578. if (!is_sharp) {
  1579. // matches outer corner vertices
  1580. if (is_right_turn)
  1581. match_left_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec);
  1582. else
  1583. match_right_vertices(prev_sub_path, next_sub_path, curr_s_id, vertex_size_floats, displacement_vec);
  1584. }
  1585. }
  1586. }
  1587. }
  1588. };
  1589. #if ENABLE_GCODE_VIEWER_STATISTICS
  1590. auto load_vertices_time = std::chrono::high_resolution_clock::now();
  1591. m_statistics.load_vertices = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
  1592. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1593. // smooth toolpaths corners for TBuffers using triangles
  1594. for (size_t i = 0; i < m_buffers.size(); ++i) {
  1595. const TBuffer& t_buffer = m_buffers[i];
  1596. if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle)
  1597. smooth_triangle_toolpaths_corners(t_buffer, vertices[i]);
  1598. }
  1599. // dismiss, no more needed
  1600. std::vector<size_t>().swap(biased_seams_ids);
  1601. for (MultiVertexBuffer& v_multibuffer : vertices) {
  1602. for (VertexBuffer& v_buffer : v_multibuffer) {
  1603. v_buffer.shrink_to_fit();
  1604. }
  1605. }
  1606. // move the wipe toolpaths half height up to render them on proper position
  1607. MultiVertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)];
  1608. for (VertexBuffer& v_buffer : wipe_vertices) {
  1609. for (size_t i = 2; i < v_buffer.size(); i += 3) {
  1610. v_buffer[i] += 0.5f * GCodeProcessor::Wipe_Height;
  1611. }
  1612. }
  1613. // send vertices data to gpu, where needed
  1614. for (size_t i = 0; i < m_buffers.size(); ++i) {
  1615. TBuffer& t_buffer = m_buffers[i];
  1616. if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
  1617. const InstanceBuffer& inst_buffer = instances[i];
  1618. if (!inst_buffer.empty()) {
  1619. t_buffer.model.instances.buffer = inst_buffer;
  1620. t_buffer.model.instances.s_ids = instances_ids[i];
  1621. t_buffer.model.instances.offsets = instances_offsets[i];
  1622. }
  1623. }
  1624. else {
  1625. if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
  1626. const InstanceBuffer& inst_buffer = instances[i];
  1627. if (!inst_buffer.empty()) {
  1628. t_buffer.model.instances.buffer = inst_buffer;
  1629. t_buffer.model.instances.s_ids = instances_ids[i];
  1630. t_buffer.model.instances.offsets = instances_offsets[i];
  1631. }
  1632. }
  1633. const MultiVertexBuffer& v_multibuffer = vertices[i];
  1634. for (const VertexBuffer& v_buffer : v_multibuffer) {
  1635. const size_t size_elements = v_buffer.size();
  1636. const size_t size_bytes = size_elements * sizeof(float);
  1637. const size_t vertices_count = size_elements / t_buffer.vertices.vertex_size_floats();
  1638. t_buffer.vertices.count += vertices_count;
  1639. #if ENABLE_GCODE_VIEWER_STATISTICS
  1640. m_statistics.total_vertices_gpu_size += static_cast<int64_t>(size_bytes);
  1641. m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast<int64_t>(size_bytes));
  1642. ++m_statistics.vbuffers_count;
  1643. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1644. #if ENABLE_GL_CORE_PROFILE
  1645. GLuint vao_id = 0;
  1646. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) {
  1647. glsafe(::glGenVertexArrays(1, &vao_id));
  1648. glsafe(::glBindVertexArray(vao_id));
  1649. }
  1650. #endif // ENABLE_GL_CORE_PROFILE
  1651. GLuint vbo_id = 0;
  1652. glsafe(::glGenBuffers(1, &vbo_id));
  1653. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, vbo_id));
  1654. glsafe(::glBufferData(GL_ARRAY_BUFFER, size_bytes, v_buffer.data(), GL_STATIC_DRAW));
  1655. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  1656. #if ENABLE_GL_CORE_PROFILE
  1657. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) {
  1658. glsafe(::glBindVertexArray(0));
  1659. t_buffer.vertices.vaos.push_back(static_cast<unsigned int>(vao_id));
  1660. }
  1661. #endif // ENABLE_GL_CORE_PROFILE
  1662. t_buffer.vertices.vbos.push_back(static_cast<unsigned int>(vbo_id));
  1663. t_buffer.vertices.sizes.push_back(size_bytes);
  1664. }
  1665. }
  1666. }
  1667. #if ENABLE_GCODE_VIEWER_STATISTICS
  1668. auto smooth_vertices_time = std::chrono::high_resolution_clock::now();
  1669. m_statistics.smooth_vertices = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - load_vertices_time).count();
  1670. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1671. log_memory_usage("Loaded G-code generated vertex buffers ", vertices, indices);
  1672. // dismiss vertices data, no more needed
  1673. std::vector<MultiVertexBuffer>().swap(vertices);
  1674. std::vector<InstanceBuffer>().swap(instances);
  1675. std::vector<InstanceIdBuffer>().swap(instances_ids);
  1676. // toolpaths data -> extract indices from result
  1677. // paths may have been filled while extracting vertices,
  1678. // so reset them, they will be filled again while extracting indices
  1679. for (TBuffer& buffer : m_buffers) {
  1680. buffer.paths.clear();
  1681. }
  1682. // variable used to keep track of the current vertex buffers index and size
  1683. using CurrVertexBuffer = std::pair<unsigned int, size_t>;
  1684. std::vector<CurrVertexBuffer> curr_vertex_buffers(m_buffers.size(), { 0, 0 });
  1685. #if ENABLE_GL_CORE_PROFILE
  1686. // variable used to keep track of the vertex buffers ids
  1687. using VIndexList = std::vector<unsigned int>;
  1688. std::vector<VIndexList> vao_indices(m_buffers.size());
  1689. std::vector<VIndexList> vbo_indices(m_buffers.size());
  1690. #else
  1691. // variable used to keep track of the vertex buffers ids
  1692. using VboIndexList = std::vector<unsigned int>;
  1693. std::vector<VboIndexList> vbo_indices(m_buffers.size());
  1694. #endif // ENABLE_GL_CORE_PROFILE
  1695. size_t seams_count = 0;
  1696. for (size_t i = 0; i < m_moves_count; ++i) {
  1697. const GCodeProcessorResult::MoveVertex& curr = gcode_result.moves[i];
  1698. if (curr.type == EMoveType::Seam)
  1699. ++seams_count;
  1700. const size_t move_id = i - seams_count;
  1701. // skip first vertex
  1702. if (i == 0)
  1703. continue;
  1704. const GCodeProcessorResult::MoveVertex& prev = gcode_result.moves[i - 1];
  1705. const GCodeProcessorResult::MoveVertex* next = nullptr;
  1706. if (i < m_moves_count - 1)
  1707. next = &gcode_result.moves[i + 1];
  1708. ++progress_count;
  1709. if (progress_dialog != nullptr && progress_count % progress_threshold == 0) {
  1710. progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))),
  1711. _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%");
  1712. progress_dialog->Fit();
  1713. progress_count = 0;
  1714. }
  1715. const unsigned char id = buffer_id(curr.type);
  1716. TBuffer& t_buffer = m_buffers[id];
  1717. MultiIndexBuffer& i_multibuffer = indices[id];
  1718. CurrVertexBuffer& curr_vertex_buffer = curr_vertex_buffers[id];
  1719. #if ENABLE_GL_CORE_PROFILE
  1720. VIndexList& vao_index_list = vao_indices[id];
  1721. VIndexList& vbo_index_list = vbo_indices[id];
  1722. #else
  1723. VboIndexList& vbo_index_list = vbo_indices[id];
  1724. #endif // ENABLE_GL_CORE_PROFILE
  1725. // ensure there is at least one index buffer
  1726. if (i_multibuffer.empty()) {
  1727. i_multibuffer.push_back(IndexBuffer());
  1728. #if ENABLE_GL_CORE_PROFILE
  1729. if (!t_buffer.vertices.vaos.empty() && OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  1730. vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]);
  1731. if (!t_buffer.vertices.vbos.empty())
  1732. vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
  1733. #else
  1734. if (!t_buffer.vertices.vbos.empty())
  1735. vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
  1736. #endif // ENABLE_GL_CORE_PROFILE
  1737. }
  1738. // if adding the indices for the current segment exceeds the threshold size of the current index buffer
  1739. // create another index buffer
  1740. size_t indiced_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.indices_size_bytes() : t_buffer.max_indices_per_segment_size_bytes();
  1741. if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - indiced_size_to_add) {
  1742. i_multibuffer.push_back(IndexBuffer());
  1743. #if ENABLE_GL_CORE_PROFILE
  1744. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  1745. vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]);
  1746. #endif // ENABLE_GL_CORE_PROFILE
  1747. vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
  1748. if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) {
  1749. Path& last_path = t_buffer.paths.back();
  1750. last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
  1751. }
  1752. }
  1753. // if adding the vertices for the current segment exceeds the threshold size of the current vertex buffer
  1754. // create another index buffer
  1755. size_t vertices_size_to_add = (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) ? t_buffer.model.data.vertices_size_bytes() : t_buffer.max_vertices_per_segment_size_bytes();
  1756. if (curr_vertex_buffer.second * t_buffer.vertices.vertex_size_bytes() > t_buffer.vertices.max_size_bytes() - vertices_size_to_add) {
  1757. i_multibuffer.push_back(IndexBuffer());
  1758. ++curr_vertex_buffer.first;
  1759. curr_vertex_buffer.second = 0;
  1760. #if ENABLE_GL_CORE_PROFILE
  1761. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  1762. vao_index_list.push_back(t_buffer.vertices.vaos[curr_vertex_buffer.first]);
  1763. #endif // ENABLE_GL_CORE_PROFILE
  1764. vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]);
  1765. if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel) {
  1766. Path& last_path = t_buffer.paths.back();
  1767. last_path.add_sub_path(prev, static_cast<unsigned int>(i_multibuffer.size()) - 1, 0, move_id - 1);
  1768. }
  1769. }
  1770. IndexBuffer& i_buffer = i_multibuffer.back();
  1771. switch (t_buffer.render_primitive_type)
  1772. {
  1773. case TBuffer::ERenderPrimitiveType::Line: {
  1774. add_indices_as_line(prev, curr, t_buffer, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate);
  1775. curr_vertex_buffer.second += t_buffer.max_vertices_per_segment();
  1776. break;
  1777. }
  1778. case TBuffer::ERenderPrimitiveType::Triangle: {
  1779. add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast<unsigned int>(i_multibuffer.size()) - 1, i_buffer, move_id, account_for_volumetric_rate);
  1780. break;
  1781. }
  1782. case TBuffer::ERenderPrimitiveType::BatchedModel: {
  1783. add_indices_as_model_batch(t_buffer.model.data, i_buffer, curr_vertex_buffer.second);
  1784. curr_vertex_buffer.second += t_buffer.model.data.vertices_count();
  1785. break;
  1786. }
  1787. default: { break; }
  1788. }
  1789. }
  1790. for (MultiIndexBuffer& i_multibuffer : indices) {
  1791. for (IndexBuffer& i_buffer : i_multibuffer) {
  1792. i_buffer.shrink_to_fit();
  1793. }
  1794. }
  1795. // toolpaths data -> send indices data to gpu
  1796. for (size_t i = 0; i < m_buffers.size(); ++i) {
  1797. TBuffer& t_buffer = m_buffers[i];
  1798. if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel) {
  1799. const MultiIndexBuffer& i_multibuffer = indices[i];
  1800. for (const IndexBuffer& i_buffer : i_multibuffer) {
  1801. const size_t size_elements = i_buffer.size();
  1802. const size_t size_bytes = size_elements * sizeof(IBufferType);
  1803. // stores index buffer informations into TBuffer
  1804. t_buffer.indices.push_back(IBuffer());
  1805. IBuffer& ibuf = t_buffer.indices.back();
  1806. ibuf.count = size_elements;
  1807. #if ENABLE_GL_CORE_PROFILE
  1808. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  1809. ibuf.vao = vao_indices[i][t_buffer.indices.size() - 1];
  1810. #endif // ENABLE_GL_CORE_PROFILE
  1811. ibuf.vbo = vbo_indices[i][t_buffer.indices.size() - 1];
  1812. #if ENABLE_GCODE_VIEWER_STATISTICS
  1813. m_statistics.total_indices_gpu_size += static_cast<int64_t>(size_bytes);
  1814. m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast<int64_t>(size_bytes));
  1815. ++m_statistics.ibuffers_count;
  1816. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1817. glsafe(::glGenBuffers(1, &ibuf.ibo));
  1818. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuf.ibo));
  1819. glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, size_bytes, i_buffer.data(), GL_STATIC_DRAW));
  1820. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  1821. }
  1822. }
  1823. }
  1824. if (progress_dialog != nullptr) {
  1825. progress_dialog->Update(100, "");
  1826. progress_dialog->Fit();
  1827. }
  1828. #if ENABLE_GCODE_VIEWER_STATISTICS
  1829. for (const TBuffer& buffer : m_buffers) {
  1830. m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path);
  1831. }
  1832. auto update_segments_count = [&](EMoveType type, int64_t& count) {
  1833. unsigned int id = buffer_id(type);
  1834. const MultiIndexBuffer& buffers = indices[id];
  1835. int64_t indices_count = 0;
  1836. for (const IndexBuffer& buffer : buffers) {
  1837. indices_count += buffer.size();
  1838. }
  1839. const TBuffer& t_buffer = m_buffers[id];
  1840. if (t_buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle)
  1841. indices_count -= static_cast<int64_t>(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles
  1842. count += indices_count / t_buffer.indices_per_segment();
  1843. };
  1844. update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count);
  1845. update_segments_count(EMoveType::Wipe, m_statistics.wipe_segments_count);
  1846. update_segments_count(EMoveType::Extrude, m_statistics.extrude_segments_count);
  1847. m_statistics.load_indices = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - smooth_vertices_time).count();
  1848. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1849. log_memory_usage("Loaded G-code generated indices buffers ", vertices, indices);
  1850. // dismiss indices data, no more needed
  1851. std::vector<MultiIndexBuffer>().swap(indices);
  1852. // layers zs / roles / extruder ids -> extract from result
  1853. size_t last_travel_s_id = 0;
  1854. size_t first_travel_s_id = 0;
  1855. seams_count = 0;
  1856. for (size_t i = 0; i < m_moves_count; ++i) {
  1857. const GCodeProcessorResult::MoveVertex& move = gcode_result.moves[i];
  1858. if (move.type == EMoveType::Seam)
  1859. ++seams_count;
  1860. size_t move_id = i - seams_count;
  1861. if (move.type == EMoveType::Extrude) {
  1862. if (!move.internal_only) {
  1863. // layers zs
  1864. const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back();
  1865. const double z = static_cast<double>(move.position.z());
  1866. if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) {
  1867. const size_t start_it = (m_layers.empty() && first_travel_s_id != 0) ? first_travel_s_id : last_travel_s_id;
  1868. m_layers.append(z, { start_it, move_id });
  1869. }
  1870. else
  1871. m_layers.get_ranges().back().last = move_id;
  1872. }
  1873. // extruder ids
  1874. m_extruder_ids.emplace_back(move.extruder_id);
  1875. // roles
  1876. if (i > 0)
  1877. m_roles.emplace_back(move.extrusion_role);
  1878. }
  1879. else if (move.type == EMoveType::Travel) {
  1880. if (move_id - last_travel_s_id > 1 && !m_layers.empty())
  1881. m_layers.get_ranges().back().last = move_id;
  1882. else if (m_layers.empty() && first_travel_s_id == 0)
  1883. first_travel_s_id = move_id;
  1884. last_travel_s_id = move_id;
  1885. }
  1886. }
  1887. // roles -> remove duplicates
  1888. sort_remove_duplicates(m_roles);
  1889. m_roles.shrink_to_fit();
  1890. // extruder ids -> remove duplicates
  1891. sort_remove_duplicates(m_extruder_ids);
  1892. m_extruder_ids.shrink_to_fit();
  1893. // replace layers for spiral vase mode
  1894. if (!gcode_result.spiral_vase_layers.empty()) {
  1895. m_layers.reset();
  1896. for (const auto& layer : gcode_result.spiral_vase_layers) {
  1897. m_layers.append(layer.first, { layer.second.first, layer.second.second });
  1898. }
  1899. }
  1900. // set layers z range
  1901. if (!m_layers.empty())
  1902. m_layers_z_range = { 0, static_cast<unsigned int>(m_layers.size() - 1) };
  1903. // change color of paths whose layer contains option points
  1904. if (!options_zs.empty()) {
  1905. TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)];
  1906. for (Path& path : extrude_buffer.paths) {
  1907. const float z = path.sub_paths.front().first.position.z();
  1908. if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end())
  1909. path.cp_color_id = 255 - path.cp_color_id;
  1910. }
  1911. }
  1912. #if ENABLE_GCODE_VIEWER_STATISTICS
  1913. m_statistics.load_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
  1914. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1915. if (progress_dialog != nullptr)
  1916. progress_dialog->Destroy();
  1917. }
  1918. void GCodeViewer::load_shells(const Print& print)
  1919. {
  1920. m_shells.volumes.clear();
  1921. if (print.objects().empty())
  1922. // no shells, return
  1923. return;
  1924. // adds objects' volumes
  1925. int object_id = 0;
  1926. for (const PrintObject* obj : print.objects()) {
  1927. const ModelObject* model_obj = obj->model_object();
  1928. std::vector<int> instance_ids(model_obj->instances.size());
  1929. for (int i = 0; i < (int)model_obj->instances.size(); ++i) {
  1930. instance_ids[i] = i;
  1931. }
  1932. size_t current_volumes_count = m_shells.volumes.volumes.size();
  1933. m_shells.volumes.load_object(model_obj, object_id, instance_ids);
  1934. // adjust shells' z if raft is present
  1935. const SlicingParameters& slicing_parameters = obj->slicing_parameters();
  1936. if (slicing_parameters.object_print_z_min != 0.0) {
  1937. const Vec3d z_offset = slicing_parameters.object_print_z_min * Vec3d::UnitZ();
  1938. for (size_t i = current_volumes_count; i < m_shells.volumes.volumes.size(); ++i) {
  1939. GLVolume* v = m_shells.volumes.volumes[i];
  1940. v->set_volume_offset(v->get_volume_offset() + z_offset);
  1941. }
  1942. }
  1943. ++object_id;
  1944. }
  1945. if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) {
  1946. // adds wipe tower's volume
  1947. const double max_z = print.objects()[0]->model_object()->get_model()->max_z();
  1948. const PrintConfig& config = print.config();
  1949. const size_t extruders_count = config.nozzle_diameter.size();
  1950. if (extruders_count > 1 && config.wipe_tower && !config.complete_objects) {
  1951. const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count);
  1952. const float depth = wipe_tower_data.depth;
  1953. const std::vector<std::pair<float, float>> z_and_depth_pairs = print.wipe_tower_data(extruders_count).z_and_depth_pairs;
  1954. const float brim_width = wipe_tower_data.brim_width;
  1955. if (depth != 0.)
  1956. m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, z_and_depth_pairs, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle,
  1957. !print.is_step_done(psWipeTower), brim_width);
  1958. }
  1959. }
  1960. // remove modifiers
  1961. while (true) {
  1962. GLVolumePtrs::iterator it = std::find_if(m_shells.volumes.volumes.begin(), m_shells.volumes.volumes.end(), [](GLVolume* volume) { return volume->is_modifier; });
  1963. if (it != m_shells.volumes.volumes.end()) {
  1964. delete (*it);
  1965. m_shells.volumes.volumes.erase(it);
  1966. }
  1967. else
  1968. break;
  1969. }
  1970. for (GLVolume* volume : m_shells.volumes.volumes) {
  1971. volume->zoom_to_volumes = false;
  1972. volume->color.a(0.25f);
  1973. volume->force_native_color = true;
  1974. volume->set_render_color(true);
  1975. }
  1976. m_shells_bounding_box.reset();
  1977. for (const GLVolume* volume : m_shells.volumes.volumes) {
  1978. m_shells_bounding_box.merge(volume->transformed_bounding_box());
  1979. }
  1980. m_max_bounding_box.reset();
  1981. }
  1982. void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const
  1983. {
  1984. #if ENABLE_GCODE_VIEWER_STATISTICS
  1985. auto start_time = std::chrono::high_resolution_clock::now();
  1986. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  1987. auto extrusion_color = [this](const Path& path) {
  1988. ColorRGBA color;
  1989. switch (m_view_type)
  1990. {
  1991. case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast<unsigned int>(path.role)]; break; }
  1992. case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; }
  1993. case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; }
  1994. case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; }
  1995. case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; }
  1996. case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; }
  1997. case EViewType::LayerTimeLinear:
  1998. case EViewType::LayerTimeLogarithmic: {
  1999. const Path::Sub_Path& sub_path = path.sub_paths.front();
  2000. double z = static_cast<double>(sub_path.first.position.z());
  2001. const std::vector<double>& zs = m_layers.get_zs();
  2002. const std::vector<Layers::Range>& ranges = m_layers.get_ranges();
  2003. size_t time_mode_id = static_cast<size_t>(m_time_estimate_mode);
  2004. for (size_t i = 0; i < zs.size(); ++i) {
  2005. if (std::abs(zs[i] - z) < EPSILON) {
  2006. if (ranges[i].contains(sub_path.first.s_id)) {
  2007. color = m_extrusions.ranges.layer_time[time_mode_id].get_color_at(m_layers_times[time_mode_id][i],
  2008. (m_view_type == EViewType::LayerTimeLinear) ? Extrusions::Range::EType::Linear : Extrusions::Range::EType::Logarithmic);
  2009. break;
  2010. }
  2011. }
  2012. }
  2013. break;
  2014. }
  2015. case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; }
  2016. case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; }
  2017. case EViewType::ColorPrint: {
  2018. if (path.cp_color_id >= static_cast<unsigned char>(m_tool_colors.size()))
  2019. color = ColorRGBA::GRAY();
  2020. else
  2021. color = m_tool_colors[path.cp_color_id];
  2022. break;
  2023. }
  2024. default: { color = ColorRGBA::WHITE(); break; }
  2025. }
  2026. return color;
  2027. };
  2028. auto travel_color = [](const Path& path) {
  2029. return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ :
  2030. ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ :
  2031. Travel_Colors[0] /* Move */);
  2032. };
  2033. auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) {
  2034. auto in_layers_range = [this, min_id, max_id](size_t id) {
  2035. return m_layers.get_range_at(min_id).first <= id && id <= m_layers.get_range_at(max_id).last;
  2036. };
  2037. return in_layers_range(path.sub_paths.front().first.s_id) && in_layers_range(path.sub_paths.back().last.s_id);
  2038. };
  2039. auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) {
  2040. const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)];
  2041. if (path_id >= buffer.paths.size())
  2042. return false;
  2043. Path path = buffer.paths[path_id];
  2044. size_t first = path_id;
  2045. size_t last = path_id;
  2046. // check adjacent paths
  2047. while (first > 0) {
  2048. const Path& ref_path = buffer.paths[first - 1];
  2049. if (!path.sub_paths.front().first.position.isApprox(ref_path.sub_paths.back().last.position) ||
  2050. path.role != ref_path.role)
  2051. break;
  2052. path.sub_paths.front().first = ref_path.sub_paths.front().first;
  2053. --first;
  2054. }
  2055. while (last < buffer.paths.size() - 1) {
  2056. const Path& ref_path = buffer.paths[last + 1];
  2057. if (!path.sub_paths.back().last.position.isApprox(ref_path.sub_paths.front().first.position) ||
  2058. path.role != ref_path.role)
  2059. break;
  2060. path.sub_paths.back().last = ref_path.sub_paths.back().last;
  2061. ++last;
  2062. }
  2063. const size_t min_s_id = m_layers.get_range_at(min_id).first;
  2064. const size_t max_s_id = m_layers.get_range_at(max_id).last;
  2065. return (min_s_id <= path.sub_paths.front().first.s_id && path.sub_paths.front().first.s_id <= max_s_id) ||
  2066. (min_s_id <= path.sub_paths.back().last.s_id && path.sub_paths.back().last.s_id <= max_s_id);
  2067. };
  2068. #if ENABLE_GCODE_VIEWER_STATISTICS
  2069. Statistics* statistics = const_cast<Statistics*>(&m_statistics);
  2070. statistics->render_paths_size = 0;
  2071. statistics->models_instances_size = 0;
  2072. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2073. const bool top_layer_only = get_app_config()->get_bool("seq_top_layer_only");
  2074. SequentialView::Endpoints global_endpoints = { m_moves_count , 0 };
  2075. SequentialView::Endpoints top_layer_endpoints = global_endpoints;
  2076. SequentialView* sequential_view = const_cast<SequentialView*>(&m_sequential_view);
  2077. if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0;
  2078. if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count;
  2079. // first pass: collect visible paths and update sequential view data
  2080. std::vector<std::tuple<unsigned char, unsigned int, unsigned int, unsigned int>> paths;
  2081. for (size_t b = 0; b < m_buffers.size(); ++b) {
  2082. TBuffer& buffer = const_cast<TBuffer&>(m_buffers[b]);
  2083. // reset render paths
  2084. buffer.render_paths.clear();
  2085. if (!buffer.visible)
  2086. continue;
  2087. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
  2088. buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
  2089. for (size_t id : buffer.model.instances.s_ids) {
  2090. if (id < m_layers.get_range_at(m_layers_z_range[0]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id)
  2091. continue;
  2092. global_endpoints.first = std::min(global_endpoints.first, id);
  2093. global_endpoints.last = std::max(global_endpoints.last, id);
  2094. if (top_layer_only) {
  2095. if (id < m_layers.get_range_at(m_layers_z_range[1]).first || m_layers.get_range_at(m_layers_z_range[1]).last < id)
  2096. continue;
  2097. top_layer_endpoints.first = std::min(top_layer_endpoints.first, id);
  2098. top_layer_endpoints.last = std::max(top_layer_endpoints.last, id);
  2099. }
  2100. }
  2101. }
  2102. else {
  2103. for (size_t i = 0; i < buffer.paths.size(); ++i) {
  2104. const Path& path = buffer.paths[i];
  2105. if (path.type == EMoveType::Travel) {
  2106. if (path.sub_paths.front().first.s_id > m_layers_z_range[0]) {
  2107. if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1]))
  2108. continue;
  2109. }
  2110. }
  2111. else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1]))
  2112. continue;
  2113. if (path.type == EMoveType::Extrude && !is_visible(path))
  2114. continue;
  2115. // store valid path
  2116. for (size_t j = 0; j < path.sub_paths.size(); ++j) {
  2117. paths.push_back({ static_cast<unsigned char>(b), path.sub_paths[j].first.b_id, static_cast<unsigned int>(i), static_cast<unsigned int>(j) });
  2118. }
  2119. global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id);
  2120. global_endpoints.last = std::max(global_endpoints.last, path.sub_paths.back().last.s_id);
  2121. if (top_layer_only) {
  2122. if (path.type == EMoveType::Travel) {
  2123. if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) {
  2124. top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
  2125. top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
  2126. }
  2127. }
  2128. else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) {
  2129. top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.sub_paths.front().first.s_id);
  2130. top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.sub_paths.back().last.s_id);
  2131. }
  2132. }
  2133. }
  2134. }
  2135. }
  2136. // update current sequential position
  2137. sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first;
  2138. sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last;
  2139. // get the world position from the vertex buffer
  2140. bool found = false;
  2141. for (const TBuffer& buffer : m_buffers) {
  2142. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel ||
  2143. buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
  2144. for (size_t i = 0; i < buffer.model.instances.s_ids.size(); ++i) {
  2145. if (buffer.model.instances.s_ids[i] == m_sequential_view.current.last) {
  2146. size_t offset = i * buffer.model.instances.instance_size_floats();
  2147. sequential_view->current_position.x() = buffer.model.instances.buffer[offset + 0];
  2148. sequential_view->current_position.y() = buffer.model.instances.buffer[offset + 1];
  2149. sequential_view->current_position.z() = buffer.model.instances.buffer[offset + 2];
  2150. sequential_view->current_offset = buffer.model.instances.offsets[i];
  2151. found = true;
  2152. break;
  2153. }
  2154. }
  2155. }
  2156. else {
  2157. // searches the path containing the current position
  2158. for (const Path& path : buffer.paths) {
  2159. if (path.contains(m_sequential_view.current.last)) {
  2160. const int sub_path_id = path.get_id_of_sub_path_containing(m_sequential_view.current.last);
  2161. if (sub_path_id != -1) {
  2162. const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
  2163. unsigned int offset = static_cast<unsigned int>(m_sequential_view.current.last - sub_path.first.s_id);
  2164. if (offset > 0) {
  2165. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line)
  2166. offset = 2 * offset - 1;
  2167. else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
  2168. unsigned int indices_count = buffer.indices_per_segment();
  2169. offset = indices_count * (offset - 1) + (indices_count - 2);
  2170. if (sub_path_id == 0)
  2171. offset += 6; // add 2 triangles for starting cap
  2172. }
  2173. }
  2174. offset += static_cast<unsigned int>(sub_path.first.i_id);
  2175. // gets the vertex index from the index buffer on gpu
  2176. const IBuffer& i_buffer = buffer.indices[sub_path.first.b_id];
  2177. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
  2178. #if ENABLE_OPENGL_ES
  2179. IBufferType index = *static_cast<IBufferType*>(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER,
  2180. static_cast<GLintptr>(offset * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)),
  2181. GL_MAP_READ_BIT));
  2182. glcheck();
  2183. glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER));
  2184. #else
  2185. IBufferType index = 0;
  2186. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>(offset * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&index)));
  2187. #endif // ENABLE_OPENGL_ES
  2188. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2189. // gets the position from the vertices buffer on gpu
  2190. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
  2191. #if ENABLE_OPENGL_ES
  2192. sequential_view->current_position = *static_cast<Vec3f*>(::glMapBufferRange(GL_ARRAY_BUFFER,
  2193. static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()),
  2194. static_cast<GLsizeiptr>(buffer.vertices.position_size_bytes()), GL_MAP_READ_BIT));
  2195. glcheck();
  2196. glsafe(::glUnmapBuffer(GL_ARRAY_BUFFER));
  2197. #else
  2198. glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast<GLintptr>(index * buffer.vertices.vertex_size_bytes()), static_cast<GLsizeiptr>(3 * sizeof(float)), static_cast<void*>(sequential_view->current_position.data())));
  2199. #endif // ENABLE_OPENGL_ES
  2200. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  2201. sequential_view->current_offset = Vec3f::Zero();
  2202. found = true;
  2203. break;
  2204. }
  2205. }
  2206. }
  2207. }
  2208. if (found)
  2209. break;
  2210. }
  2211. // second pass: filter paths by sequential data and collect them by color
  2212. RenderPath* render_path = nullptr;
  2213. for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) {
  2214. TBuffer& buffer = const_cast<TBuffer&>(m_buffers[tbuffer_id]);
  2215. const Path& path = buffer.paths[path_id];
  2216. const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
  2217. if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first)
  2218. continue;
  2219. ColorRGBA color;
  2220. switch (path.type)
  2221. {
  2222. case EMoveType::Tool_change:
  2223. case EMoveType::Color_change:
  2224. case EMoveType::Pause_Print:
  2225. case EMoveType::Custom_GCode:
  2226. case EMoveType::Retract:
  2227. case EMoveType::Unretract:
  2228. case EMoveType::Seam: { color = option_color(path.type); break; }
  2229. case EMoveType::Extrude: {
  2230. if (!top_layer_only ||
  2231. m_sequential_view.current.last == global_endpoints.last ||
  2232. is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1]))
  2233. color = extrusion_color(path);
  2234. else
  2235. color = Neutral_Color;
  2236. break;
  2237. }
  2238. case EMoveType::Travel: {
  2239. if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1]))
  2240. color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path);
  2241. else
  2242. color = Neutral_Color;
  2243. break;
  2244. }
  2245. case EMoveType::Wipe: { color = Wipe_Color; break; }
  2246. default: { color = { 0.0f, 0.0f, 0.0f, 1.0f }; break; }
  2247. }
  2248. RenderPath key{ tbuffer_id, color, static_cast<unsigned int>(ibuffer_id), path_id };
  2249. if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) {
  2250. buffer.render_paths.emplace_back(key);
  2251. render_path = const_cast<RenderPath*>(&buffer.render_paths.back());
  2252. }
  2253. unsigned int delta_1st = 0;
  2254. if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id)
  2255. delta_1st = static_cast<unsigned int>(m_sequential_view.current.first - sub_path.first.s_id);
  2256. unsigned int size_in_indices = 0;
  2257. switch (buffer.render_primitive_type)
  2258. {
  2259. case TBuffer::ERenderPrimitiveType::Line:
  2260. case TBuffer::ERenderPrimitiveType::Triangle: {
  2261. unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id);
  2262. size_in_indices = buffer.indices_per_segment() * segments_count;
  2263. break;
  2264. }
  2265. default: { break; }
  2266. }
  2267. if (size_in_indices == 0)
  2268. continue;
  2269. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
  2270. if (sub_path_id == 0 && delta_1st == 0)
  2271. size_in_indices += 6; // add 2 triangles for starting cap
  2272. if (sub_path_id == path.sub_paths.size() - 1 && path.sub_paths.back().last.s_id <= m_sequential_view.current.last)
  2273. size_in_indices += 6; // add 2 triangles for ending cap
  2274. if (delta_1st > 0)
  2275. size_in_indices -= 6; // remove 2 triangles for corner cap
  2276. }
  2277. render_path->sizes.push_back(size_in_indices);
  2278. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) {
  2279. delta_1st *= buffer.indices_per_segment();
  2280. if (delta_1st > 0) {
  2281. delta_1st += 6; // skip 2 triangles for corner cap
  2282. if (sub_path_id == 0)
  2283. delta_1st += 6; // skip 2 triangles for starting cap
  2284. }
  2285. }
  2286. render_path->offsets.push_back(static_cast<size_t>((sub_path.first.i_id + delta_1st) * sizeof(IBufferType)));
  2287. #if 0
  2288. // check sizes and offsets against index buffer size on gpu
  2289. GLint buffer_size;
  2290. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indices[render_path->ibuffer_id].ibo));
  2291. glsafe(::glGetBufferParameteriv(GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &buffer_size));
  2292. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2293. if (render_path->offsets.back() + render_path->sizes.back() * sizeof(IBufferType) > buffer_size)
  2294. BOOST_LOG_TRIVIAL(error) << "GCodeViewer::refresh_render_paths: Invalid render path data";
  2295. #endif
  2296. }
  2297. // Removes empty render paths and sort.
  2298. for (size_t b = 0; b < m_buffers.size(); ++b) {
  2299. TBuffer* buffer = const_cast<TBuffer*>(&m_buffers[b]);
  2300. buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(),
  2301. [](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }),
  2302. buffer->render_paths.end());
  2303. }
  2304. // second pass: for buffers using instanced and batched models, update the instances render ranges
  2305. for (size_t b = 0; b < m_buffers.size(); ++b) {
  2306. TBuffer& buffer = const_cast<TBuffer&>(m_buffers[b]);
  2307. if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::InstancedModel &&
  2308. buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::BatchedModel)
  2309. continue;
  2310. buffer.model.instances.render_ranges.reset();
  2311. if (!buffer.visible || buffer.model.instances.s_ids.empty())
  2312. continue;
  2313. buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, buffer.model.color });
  2314. bool has_second_range = top_layer_only && m_sequential_view.current.last != m_sequential_view.global.last;
  2315. if (has_second_range)
  2316. buffer.model.instances.render_ranges.ranges.push_back({ 0, 0, 0, Neutral_Color });
  2317. if (m_sequential_view.current.first <= buffer.model.instances.s_ids.back() && buffer.model.instances.s_ids.front() <= m_sequential_view.current.last) {
  2318. for (size_t id : buffer.model.instances.s_ids) {
  2319. if (has_second_range) {
  2320. if (id < m_sequential_view.endpoints.first) {
  2321. ++buffer.model.instances.render_ranges.ranges.front().offset;
  2322. if (id <= m_sequential_view.current.first)
  2323. ++buffer.model.instances.render_ranges.ranges.back().offset;
  2324. else
  2325. ++buffer.model.instances.render_ranges.ranges.back().count;
  2326. }
  2327. else if (id <= m_sequential_view.current.last)
  2328. ++buffer.model.instances.render_ranges.ranges.front().count;
  2329. else
  2330. break;
  2331. }
  2332. else {
  2333. if (id <= m_sequential_view.current.first)
  2334. ++buffer.model.instances.render_ranges.ranges.front().offset;
  2335. else if (id <= m_sequential_view.current.last)
  2336. ++buffer.model.instances.render_ranges.ranges.front().count;
  2337. else
  2338. break;
  2339. }
  2340. }
  2341. }
  2342. }
  2343. // set sequential data to their final value
  2344. sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints;
  2345. sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first;
  2346. sequential_view->global = global_endpoints;
  2347. // updates sequential range caps
  2348. std::array<SequentialRangeCap, 2>* sequential_range_caps = const_cast<std::array<SequentialRangeCap, 2>*>(&m_sequential_range_caps);
  2349. (*sequential_range_caps)[0].reset();
  2350. (*sequential_range_caps)[1].reset();
  2351. if (m_sequential_view.current.first != m_sequential_view.current.last) {
  2352. for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) {
  2353. TBuffer& buffer = const_cast<TBuffer&>(m_buffers[tbuffer_id]);
  2354. if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle)
  2355. continue;
  2356. const Path& path = buffer.paths[path_id];
  2357. const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id];
  2358. if (m_sequential_view.current.last <= sub_path.first.s_id || sub_path.last.s_id <= m_sequential_view.current.first)
  2359. continue;
  2360. // update cap for first endpoint of current range
  2361. if (m_sequential_view.current.first > sub_path.first.s_id) {
  2362. SequentialRangeCap& cap = (*sequential_range_caps)[0];
  2363. const IBuffer& i_buffer = buffer.indices[ibuffer_id];
  2364. cap.buffer = &buffer;
  2365. #if ENABLE_GL_CORE_PROFILE
  2366. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2367. cap.vao = i_buffer.vao;
  2368. #endif // ENABLE_GL_CORE_PROFILE
  2369. cap.vbo = i_buffer.vbo;
  2370. // calculate offset into the index buffer
  2371. unsigned int offset = sub_path.first.i_id;
  2372. offset += 6; // add 2 triangles for corner cap
  2373. offset += static_cast<unsigned int>(m_sequential_view.current.first - sub_path.first.s_id) * buffer.indices_per_segment();
  2374. if (sub_path_id == 0)
  2375. offset += 6; // add 2 triangles for starting cap
  2376. // extract indices from index buffer
  2377. std::array<IBufferType, 6> indices{ 0, 0, 0, 0, 0, 0 };
  2378. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
  2379. #if ENABLE_OPENGL_ES
  2380. IBufferType* index_ptr = static_cast<IBufferType*>(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER,
  2381. static_cast<GLintptr>(offset * sizeof(IBufferType)), static_cast<GLsizeiptr>(14 * sizeof(IBufferType)),
  2382. GL_MAP_READ_BIT));
  2383. glcheck();
  2384. indices[0] = *(index_ptr + 0);
  2385. indices[1] = *(index_ptr + 7);
  2386. indices[2] = *(index_ptr + 1);
  2387. indices[4] = *(index_ptr + 13);
  2388. glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER));
  2389. #else
  2390. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 0) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[0])));
  2391. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 7) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[1])));
  2392. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 1) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[2])));
  2393. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 13) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[4])));
  2394. #endif // ENABLE_OPENGL_ES
  2395. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2396. indices[3] = indices[0];
  2397. indices[5] = indices[1];
  2398. // send indices to gpu
  2399. glsafe(::glGenBuffers(1, &cap.ibo));
  2400. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo));
  2401. glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW));
  2402. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2403. // extract color from render path
  2404. size_t offset_bytes = offset * sizeof(IBufferType);
  2405. for (const RenderPath& render_path : buffer.render_paths) {
  2406. if (render_path.ibuffer_id == ibuffer_id) {
  2407. for (size_t j = 0; j < render_path.offsets.size(); ++j) {
  2408. if (render_path.contains(offset_bytes)) {
  2409. cap.color = render_path.color;
  2410. break;
  2411. }
  2412. }
  2413. }
  2414. }
  2415. }
  2416. // update cap for last endpoint of current range
  2417. if (m_sequential_view.current.last < sub_path.last.s_id) {
  2418. SequentialRangeCap& cap = (*sequential_range_caps)[1];
  2419. const IBuffer& i_buffer = buffer.indices[ibuffer_id];
  2420. cap.buffer = &buffer;
  2421. #if ENABLE_GL_CORE_PROFILE
  2422. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2423. cap.vao = i_buffer.vao;
  2424. #endif // ENABLE_GL_CORE_PROFILE
  2425. cap.vbo = i_buffer.vbo;
  2426. // calculate offset into the index buffer
  2427. unsigned int offset = sub_path.first.i_id;
  2428. offset += 6; // add 2 triangles for corner cap
  2429. offset += static_cast<unsigned int>(m_sequential_view.current.last - 1 - sub_path.first.s_id) * buffer.indices_per_segment();
  2430. if (sub_path_id == 0)
  2431. offset += 6; // add 2 triangles for starting cap
  2432. // extract indices from index buffer
  2433. std::array<IBufferType, 6> indices{ 0, 0, 0, 0, 0, 0 };
  2434. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
  2435. #if ENABLE_OPENGL_ES
  2436. IBufferType* index_ptr = static_cast<IBufferType*>(::glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER,
  2437. static_cast<GLintptr>(offset * sizeof(IBufferType)), static_cast<GLsizeiptr>(17 * sizeof(IBufferType)),
  2438. GL_MAP_READ_BIT));
  2439. glcheck();
  2440. indices[0] = *(index_ptr + 2);
  2441. indices[1] = *(index_ptr + 4);
  2442. indices[2] = *(index_ptr + 10);
  2443. indices[5] = *(index_ptr + 16);
  2444. glsafe(::glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER));
  2445. #else
  2446. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 2) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[0])));
  2447. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 4) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[1])));
  2448. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 10) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[2])));
  2449. glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLintptr>((offset + 16) * sizeof(IBufferType)), static_cast<GLsizeiptr>(sizeof(IBufferType)), static_cast<void*>(&indices[5])));
  2450. #endif // ENABLE_OPENGL_ES
  2451. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2452. indices[3] = indices[0];
  2453. indices[4] = indices[2];
  2454. // send indices to gpu
  2455. glsafe(::glGenBuffers(1, &cap.ibo));
  2456. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo));
  2457. glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(IBufferType), indices.data(), GL_STATIC_DRAW));
  2458. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2459. // extract color from render path
  2460. size_t offset_bytes = offset * sizeof(IBufferType);
  2461. for (const RenderPath& render_path : buffer.render_paths) {
  2462. if (render_path.ibuffer_id == ibuffer_id) {
  2463. for (size_t j = 0; j < render_path.offsets.size(); ++j) {
  2464. if (render_path.contains(offset_bytes)) {
  2465. cap.color = render_path.color;
  2466. break;
  2467. }
  2468. }
  2469. }
  2470. }
  2471. }
  2472. if ((*sequential_range_caps)[0].is_renderable() && (*sequential_range_caps)[1].is_renderable())
  2473. break;
  2474. }
  2475. }
  2476. wxGetApp().plater()->enable_preview_moves_slider(!paths.empty());
  2477. #if ENABLE_GCODE_VIEWER_STATISTICS
  2478. for (const TBuffer& buffer : m_buffers) {
  2479. statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath);
  2480. for (const RenderPath& path : buffer.render_paths) {
  2481. statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
  2482. statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
  2483. }
  2484. statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.buffer, float);
  2485. statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.s_ids, size_t);
  2486. statistics->models_instances_size += SLIC3R_STDVEC_MEMSIZE(buffer.model.instances.render_ranges.ranges, InstanceVBuffer::Ranges::Range);
  2487. }
  2488. statistics->refresh_paths_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time).count();
  2489. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2490. }
  2491. void GCodeViewer::render_toolpaths()
  2492. {
  2493. const Camera& camera = wxGetApp().plater()->get_camera();
  2494. #if !ENABLE_GL_CORE_PROFILE
  2495. const double zoom = camera.get_zoom();
  2496. #endif // !ENABLE_GL_CORE_PROFILE
  2497. auto render_as_lines = [
  2498. #if ENABLE_GCODE_VIEWER_STATISTICS
  2499. this
  2500. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2501. ](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
  2502. for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
  2503. const RenderPath& path = *it;
  2504. // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
  2505. assert(! path.sizes.empty());
  2506. assert(! path.offsets.empty());
  2507. shader.set_uniform(uniform_color, path.color);
  2508. #if ENABLE_GL_CORE_PROFILE
  2509. const Camera& camera = wxGetApp().plater()->get_camera();
  2510. const std::array<int, 4>& viewport = camera.get_viewport();
  2511. const float zoom = float(camera.get_zoom());
  2512. shader.set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
  2513. shader.set_uniform("width", (zoom < 5.0f) ? 0.5f : (0.5f + 5.0f * (zoom - 5.0f) / (100.0f - 5.0f)));
  2514. shader.set_uniform("gap_size", 0.0f);
  2515. #endif // ENABLE_GL_CORE_PROFILE
  2516. #if ENABLE_OPENGL_ES
  2517. for (size_t i = 0; i < path.sizes.size(); ++i) {
  2518. glsafe(::glDrawElements(GL_LINES, (GLsizei)path.sizes[i], GL_UNSIGNED_SHORT, (const void*)path.offsets[i]));
  2519. }
  2520. #else
  2521. glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
  2522. #endif // ENABLE_OPENGL_ES
  2523. #if ENABLE_GCODE_VIEWER_STATISTICS
  2524. ++m_statistics.gl_multi_lines_calls_count;
  2525. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2526. }
  2527. };
  2528. auto render_as_triangles = [
  2529. #if ENABLE_GCODE_VIEWER_STATISTICS
  2530. this
  2531. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2532. ](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
  2533. for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
  2534. const RenderPath& path = *it;
  2535. // Some OpenGL drivers crash on empty glMultiDrawElements, see GH #7415.
  2536. assert(! path.sizes.empty());
  2537. assert(! path.offsets.empty());
  2538. shader.set_uniform(uniform_color, path.color);
  2539. #if ENABLE_OPENGL_ES
  2540. for (size_t i = 0; i < path.sizes.size(); ++i) {
  2541. glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)path.sizes[i], GL_UNSIGNED_SHORT, (const void*)path.offsets[i]));
  2542. }
  2543. #else
  2544. glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
  2545. #endif // ENABLE_OPENGL_ES
  2546. #if ENABLE_GCODE_VIEWER_STATISTICS
  2547. ++m_statistics.gl_multi_triangles_calls_count;
  2548. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2549. }
  2550. };
  2551. auto render_as_instanced_model = [
  2552. #if ENABLE_GCODE_VIEWER_STATISTICS
  2553. this
  2554. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2555. ](TBuffer& buffer, GLShaderProgram & shader) {
  2556. for (auto& range : buffer.model.instances.render_ranges.ranges) {
  2557. if (range.vbo == 0 && range.count > 0) {
  2558. glsafe(::glGenBuffers(1, &range.vbo));
  2559. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, range.vbo));
  2560. glsafe(::glBufferData(GL_ARRAY_BUFFER, range.count * buffer.model.instances.instance_size_bytes(), (const void*)&buffer.model.instances.buffer[range.offset * buffer.model.instances.instance_size_floats()], GL_STATIC_DRAW));
  2561. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  2562. }
  2563. if (range.vbo > 0) {
  2564. buffer.model.model.set_color(range.color);
  2565. buffer.model.model.render_instanced(range.vbo, range.count);
  2566. #if ENABLE_GCODE_VIEWER_STATISTICS
  2567. ++m_statistics.gl_instanced_models_calls_count;
  2568. m_statistics.total_instances_gpu_size += static_cast<int64_t>(range.count * buffer.model.instances.instance_size_bytes());
  2569. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2570. }
  2571. }
  2572. };
  2573. #if ENABLE_GCODE_VIEWER_STATISTICS
  2574. auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) {
  2575. #else
  2576. auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) {
  2577. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2578. struct Range
  2579. {
  2580. unsigned int first;
  2581. unsigned int last;
  2582. bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; }
  2583. };
  2584. Range buffer_range = { 0, 0 };
  2585. const size_t indices_per_instance = buffer.model.data.indices_count();
  2586. for (size_t j = 0; j < buffer.indices.size(); ++j) {
  2587. const IBuffer& i_buffer = buffer.indices[j];
  2588. buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance;
  2589. #if ENABLE_GL_CORE_PROFILE
  2590. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2591. glsafe(::glBindVertexArray(i_buffer.vao));
  2592. #endif // ENABLE_GL_CORE_PROFILE
  2593. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
  2594. if (position_id != -1) {
  2595. glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
  2596. glsafe(::glEnableVertexAttribArray(position_id));
  2597. }
  2598. const bool has_normals = buffer.vertices.normal_size_floats() > 0;
  2599. if (has_normals) {
  2600. if (normal_id != -1) {
  2601. glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
  2602. glsafe(::glEnableVertexAttribArray(normal_id));
  2603. }
  2604. }
  2605. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
  2606. for (auto& range : buffer.model.instances.render_ranges.ranges) {
  2607. const Range range_range = { range.offset, range.offset + range.count };
  2608. if (range_range.intersects(buffer_range)) {
  2609. shader.set_uniform("uniform_color", range.color);
  2610. const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0;
  2611. const size_t offset_bytes = static_cast<size_t>(offset) * indices_per_instance * sizeof(IBufferType);
  2612. const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) };
  2613. const size_t count = static_cast<size_t>(render_range.last - render_range.first) * indices_per_instance;
  2614. if (count > 0) {
  2615. glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes));
  2616. #if ENABLE_GCODE_VIEWER_STATISTICS
  2617. ++m_statistics.gl_batched_models_calls_count;
  2618. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2619. }
  2620. }
  2621. }
  2622. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2623. if (normal_id != -1)
  2624. glsafe(::glDisableVertexAttribArray(normal_id));
  2625. if (position_id != -1)
  2626. glsafe(::glDisableVertexAttribArray(position_id));
  2627. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  2628. #if ENABLE_GL_CORE_PROFILE
  2629. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2630. glsafe(::glBindVertexArray(0));
  2631. #endif // ENABLE_GL_CORE_PROFILE
  2632. buffer_range.first = buffer_range.last;
  2633. }
  2634. };
  2635. auto line_width = [](double zoom) {
  2636. return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0));
  2637. };
  2638. const unsigned char begin_id = buffer_id(EMoveType::Retract);
  2639. const unsigned char end_id = buffer_id(EMoveType::Count);
  2640. for (unsigned char i = begin_id; i < end_id; ++i) {
  2641. TBuffer& buffer = m_buffers[i];
  2642. if (!buffer.visible || !buffer.has_data())
  2643. continue;
  2644. GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str());
  2645. if (shader == nullptr)
  2646. continue;
  2647. shader->start_using();
  2648. shader->set_uniform("view_model_matrix", camera.get_view_matrix());
  2649. shader->set_uniform("projection_matrix", camera.get_projection_matrix());
  2650. shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity());
  2651. if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) {
  2652. shader->set_uniform("emission_factor", 0.25f);
  2653. render_as_instanced_model(buffer, *shader);
  2654. shader->set_uniform("emission_factor", 0.0f);
  2655. }
  2656. else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) {
  2657. shader->set_uniform("emission_factor", 0.25f);
  2658. const int position_id = shader->get_attrib_location("v_position");
  2659. const int normal_id = shader->get_attrib_location("v_normal");
  2660. render_as_batched_model(buffer, *shader, position_id, normal_id);
  2661. shader->set_uniform("emission_factor", 0.0f);
  2662. }
  2663. else {
  2664. shader->set_uniform("emission_factor", 0.15f);
  2665. const int position_id = shader->get_attrib_location("v_position");
  2666. const int normal_id = shader->get_attrib_location("v_normal");
  2667. const int uniform_color = shader->get_uniform_location("uniform_color");
  2668. auto it_path = buffer.render_paths.begin();
  2669. for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast<unsigned int>(buffer.indices.size()); ++ibuffer_id) {
  2670. const IBuffer& i_buffer = buffer.indices[ibuffer_id];
  2671. // Skip all paths with ibuffer_id < ibuffer_id.
  2672. for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++it_path);
  2673. if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id)
  2674. // Not found. This shall not happen.
  2675. continue;
  2676. #if ENABLE_GL_CORE_PROFILE
  2677. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2678. glsafe(::glBindVertexArray(i_buffer.vao));
  2679. #endif // ENABLE_GL_CORE_PROFILE
  2680. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
  2681. if (position_id != -1) {
  2682. glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
  2683. glsafe(::glEnableVertexAttribArray(position_id));
  2684. }
  2685. const bool has_normals = buffer.vertices.normal_size_floats() > 0;
  2686. if (has_normals) {
  2687. if (normal_id != -1) {
  2688. glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes()));
  2689. glsafe(::glEnableVertexAttribArray(normal_id));
  2690. }
  2691. }
  2692. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
  2693. // Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors.
  2694. switch (buffer.render_primitive_type)
  2695. {
  2696. case TBuffer::ERenderPrimitiveType::Line: {
  2697. #if ENABLE_GL_CORE_PROFILE
  2698. if (!OpenGLManager::get_gl_info().is_core_profile())
  2699. glsafe(::glLineWidth(static_cast<GLfloat>(line_width(camera.get_zoom()))));
  2700. #else
  2701. glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
  2702. #endif // ENABLE_GL_CORE_PROFILE
  2703. render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color);
  2704. break;
  2705. }
  2706. case TBuffer::ERenderPrimitiveType::Triangle: {
  2707. render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color);
  2708. break;
  2709. }
  2710. default: { break; }
  2711. }
  2712. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2713. if (normal_id != -1)
  2714. glsafe(::glDisableVertexAttribArray(normal_id));
  2715. if (position_id != -1)
  2716. glsafe(::glDisableVertexAttribArray(position_id));
  2717. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  2718. #if ENABLE_GL_CORE_PROFILE
  2719. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2720. glsafe(::glBindVertexArray(0));
  2721. #endif // ENABLE_GL_CORE_PROFILE
  2722. }
  2723. }
  2724. shader->stop_using();
  2725. }
  2726. #if ENABLE_GCODE_VIEWER_STATISTICS
  2727. auto render_sequential_range_cap = [this, &camera]
  2728. #else
  2729. auto render_sequential_range_cap = [&camera]
  2730. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2731. (const SequentialRangeCap& cap) {
  2732. const TBuffer* buffer = cap.buffer;
  2733. GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str());
  2734. if (shader == nullptr)
  2735. return;
  2736. shader->start_using();
  2737. shader->set_uniform("view_model_matrix", camera.get_view_matrix());
  2738. shader->set_uniform("projection_matrix", camera.get_projection_matrix());
  2739. shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity());
  2740. const int position_id = shader->get_attrib_location("v_position");
  2741. const int normal_id = shader->get_attrib_location("v_normal");
  2742. #if ENABLE_GL_CORE_PROFILE
  2743. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2744. glsafe(::glBindVertexArray(cap.vao));
  2745. #endif // ENABLE_GL_CORE_PROFILE
  2746. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo));
  2747. if (position_id != -1) {
  2748. glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes()));
  2749. glsafe(::glEnableVertexAttribArray(position_id));
  2750. }
  2751. const bool has_normals = buffer->vertices.normal_size_floats() > 0;
  2752. if (has_normals) {
  2753. if (normal_id != -1) {
  2754. glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes()));
  2755. glsafe(::glEnableVertexAttribArray(normal_id));
  2756. }
  2757. }
  2758. shader->set_uniform("uniform_color", cap.color);
  2759. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo));
  2760. glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr));
  2761. glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
  2762. #if ENABLE_GCODE_VIEWER_STATISTICS
  2763. ++m_statistics.gl_triangles_calls_count;
  2764. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  2765. if (normal_id != -1)
  2766. glsafe(::glDisableVertexAttribArray(normal_id));
  2767. if (position_id != -1)
  2768. glsafe(::glDisableVertexAttribArray(position_id));
  2769. glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
  2770. #if ENABLE_GL_CORE_PROFILE
  2771. if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0))
  2772. glsafe(::glBindVertexArray(0));
  2773. #endif // ENABLE_GL_CORE_PROFILE
  2774. shader->stop_using();
  2775. };
  2776. for (unsigned int i = 0; i < 2; ++i) {
  2777. if (m_sequential_range_caps[i].is_renderable())
  2778. render_sequential_range_cap(m_sequential_range_caps[i]);
  2779. }
  2780. }
  2781. void GCodeViewer::render_shells()
  2782. {
  2783. if (!m_shells.visible || m_shells.volumes.empty())
  2784. return;
  2785. GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
  2786. if (shader == nullptr)
  2787. return;
  2788. shader->start_using();
  2789. shader->set_uniform("emission_factor", 0.1f);
  2790. const Camera& camera = wxGetApp().plater()->get_camera();
  2791. m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, camera.get_view_matrix(), camera.get_projection_matrix());
  2792. shader->set_uniform("emission_factor", 0.0f);
  2793. shader->stop_using();
  2794. }
  2795. void GCodeViewer::render_legend(float& legend_height)
  2796. {
  2797. if (!m_legend_enabled)
  2798. return;
  2799. const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size();
  2800. ImGuiWrapper& imgui = *wxGetApp().imgui();
  2801. imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always);
  2802. ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
  2803. ImGui::SetNextWindowBgAlpha(0.6f);
  2804. const float max_height = 0.75f * static_cast<float>(cnv_size.get_height());
  2805. const float child_height = 0.3333f * max_height;
  2806. ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height });
  2807. imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove);
  2808. enum class EItemType : unsigned char
  2809. {
  2810. Rect,
  2811. Circle,
  2812. Hexagon,
  2813. Line
  2814. };
  2815. const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast<size_t>(m_time_estimate_mode)];
  2816. bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType ||
  2817. m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic ||
  2818. (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()));
  2819. const float icon_size = ImGui::GetTextLineHeight();
  2820. const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight();
  2821. bool imperial_units = wxGetApp().app_config->get_bool("use_inches");
  2822. auto append_item = [icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const ColorRGBA& color, const std::string& label,
  2823. bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array<float, 4>& offsets = { 0.0f, 0.0f, 0.0f, 0.0f },
  2824. double used_filament_m = 0.0, double used_filament_g = 0.0,
  2825. std::function<void()> callback = nullptr) {
  2826. if (!visible)
  2827. ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
  2828. ImDrawList* draw_list = ImGui::GetWindowDrawList();
  2829. ImVec2 pos = ImGui::GetCursorScreenPos();
  2830. switch (type) {
  2831. default:
  2832. case EItemType::Rect: {
  2833. draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
  2834. ImGuiWrapper::to_ImU32(color));
  2835. break;
  2836. }
  2837. case EItemType::Circle: {
  2838. ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
  2839. draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 16);
  2840. break;
  2841. }
  2842. case EItemType::Hexagon: {
  2843. ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size));
  2844. draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGuiWrapper::to_ImU32(color), 6);
  2845. break;
  2846. }
  2847. case EItemType::Line: {
  2848. draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGuiWrapper::to_ImU32(color), 3.0f);
  2849. break;
  2850. }
  2851. }
  2852. // draw text
  2853. ImGui::Dummy({ icon_size, icon_size });
  2854. ImGui::SameLine();
  2855. // localize "g" and "m" units
  2856. const std::string& grams = _u8L("g");
  2857. const std::string& inches = _u8L("in");
  2858. const std::string& metres = _CTX_utf8(L_CONTEXT("m", "Metre"), "Metre");
  2859. if (callback != nullptr) {
  2860. if (ImGui::MenuItem(label.c_str()))
  2861. callback();
  2862. else {
  2863. // show tooltip
  2864. if (ImGui::IsItemHovered()) {
  2865. if (!visible)
  2866. ImGui::PopStyleVar();
  2867. ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
  2868. ImGui::BeginTooltip();
  2869. imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show"));
  2870. ImGui::EndTooltip();
  2871. ImGui::PopStyleColor();
  2872. if (!visible)
  2873. ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
  2874. // to avoid the tooltip to change size when moving the mouse
  2875. imgui.set_requires_extra_frame();
  2876. }
  2877. }
  2878. if (!time.empty()) {
  2879. ImGui::SameLine(offsets[0]);
  2880. imgui.text(time);
  2881. ImGui::SameLine(offsets[1]);
  2882. pos = ImGui::GetCursorScreenPos();
  2883. const float width = std::max(1.0f, percent_bar_size * percent / max_percent);
  2884. draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
  2885. ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
  2886. ImGui::Dummy({ percent_bar_size, icon_size });
  2887. ImGui::SameLine();
  2888. char buf[64];
  2889. ::sprintf(buf, "%.1f%%", 100.0f * percent);
  2890. ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
  2891. ImGui::SameLine(offsets[2]);
  2892. imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres)));
  2893. ImGui::SameLine(offsets[3]);
  2894. imgui.text(format("%1$.2f %2%", used_filament_g, grams));
  2895. }
  2896. }
  2897. else {
  2898. imgui.text(label);
  2899. if (!time.empty()) {
  2900. ImGui::SameLine(offsets[0]);
  2901. imgui.text(time);
  2902. ImGui::SameLine(offsets[1]);
  2903. pos = ImGui::GetCursorScreenPos();
  2904. const float width = std::max(1.0f, percent_bar_size * percent / max_percent);
  2905. draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f },
  2906. ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT));
  2907. ImGui::Dummy({ percent_bar_size, icon_size });
  2908. ImGui::SameLine();
  2909. char buf[64];
  2910. ::sprintf(buf, "%.1f%%", 100.0f * percent);
  2911. ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
  2912. }
  2913. else if (used_filament_m > 0.0) {
  2914. ImGui::SameLine(offsets[0]);
  2915. imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres)));
  2916. ImGui::SameLine(offsets[1]);
  2917. imgui.text(format("%1$.2f %2%", used_filament_g, grams));
  2918. }
  2919. }
  2920. if (!visible)
  2921. ImGui::PopStyleVar();
  2922. };
  2923. auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) {
  2924. auto append_range_item = [append_item](int i, float value, unsigned int decimals) {
  2925. char buf[1024];
  2926. ::sprintf(buf, "%.*f", decimals, value);
  2927. append_item(EItemType::Rect, Range_Colors[i], buf);
  2928. };
  2929. if (range.count == 1)
  2930. // single item use case
  2931. append_range_item(0, range.min, decimals);
  2932. else if (range.count == 2) {
  2933. // two items use case
  2934. append_range_item(static_cast<int>(Range_Colors.size()) - 1, range.max, decimals);
  2935. append_range_item(0, range.min, decimals);
  2936. }
  2937. else {
  2938. const float step_size = range.step_size();
  2939. for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
  2940. append_range_item(i, range.min + static_cast<float>(i) * step_size, decimals);
  2941. }
  2942. }
  2943. };
  2944. auto append_time_range = [append_item](const Extrusions::Range& range, Extrusions::Range::EType type) {
  2945. auto append_range_item = [append_item](int i, float value) {
  2946. std::string str_value = get_time_dhms(value);
  2947. if (str_value == "0s")
  2948. str_value = "< 1s";
  2949. append_item(EItemType::Rect, Range_Colors[i], str_value);
  2950. };
  2951. if (range.count == 1)
  2952. // single item use case
  2953. append_range_item(0, range.min);
  2954. else if (range.count == 2) {
  2955. // two items use case
  2956. append_range_item(static_cast<int>(Range_Colors.size()) - 1, range.max);
  2957. append_range_item(0, range.min);
  2958. }
  2959. else {
  2960. float step_size = range.step_size(type);
  2961. for (int i = static_cast<int>(Range_Colors.size()) - 1; i >= 0; --i) {
  2962. float value = 0.0f;
  2963. switch (type)
  2964. {
  2965. default:
  2966. case Extrusions::Range::EType::Linear: { value = range.min + static_cast<float>(i) * step_size; break; }
  2967. case Extrusions::Range::EType::Logarithmic: { value = ::exp(::log(range.min) + static_cast<float>(i) * step_size); break; }
  2968. }
  2969. append_range_item(i, value);
  2970. }
  2971. }
  2972. };
  2973. auto append_headers = [&imgui](const std::array<std::string, 5>& texts, const std::array<float, 4>& offsets) {
  2974. size_t i = 0;
  2975. for (; i < offsets.size(); i++) {
  2976. imgui.text(texts[i]);
  2977. ImGui::SameLine(offsets[i]);
  2978. }
  2979. imgui.text(texts[i]);
  2980. ImGui::Separator();
  2981. };
  2982. auto max_width = [](const std::vector<std::string>& items, const std::string& title, float extra_size = 0.0f) {
  2983. float ret = ImGui::CalcTextSize(title.c_str()).x;
  2984. for (const std::string& item : items) {
  2985. ret = std::max(ret, extra_size + ImGui::CalcTextSize(item.c_str()).x);
  2986. }
  2987. return ret;
  2988. };
  2989. auto calculate_offsets = [max_width](const std::vector<std::string>& labels, const std::vector<std::string>& times,
  2990. const std::array<std::string, 4>& titles, float extra_size = 0.0f) {
  2991. const ImGuiStyle& style = ImGui::GetStyle();
  2992. std::array<float, 4> ret = { 0.0f, 0.0f, 0.0f, 0.0f };
  2993. ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x;
  2994. for (size_t i = 1; i < titles.size(); i++)
  2995. ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x;
  2996. return ret;
  2997. };
  2998. auto color_print_ranges = [this](unsigned char extruder_id, const std::vector<CustomGCode::Item>& custom_gcode_per_print_z) {
  2999. std::vector<std::pair<ColorRGBA, std::pair<double, double>>> ret;
  3000. ret.reserve(custom_gcode_per_print_z.size());
  3001. for (const auto& item : custom_gcode_per_print_z) {
  3002. if (extruder_id + 1 != static_cast<unsigned char>(item.extruder))
  3003. continue;
  3004. if (item.type != ColorChange)
  3005. continue;
  3006. const std::vector<double> zs = m_layers.get_zs();
  3007. auto lower_b = std::lower_bound(zs.begin(), zs.end(), item.print_z - Slic3r::DoubleSlider::epsilon());
  3008. if (lower_b == zs.end())
  3009. continue;
  3010. const double current_z = *lower_b;
  3011. const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b);
  3012. // to avoid duplicate values, check adding values
  3013. if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) {
  3014. ColorRGBA color;
  3015. decode_color(item.color, color);
  3016. ret.push_back({ color, { previous_z, current_z } });
  3017. }
  3018. }
  3019. return ret;
  3020. };
  3021. auto upto_label = [](double z) {
  3022. char buf[64];
  3023. ::sprintf(buf, "%.2f", z);
  3024. return _u8L("up to") + " " + std::string(buf) + " " + _u8L("mm");
  3025. };
  3026. auto above_label = [](double z) {
  3027. char buf[64];
  3028. ::sprintf(buf, "%.2f", z);
  3029. return _u8L("above") + " " + std::string(buf) + " " + _u8L("mm");
  3030. };
  3031. auto fromto_label = [](double z1, double z2) {
  3032. char buf1[64];
  3033. ::sprintf(buf1, "%.2f", z1);
  3034. char buf2[64];
  3035. ::sprintf(buf2, "%.2f", z2);
  3036. return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm");
  3037. };
  3038. auto role_time_and_percent = [time_mode](GCodeExtrusionRole role) {
  3039. auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair<GCodeExtrusionRole, float>& item) { return role == item.first; });
  3040. return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f);
  3041. };
  3042. auto used_filament_per_role = [this, imperial_units](GCodeExtrusionRole role) {
  3043. auto it = m_print_statistics.used_filaments_per_role.find(role);
  3044. if (it == m_print_statistics.used_filaments_per_role.end())
  3045. return std::make_pair(0.0, 0.0);
  3046. double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0;
  3047. return std::make_pair(it->second.first * koef, it->second.second);
  3048. };
  3049. // data used to properly align items in columns when showing time
  3050. std::array<float, 4> offsets = { 0.0f, 0.0f, 0.0f, 0.0f };
  3051. std::vector<std::string> labels;
  3052. std::vector<std::string> times;
  3053. std::vector<float> percents;
  3054. std::vector<double> used_filaments_m;
  3055. std::vector<double> used_filaments_g;
  3056. float max_time_percent = 0.0f;
  3057. if (m_view_type == EViewType::FeatureType) {
  3058. // calculate offsets to align time/percentage data
  3059. for (GCodeExtrusionRole role : m_roles) {
  3060. assert(role < GCodeExtrusionRole::Count);
  3061. if (role < GCodeExtrusionRole::Count) {
  3062. labels.push_back(_u8L(gcode_extrusion_role_to_string(role)));
  3063. auto [time, percent] = role_time_and_percent(role);
  3064. times.push_back((time > 0.0f) ? short_time_ui(get_time_dhms(time)) : "");
  3065. percents.push_back(percent);
  3066. max_time_percent = std::max(max_time_percent, percent);
  3067. auto [used_filament_m, used_filament_g] = used_filament_per_role(role);
  3068. used_filaments_m.push_back(used_filament_m);
  3069. used_filaments_g.push_back(used_filament_g);
  3070. }
  3071. }
  3072. std::string longest_percentage_string;
  3073. for (double item : percents) {
  3074. char buffer[64];
  3075. ::sprintf(buffer, "%.2f %%", item);
  3076. if (::strlen(buffer) > longest_percentage_string.length())
  3077. longest_percentage_string = buffer;
  3078. }
  3079. longest_percentage_string += " ";
  3080. if (_u8L("Percentage").length() > longest_percentage_string.length())
  3081. longest_percentage_string = _u8L("Percentage");
  3082. std::string longest_used_filament_string;
  3083. for (double item : used_filaments_m) {
  3084. char buffer[64];
  3085. ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
  3086. if (::strlen(buffer) > longest_used_filament_string.length())
  3087. longest_used_filament_string = buffer;
  3088. }
  3089. offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size);
  3090. }
  3091. // get used filament (meters and grams) from used volume in respect to the active extruder
  3092. auto get_used_filament_from_volume = [this, imperial_units](double volume, int extruder_id) {
  3093. double koef = imperial_units ? 1.0 / ObjectManipulation::in_to_mm : 0.001;
  3094. std::pair<double, double> ret = { koef * volume / (PI * sqr(0.5 * m_filament_diameters[extruder_id])),
  3095. volume * m_filament_densities[extruder_id] * 0.001 };
  3096. return ret;
  3097. };
  3098. if (m_view_type == EViewType::Tool) {
  3099. // calculate used filaments data
  3100. used_filaments_m = std::vector<double>(m_extruders_count, 0.0);
  3101. used_filaments_g = std::vector<double>(m_extruders_count, 0.0);
  3102. for (size_t extruder_id : m_extruder_ids) {
  3103. if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end())
  3104. continue;
  3105. double volume = m_print_statistics.volumes_per_extruder.at(extruder_id);
  3106. auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id);
  3107. used_filaments_m[extruder_id] = used_filament_m;
  3108. used_filaments_g[extruder_id] = used_filament_g;
  3109. }
  3110. std::string longest_used_filament_string;
  3111. for (double item : used_filaments_m) {
  3112. char buffer[64];
  3113. ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item);
  3114. if (::strlen(buffer) > longest_used_filament_string.length())
  3115. longest_used_filament_string = buffer;
  3116. }
  3117. offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size);
  3118. }
  3119. // selection section
  3120. bool view_type_changed = false;
  3121. int old_view_type = static_cast<int>(get_view_type());
  3122. int view_type = old_view_type;
  3123. ImGui::PushStyleColor(ImGuiCol_FrameBg, { 0.1f, 0.1f, 0.1f, 0.8f });
  3124. ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, { 0.2f, 0.2f, 0.2f, 0.8f });
  3125. imgui.combo(std::string(), { _u8L("Feature type"),
  3126. _u8L("Height (mm)"),
  3127. _u8L("Width (mm)"),
  3128. _u8L("Speed (mm/s)"),
  3129. _u8L("Fan speed (%)"),
  3130. _u8L("Temperature (°C)"),
  3131. _u8L("Volumetric flow rate (mm³/s)"),
  3132. _u8L("Layer time (linear)"),
  3133. _u8L("Layer time (logarithmic)"),
  3134. _u8L("Tool"),
  3135. _u8L("Color Print") }, view_type, ImGuiComboFlags_HeightLargest, 0.0f, -1.0f);
  3136. ImGui::PopStyleColor(2);
  3137. if (old_view_type != view_type) {
  3138. set_view_type(static_cast<EViewType>(view_type));
  3139. wxGetApp().plater()->set_keep_current_preview_type(true);
  3140. wxGetApp().plater()->refresh_print();
  3141. view_type_changed = true;
  3142. }
  3143. // extrusion paths section -> title
  3144. if (m_view_type == EViewType::FeatureType)
  3145. append_headers({ "", _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets);
  3146. else if (m_view_type == EViewType::Tool)
  3147. append_headers({ "", _u8L("Used filament"), "", ""}, offsets);
  3148. else
  3149. ImGui::Separator();
  3150. if (!view_type_changed) {
  3151. // extrusion paths section -> items
  3152. switch (m_view_type)
  3153. {
  3154. case EViewType::FeatureType:
  3155. {
  3156. max_time_percent = std::max(max_time_percent, time_mode.travel_time / time_mode.time);
  3157. for (size_t i = 0; i < m_roles.size(); ++i) {
  3158. GCodeExtrusionRole role = m_roles[i];
  3159. if (role >= GCodeExtrusionRole::Count)
  3160. continue;
  3161. const bool visible = is_visible(role);
  3162. append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast<unsigned int>(role)], labels[i],
  3163. visible, times[i], percents[i], max_time_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() {
  3164. m_extrusions.role_visibility_flags = visible ? m_extrusions.role_visibility_flags & ~(1 << int(role)) : m_extrusions.role_visibility_flags | (1 << int(role));
  3165. // update buffers' render paths
  3166. refresh_render_paths(false, false);
  3167. wxGetApp().plater()->update_preview_moves_slider();
  3168. wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
  3169. }
  3170. );
  3171. }
  3172. if (m_buffers[buffer_id(EMoveType::Travel)].visible)
  3173. append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time_ui(get_time_dhms(time_mode.travel_time)),
  3174. time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f);
  3175. break;
  3176. }
  3177. case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; }
  3178. case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; }
  3179. case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; }
  3180. case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; }
  3181. case EViewType::Temperature: { append_range(m_extrusions.ranges.temperature, 0); break; }
  3182. case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; }
  3183. case EViewType::LayerTimeLinear: { append_time_range(m_extrusions.ranges.layer_time[static_cast<size_t>(m_time_estimate_mode)], Extrusions::Range::EType::Linear); break; }
  3184. case EViewType::LayerTimeLogarithmic: { append_time_range(m_extrusions.ranges.layer_time[static_cast<size_t>(m_time_estimate_mode)], Extrusions::Range::EType::Logarithmic); break; }
  3185. case EViewType::Tool: {
  3186. // shows only extruders actually used
  3187. for (unsigned char extruder_id : m_extruder_ids) {
  3188. if (used_filaments_m[extruder_id] > 0.0 && used_filaments_g[extruder_id] > 0.0)
  3189. append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1),
  3190. true, "", 0.0f, 0.0f, offsets, used_filaments_m[extruder_id], used_filaments_g[extruder_id]);
  3191. }
  3192. break;
  3193. }
  3194. case EViewType::ColorPrint:
  3195. {
  3196. const std::vector<CustomGCode::Item>& custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
  3197. size_t total_items = 1;
  3198. for (unsigned char i : m_extruder_ids) {
  3199. total_items += color_print_ranges(i, custom_gcode_per_print_z).size();
  3200. }
  3201. const bool need_scrollable = static_cast<float>(total_items) * icon_size + (static_cast<float>(total_items) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height;
  3202. // add scrollable region, if needed
  3203. if (need_scrollable)
  3204. ImGui::BeginChild("color_prints", { -1.0f, child_height }, false);
  3205. if (m_extruders_count == 1) { // single extruder use case
  3206. const std::vector<std::pair<ColorRGBA, std::pair<double, double>>> cp_values = color_print_ranges(0, custom_gcode_per_print_z);
  3207. const int items_cnt = static_cast<int>(cp_values.size());
  3208. if (items_cnt == 0) // There are no color changes, but there are some pause print or custom Gcode
  3209. append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color"));
  3210. else {
  3211. for (int i = items_cnt; i >= 0; --i) {
  3212. // create label for color change item
  3213. if (i == 0) {
  3214. append_item(EItemType::Rect, m_tool_colors[0], upto_label(cp_values.front().second.first));
  3215. break;
  3216. }
  3217. else if (i == items_cnt) {
  3218. append_item(EItemType::Rect, cp_values[i - 1].first, above_label(cp_values[i - 1].second.second));
  3219. continue;
  3220. }
  3221. append_item(EItemType::Rect, cp_values[i - 1].first, fromto_label(cp_values[i - 1].second.second, cp_values[i].second.first));
  3222. }
  3223. }
  3224. }
  3225. else { // multi extruder use case
  3226. // shows only extruders actually used
  3227. for (unsigned char i : m_extruder_ids) {
  3228. const std::vector<std::pair<ColorRGBA, std::pair<double, double>>> cp_values = color_print_ranges(i, custom_gcode_per_print_z);
  3229. const int items_cnt = static_cast<int>(cp_values.size());
  3230. if (items_cnt == 0)
  3231. // There are no color changes, but there are some pause print or custom Gcode
  3232. append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color"));
  3233. else {
  3234. for (int j = items_cnt; j >= 0; --j) {
  3235. // create label for color change item
  3236. std::string label = _u8L("Extruder") + " " + std::to_string(i + 1);
  3237. if (j == 0) {
  3238. label += " " + upto_label(cp_values.front().second.first);
  3239. append_item(EItemType::Rect, m_tool_colors[i], label);
  3240. break;
  3241. }
  3242. else if (j == items_cnt) {
  3243. label += " " + above_label(cp_values[j - 1].second.second);
  3244. append_item(EItemType::Rect, cp_values[j - 1].first, label);
  3245. continue;
  3246. }
  3247. label += " " + fromto_label(cp_values[j - 1].second.second, cp_values[j].second.first);
  3248. append_item(EItemType::Rect, cp_values[j - 1].first, label);
  3249. }
  3250. }
  3251. }
  3252. }
  3253. if (need_scrollable)
  3254. ImGui::EndChild();
  3255. break;
  3256. }
  3257. default: { break; }
  3258. }
  3259. }
  3260. // partial estimated printing time section
  3261. if (m_view_type == EViewType::ColorPrint) {
  3262. using Times = std::pair<float, float>;
  3263. using TimesList = std::vector<std::pair<CustomGCode::Type, Times>>;
  3264. // helper structure containig the data needed to render the time items
  3265. struct PartialTime
  3266. {
  3267. enum class EType : unsigned char
  3268. {
  3269. Print,
  3270. ColorChange,
  3271. Pause
  3272. };
  3273. EType type;
  3274. int extruder_id;
  3275. ColorRGBA color1;
  3276. ColorRGBA color2;
  3277. Times times;
  3278. std::pair<double, double> used_filament{ 0.0f, 0.0f };
  3279. };
  3280. using PartialTimes = std::vector<PartialTime>;
  3281. auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector<double>& used_filaments) {
  3282. PartialTimes items;
  3283. std::vector<CustomGCode::Item> custom_gcode_per_print_z = wxGetApp().is_editor() ? wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes : m_custom_gcode_per_print_z;
  3284. std::vector<ColorRGBA> last_color(m_extruders_count);
  3285. for (size_t i = 0; i < m_extruders_count; ++i) {
  3286. last_color[i] = m_tool_colors[i];
  3287. }
  3288. int last_extruder_id = 1;
  3289. int color_change_idx = 0;
  3290. for (const auto& time_rec : times) {
  3291. switch (time_rec.first)
  3292. {
  3293. case CustomGCode::PausePrint: {
  3294. auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
  3295. if (it != custom_gcode_per_print_z.end()) {
  3296. items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second });
  3297. items.push_back({ PartialTime::EType::Pause, it->extruder, ColorRGBA::BLACK(), ColorRGBA::BLACK(), time_rec.second });
  3298. custom_gcode_per_print_z.erase(it);
  3299. }
  3300. break;
  3301. }
  3302. case CustomGCode::ColorChange: {
  3303. auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; });
  3304. if (it != custom_gcode_per_print_z.end()) {
  3305. items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder - 1) });
  3306. ColorRGBA color;
  3307. decode_color(it->color, color);
  3308. items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], color, time_rec.second });
  3309. last_color[it->extruder - 1] = color;
  3310. last_extruder_id = it->extruder;
  3311. custom_gcode_per_print_z.erase(it);
  3312. }
  3313. else
  3314. items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], ColorRGBA::BLACK(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id - 1) });
  3315. break;
  3316. }
  3317. default: { break; }
  3318. }
  3319. }
  3320. return items;
  3321. };
  3322. auto append_color_change = [&imgui](const ColorRGBA& color1, const ColorRGBA& color2, const std::array<float, 4>& offsets, const Times& times) {
  3323. imgui.text(_u8L("Color change"));
  3324. ImGui::SameLine();
  3325. float icon_size = ImGui::GetTextLineHeight();
  3326. ImDrawList* draw_list = ImGui::GetWindowDrawList();
  3327. ImVec2 pos = ImGui::GetCursorScreenPos();
  3328. pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x;
  3329. draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
  3330. ImGuiWrapper::to_ImU32(color1));
  3331. pos.x += icon_size;
  3332. draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
  3333. ImGuiWrapper::to_ImU32(color2));
  3334. ImGui::SameLine(offsets[0]);
  3335. imgui.text(short_time_ui(get_time_dhms(times.second - times.first)));
  3336. };
  3337. auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
  3338. imgui.text(_u8L("Print"));
  3339. ImGui::SameLine();
  3340. float icon_size = ImGui::GetTextLineHeight();
  3341. ImDrawList* draw_list = ImGui::GetWindowDrawList();
  3342. ImVec2 pos = ImGui::GetCursorScreenPos();
  3343. pos.x -= 0.5f * ImGui::GetStyle().ItemSpacing.x;
  3344. draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f },
  3345. ImGuiWrapper::to_ImU32(color));
  3346. ImGui::SameLine(offsets[0]);
  3347. imgui.text(short_time_ui(get_time_dhms(times.second)));
  3348. ImGui::SameLine(offsets[1]);
  3349. imgui.text(short_time_ui(get_time_dhms(times.first)));
  3350. if (used_filament.first > 0.0f) {
  3351. char buffer[64];
  3352. ImGui::SameLine(offsets[2]);
  3353. ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first);
  3354. imgui.text(buffer);
  3355. ImGui::SameLine(offsets[3]);
  3356. ::sprintf(buffer, "%.2f g", used_filament.second);
  3357. imgui.text(buffer);
  3358. }
  3359. };
  3360. PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change);
  3361. if (!partial_times.empty()) {
  3362. labels.clear();
  3363. times.clear();
  3364. for (const PartialTime& item : partial_times) {
  3365. switch (item.type)
  3366. {
  3367. case PartialTime::EType::Print: { labels.push_back(_u8L("Print")); break; }
  3368. case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
  3369. case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
  3370. }
  3371. times.push_back(short_time_ui(get_time_dhms(item.times.second)));
  3372. }
  3373. std::string longest_used_filament_string;
  3374. for (const PartialTime& item : partial_times) {
  3375. if (item.used_filament.first > 0.0f) {
  3376. char buffer[64];
  3377. ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first);
  3378. if (::strlen(buffer) > longest_used_filament_string.length())
  3379. longest_used_filament_string = buffer;
  3380. }
  3381. }
  3382. offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size);
  3383. ImGui::Spacing();
  3384. append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets);
  3385. const bool need_scrollable = static_cast<float>(partial_times.size()) * icon_size + (static_cast<float>(partial_times.size()) - 1.0f) * ImGui::GetStyle().ItemSpacing.y > child_height;
  3386. if (need_scrollable)
  3387. // add scrollable region
  3388. ImGui::BeginChild("events", { -1.0f, child_height }, false);
  3389. for (const PartialTime& item : partial_times) {
  3390. switch (item.type)
  3391. {
  3392. case PartialTime::EType::Print: {
  3393. append_print(item.color1, offsets, item.times, item.used_filament);
  3394. break;
  3395. }
  3396. case PartialTime::EType::Pause: {
  3397. imgui.text(_u8L("Pause"));
  3398. ImGui::SameLine(offsets[0]);
  3399. imgui.text(short_time_ui(get_time_dhms(item.times.second - item.times.first)));
  3400. break;
  3401. }
  3402. case PartialTime::EType::ColorChange: {
  3403. append_color_change(item.color1, item.color2, offsets, item.times);
  3404. break;
  3405. }
  3406. }
  3407. }
  3408. if (need_scrollable)
  3409. ImGui::EndChild();
  3410. }
  3411. }
  3412. auto add_strings_row_to_table = [&imgui](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) {
  3413. ImGui::TableNextRow();
  3414. ImGui::TableSetColumnIndex(0);
  3415. imgui.text_colored(col_1_color, col_1.c_str());
  3416. ImGui::TableSetColumnIndex(1);
  3417. imgui.text_colored(col_2_color, col_2.c_str());
  3418. };
  3419. // settings section
  3420. bool has_settings = false;
  3421. has_settings |= !m_settings_ids.print.empty();
  3422. has_settings |= !m_settings_ids.printer.empty();
  3423. bool has_filament_settings = true;
  3424. has_filament_settings &= !m_settings_ids.filament.empty();
  3425. for (const std::string& fs : m_settings_ids.filament) {
  3426. has_filament_settings &= !fs.empty();
  3427. }
  3428. has_settings |= has_filament_settings;
  3429. bool show_settings = wxGetApp().is_gcode_viewer();
  3430. show_settings &= (m_view_type == EViewType::FeatureType || m_view_type == EViewType::Tool);
  3431. show_settings &= has_settings;
  3432. if (show_settings) {
  3433. ImGui::Spacing();
  3434. imgui.title(_u8L("Settings"));
  3435. auto trim_text_if_needed = [](const std::string& txt) {
  3436. const float max_length = 250.0f;
  3437. const float length = ImGui::CalcTextSize(txt.c_str()).x;
  3438. if (length > max_length) {
  3439. const size_t new_len = txt.length() * max_length / length;
  3440. return txt.substr(0, new_len) + "...";
  3441. }
  3442. return txt;
  3443. };
  3444. if (ImGui::BeginTable("Settings", 2)) {
  3445. if (!m_settings_ids.printer.empty())
  3446. add_strings_row_to_table(_u8L("Printer") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
  3447. trim_text_if_needed(m_settings_ids.printer), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
  3448. if (!m_settings_ids.print.empty())
  3449. add_strings_row_to_table(_u8L("Print settings") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
  3450. trim_text_if_needed(m_settings_ids.print), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
  3451. if (!m_settings_ids.filament.empty()) {
  3452. for (unsigned char i : m_extruder_ids) {
  3453. if (i < static_cast<unsigned char>(m_settings_ids.filament.size()) && !m_settings_ids.filament[i].empty()) {
  3454. std::string txt = _u8L("Filament");
  3455. txt += (m_extruder_ids.size() == 1) ? ":" : " " + std::to_string(i + 1);
  3456. add_strings_row_to_table(txt, ImGuiWrapper::COL_ORANGE_LIGHT,
  3457. trim_text_if_needed(m_settings_ids.filament[i]), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
  3458. }
  3459. }
  3460. }
  3461. ImGui::EndTable();
  3462. }
  3463. }
  3464. if (m_view_type == EViewType::Width || m_view_type == EViewType::VolumetricRate) {
  3465. const auto custom_it = std::find(m_roles.begin(), m_roles.end(), GCodeExtrusionRole::Custom);
  3466. if (custom_it != m_roles.end()) {
  3467. const bool custom_visible = is_visible(GCodeExtrusionRole::Custom);
  3468. const wxString btn_text = custom_visible ? _L("Hide Custom G-code") : _L("Show Custom G-code");
  3469. ImGui::Separator();
  3470. if (imgui.button(btn_text, ImVec2(-1.0f, 0.0f), true)) {
  3471. m_extrusions.role_visibility_flags = custom_visible ? m_extrusions.role_visibility_flags & ~(1 << int(GCodeExtrusionRole::Custom)) :
  3472. m_extrusions.role_visibility_flags | (1 << int(GCodeExtrusionRole::Custom));
  3473. wxGetApp().plater()->refresh_print();
  3474. }
  3475. }
  3476. }
  3477. // total estimated printing time section
  3478. if (show_estimated_time) {
  3479. ImGui::Spacing();
  3480. std::string time_title = _u8L("Estimated printing times");
  3481. auto can_show_mode_button = [this](PrintEstimatedStatistics::ETimeMode mode) {
  3482. bool show = false;
  3483. if (m_print_statistics.modes.size() > 1 && m_print_statistics.modes[static_cast<size_t>(mode)].roles_times.size() > 0) {
  3484. for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) {
  3485. if (i != static_cast<size_t>(mode) &&
  3486. m_print_statistics.modes[i].time > 0.0f &&
  3487. short_time(get_time_dhms(m_print_statistics.modes[static_cast<size_t>(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) {
  3488. show = true;
  3489. break;
  3490. }
  3491. }
  3492. }
  3493. return show;
  3494. };
  3495. if (can_show_mode_button(m_time_estimate_mode)) {
  3496. switch (m_time_estimate_mode)
  3497. {
  3498. case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]"; break; }
  3499. case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]"; break; }
  3500. default: { assert(false); break; }
  3501. }
  3502. }
  3503. imgui.title(time_title + ":");
  3504. if (ImGui::BeginTable("Times", 2)) {
  3505. if (!time_mode.layers_times.empty()) {
  3506. add_strings_row_to_table(_u8L("First layer") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
  3507. short_time_ui(get_time_dhms(time_mode.layers_times.front())), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
  3508. }
  3509. add_strings_row_to_table(_u8L("Total") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
  3510. short_time_ui(get_time_dhms(time_mode.time)), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
  3511. ImGui::EndTable();
  3512. }
  3513. auto show_mode_button = [this, &imgui, can_show_mode_button](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) {
  3514. if (can_show_mode_button(mode)) {
  3515. if (imgui.button(label)) {
  3516. m_time_estimate_mode = mode;
  3517. if (m_view_type == EViewType::LayerTimeLinear || m_view_type == EViewType::LayerTimeLogarithmic)
  3518. refresh_render_paths(false, false);
  3519. imgui.set_requires_extra_frame();
  3520. }
  3521. }
  3522. };
  3523. switch (m_time_estimate_mode) {
  3524. case PrintEstimatedStatistics::ETimeMode::Normal: {
  3525. show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth);
  3526. break;
  3527. }
  3528. case PrintEstimatedStatistics::ETimeMode::Stealth: {
  3529. show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal);
  3530. break;
  3531. }
  3532. default : { assert(false); break; }
  3533. }
  3534. }
  3535. // toolbar section
  3536. auto toggle_button = [this, &imgui, icon_size](Preview::OptionType type, const std::string& name,
  3537. std::function<void(ImGuiWindow& window, const ImVec2& pos, float size)> draw_callback) {
  3538. auto is_flag_set = [](unsigned int flags, unsigned int flag) {
  3539. return (flags & (1 << flag)) != 0;
  3540. };
  3541. auto set_flag = [](unsigned int flags, unsigned int flag, bool active) {
  3542. return active ? (flags | (1 << flag)) : (flags & ~(1 << flag));
  3543. };
  3544. unsigned int flags = get_options_visibility_flags();
  3545. unsigned int flag = static_cast<unsigned int>(type);
  3546. bool active = is_flag_set(flags, flag);
  3547. if (imgui.draw_radio_button(name, 1.5f * icon_size, active, draw_callback)) {
  3548. unsigned int new_flags = set_flag(flags, flag, !active);
  3549. set_options_visibility_from_flags(new_flags);
  3550. const unsigned int diff_flags = flags ^ new_flags;
  3551. if (m_view_type == GCodeViewer::EViewType::Feedrate && is_flag_set(diff_flags, static_cast<unsigned int>(Preview::OptionType::Travel)))
  3552. wxGetApp().plater()->refresh_print();
  3553. else {
  3554. bool keep_first = m_sequential_view.current.first != m_sequential_view.global.first;
  3555. bool keep_last = m_sequential_view.current.last != m_sequential_view.global.last;
  3556. wxGetApp().plater()->get_current_canvas3D()->refresh_gcode_preview_render_paths(keep_first, keep_last);
  3557. }
  3558. wxGetApp().plater()->update_preview_moves_slider();
  3559. }
  3560. if (ImGui::IsItemHovered()) {
  3561. ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
  3562. ImGui::BeginTooltip();
  3563. imgui.text(name);
  3564. ImGui::EndTooltip();
  3565. ImGui::PopStyleColor();
  3566. }
  3567. };
  3568. ImGui::Spacing();
  3569. ImGui::Separator();
  3570. ImGui::Spacing();
  3571. ImGui::Spacing();
  3572. toggle_button(Preview::OptionType::Travel, _u8L("Travel"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3573. imgui.draw_icon(window, pos, size, ImGui::LegendTravel);
  3574. });
  3575. ImGui::SameLine();
  3576. toggle_button(Preview::OptionType::Wipe, _u8L("Wipe"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3577. imgui.draw_icon(window, pos, size, ImGui::LegendWipe);
  3578. });
  3579. ImGui::SameLine();
  3580. toggle_button(Preview::OptionType::Retractions, _u8L("Retractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3581. imgui.draw_icon(window, pos, size, ImGui::LegendRetract);
  3582. });
  3583. ImGui::SameLine();
  3584. toggle_button(Preview::OptionType::Unretractions, _u8L("Deretractions"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3585. imgui.draw_icon(window, pos, size, ImGui::LegendDeretract);
  3586. });
  3587. ImGui::SameLine();
  3588. toggle_button(Preview::OptionType::Seams, _u8L("Seams"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3589. imgui.draw_icon(window, pos, size, ImGui::LegendSeams);
  3590. });
  3591. ImGui::SameLine();
  3592. toggle_button(Preview::OptionType::ToolChanges, _u8L("Tool changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3593. imgui.draw_icon(window, pos, size, ImGui::LegendToolChanges);
  3594. });
  3595. ImGui::SameLine();
  3596. toggle_button(Preview::OptionType::ColorChanges, _u8L("Color changes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3597. imgui.draw_icon(window, pos, size, ImGui::LegendColorChanges);
  3598. });
  3599. ImGui::SameLine();
  3600. toggle_button(Preview::OptionType::PausePrints, _u8L("Print pauses"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3601. imgui.draw_icon(window, pos, size, ImGui::LegendPausePrints);
  3602. });
  3603. ImGui::SameLine();
  3604. toggle_button(Preview::OptionType::CustomGCodes, _u8L("Custom G-codes"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3605. imgui.draw_icon(window, pos, size, ImGui::LegendCustomGCodes);
  3606. });
  3607. ImGui::SameLine();
  3608. toggle_button(Preview::OptionType::CenterOfGravity, _u8L("Center of gravity"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3609. imgui.draw_icon(window, pos, size, ImGui::LegendCOG);
  3610. });
  3611. ImGui::SameLine();
  3612. if (!wxGetApp().is_gcode_viewer()) {
  3613. toggle_button(Preview::OptionType::Shells, _u8L("Shells"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3614. imgui.draw_icon(window, pos, size, ImGui::LegendShells);
  3615. });
  3616. ImGui::SameLine();
  3617. }
  3618. toggle_button(Preview::OptionType::ToolMarker, _u8L("Tool marker"), [&imgui](ImGuiWindow& window, const ImVec2& pos, float size) {
  3619. imgui.draw_icon(window, pos, size, ImGui::LegendToolMarker);
  3620. });
  3621. bool size_dirty = !ImGui::GetCurrentWindow()->ScrollbarY && ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x != ImGui::GetWindowWidth();
  3622. if (m_legend_resizer.dirty || size_dirty != m_legend_resizer.dirty) {
  3623. wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
  3624. wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
  3625. }
  3626. m_legend_resizer.dirty = size_dirty;
  3627. legend_height = ImGui::GetWindowHeight();
  3628. imgui.end();
  3629. ImGui::PopStyleVar();
  3630. }
  3631. #if ENABLE_GCODE_VIEWER_STATISTICS
  3632. void GCodeViewer::render_statistics()
  3633. {
  3634. static const float offset = 275.0f;
  3635. ImGuiWrapper& imgui = *wxGetApp().imgui();
  3636. auto add_time = [&imgui](const std::string& label, int64_t time) {
  3637. imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
  3638. ImGui::SameLine(offset);
  3639. imgui.text(std::to_string(time) + " ms (" + get_time_dhms(static_cast<float>(time) * 0.001f) + ")");
  3640. };
  3641. auto add_memory = [&imgui](const std::string& label, int64_t memory) {
  3642. auto format_string = [memory](const std::string& units, float value) {
  3643. return std::to_string(memory) + " bytes (" +
  3644. Slic3r::float_to_string_decimal_point(float(memory) * value, 3)
  3645. + " " + units + ")";
  3646. };
  3647. static const float kb = 1024.0f;
  3648. static const float inv_kb = 1.0f / kb;
  3649. static const float mb = 1024.0f * kb;
  3650. static const float inv_mb = 1.0f / mb;
  3651. static const float gb = 1024.0f * mb;
  3652. static const float inv_gb = 1.0f / gb;
  3653. imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
  3654. ImGui::SameLine(offset);
  3655. if (static_cast<float>(memory) < mb)
  3656. imgui.text(format_string("KB", inv_kb));
  3657. else if (static_cast<float>(memory) < gb)
  3658. imgui.text(format_string("MB", inv_mb));
  3659. else
  3660. imgui.text(format_string("GB", inv_gb));
  3661. };
  3662. auto add_counter = [&imgui](const std::string& label, int64_t counter) {
  3663. imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label);
  3664. ImGui::SameLine(offset);
  3665. imgui.text(std::to_string(counter));
  3666. };
  3667. imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f);
  3668. ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f });
  3669. imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize);
  3670. ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
  3671. if (ImGui::CollapsingHeader("Time")) {
  3672. add_time(std::string("GCodeProcessor:"), m_statistics.results_time);
  3673. ImGui::Separator();
  3674. add_time(std::string("Load:"), m_statistics.load_time);
  3675. add_time(std::string(" Load vertices:"), m_statistics.load_vertices);
  3676. add_time(std::string(" Smooth vertices:"), m_statistics.smooth_vertices);
  3677. add_time(std::string(" Load indices:"), m_statistics.load_indices);
  3678. add_time(std::string("Refresh:"), m_statistics.refresh_time);
  3679. add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time);
  3680. }
  3681. if (ImGui::CollapsingHeader("OpenGL calls")) {
  3682. add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count);
  3683. add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count);
  3684. add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count);
  3685. ImGui::Separator();
  3686. add_counter(std::string("Instanced models:"), m_statistics.gl_instanced_models_calls_count);
  3687. add_counter(std::string("Batched models:"), m_statistics.gl_batched_models_calls_count);
  3688. }
  3689. if (ImGui::CollapsingHeader("CPU memory")) {
  3690. add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size);
  3691. ImGui::Separator();
  3692. add_memory(std::string("Paths:"), m_statistics.paths_size);
  3693. add_memory(std::string("Render paths:"), m_statistics.render_paths_size);
  3694. add_memory(std::string("Models instances:"), m_statistics.models_instances_size);
  3695. }
  3696. if (ImGui::CollapsingHeader("GPU memory")) {
  3697. add_memory(std::string("Vertices:"), m_statistics.total_vertices_gpu_size);
  3698. add_memory(std::string("Indices:"), m_statistics.total_indices_gpu_size);
  3699. add_memory(std::string("Instances:"), m_statistics.total_instances_gpu_size);
  3700. ImGui::Separator();
  3701. add_memory(std::string("Max VBuffer:"), m_statistics.max_vbuffer_gpu_size);
  3702. add_memory(std::string("Max IBuffer:"), m_statistics.max_ibuffer_gpu_size);
  3703. }
  3704. if (ImGui::CollapsingHeader("Other")) {
  3705. add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count);
  3706. add_counter(std::string("Wipe segments count:"), m_statistics.wipe_segments_count);
  3707. add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count);
  3708. add_counter(std::string("Instances count:"), m_statistics.instances_count);
  3709. add_counter(std::string("Batched count:"), m_statistics.batched_count);
  3710. ImGui::Separator();
  3711. add_counter(std::string("VBuffers count:"), m_statistics.vbuffers_count);
  3712. add_counter(std::string("IBuffers count:"), m_statistics.ibuffers_count);
  3713. }
  3714. imgui.end();
  3715. }
  3716. #endif // ENABLE_GCODE_VIEWER_STATISTICS
  3717. void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) const
  3718. {
  3719. if (Slic3r::get_logging_level() >= 5) {
  3720. int64_t paths_size = 0;
  3721. int64_t render_paths_size = 0;
  3722. for (const TBuffer& buffer : m_buffers) {
  3723. paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path);
  3724. render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath);
  3725. for (const RenderPath& path : buffer.render_paths) {
  3726. render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int);
  3727. render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t);
  3728. }
  3729. }
  3730. int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double);
  3731. layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_ranges(), Layers::Range);
  3732. BOOST_LOG_TRIVIAL(trace) << label
  3733. << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");"
  3734. << log_memory_info();
  3735. }
  3736. }
  3737. ColorRGBA GCodeViewer::option_color(EMoveType move_type) const
  3738. {
  3739. switch (move_type)
  3740. {
  3741. case EMoveType::Tool_change: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::ToolChanges)]; }
  3742. case EMoveType::Color_change: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::ColorChanges)]; }
  3743. case EMoveType::Pause_Print: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::PausePrints)]; }
  3744. case EMoveType::Custom_GCode: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::CustomGCodes)]; }
  3745. case EMoveType::Retract: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Retractions)]; }
  3746. case EMoveType::Unretract: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Unretractions)]; }
  3747. case EMoveType::Seam: { return Options_Colors[static_cast<unsigned int>(EOptionsColors::Seams)]; }
  3748. default: { return { 0.0f, 0.0f, 0.0f, 1.0f }; }
  3749. }
  3750. }
  3751. } // namespace GUI
  3752. } // namespace Slic3r