* ENH: Show Recent File Image Keep Scale Change-Id: Ib8a6cf916eaee8e353bf858bc4f2ea503705809e * FIX: wipetower position problem jira: STUDIO-4914 Change-Id: I7b05d3c53931ed8ce3d4603ff21ee6ef675611e5 * FIX: dailytips adapts screen scale jira: STUDIO-5019 STUDIO-5026 STUDIO-5027 STUDIO-5028 STUDIO-5025 Change-Id: I63d3af1870218ba8e0f048a6ef03fb29fabe27cb * FIX: generate process preset based on template Jira: XXXX Change-Id: I50adf0790dc239307d236a4cebece860ef6beb16 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: object list plate name edit Change-Id: I61d3dcd7d9598d759a3a0b44cc77d2af2adca25a Jira: STUDIO-4937 * ENH:no longer checking nozzle type jira:[for nozzle type check] Change-Id: I0e88445a264f21b0c11519e9a22a165d05611d14 * ENH: improve first layer tree support First layer support can't be top interface, and min brim width of auto mode should be larger than 0. Jira: STUDIO-5010 Change-Id: I02f8b017b535f8a47965387e8679f692b1966e04 (cherry picked from commit 3e7d54abe352e8ab5f9d6492b5a86a96f9067f94) * ENH: version: bumped to 1.8 JIRA: no jira Change-Id: I50903098b59f1dd9a6b6cf7656cec7d388f3ff17 * ENH:try again after subscription failure jira:[Try again after subscription failure] Change-Id: Ibfb1e8e26eb166d786a372632a86ef98030db034 * ENH:display msg dialog once jira:[for http error msg] Change-Id: I12e9c155fdb567cac99c35b6feeef650269ba75d * ENH:remove config.json file Change-Id: Idfcf3a63fefe968e88153c26fb691fd05cd83dc4 * ENH:add protection in threads jira:[for random crash] Change-Id: I6286012dd77abccba461f7cd72a6fc531a84c95f * FIX: add protection for get_model_task_thread thread Jira: XXXX Change-Id: I3cbc17d181a0e13c658f31eaeb6a4df878e6df41 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: delete all compatible presets when delete third printer Jira: XXXX Change-Id: I1a294402627e7ab7a8c6701f20679b3d04aff059 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ci: update build version to 01.08.00.51 Change-Id: I20a01adacbdb5fe69c106b9efd029f7308136e10 * ENH: default open support_interface_not_for_body jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I48e084deb18633f9ec47a8ec4ec643163bf66318 * ENH:modified text with too low version jira:[for low version] Change-Id: I862a0defda976a35f326a8805e002330f2ed7fdf * NEW:update printer config file version Change-Id: I9a46b29b03beb67a3da0b8f76d8b5c4b3c482928 * FIX:The plane should rotate around the world coordinate system Jira: STUDIO-5054 Change-Id: I16e484b38d79cabd9473acf1abf3c5c6b0adc4c6 * ENH:translate for limit file size and so on Jira: STUDIO-5007 Change-Id: I2c279eb690841aa51cd8128f8028266cbc17e977 * ENH:use on_render_rotate_gizmos() replace GLGizmoRotate3D::on_render() Jira: STUDIO-4227 Change-Id: If9b9ea5596e59472d5fa87ac56aeb7f6ecc65643 * FIX: some mistakes in filament profiles jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ibe7f3650f2d9cf47561dd5f2ec591a5f6c553503 * FIX: fix shard_ptr is null Change-Id: I0187cf64ffbb08a2265a11900b5c865e9ac9678f * FIX:N1 printer image in dark mode JIRA:STUDIO-4057 Change-Id: I22c001d96839daf213d5096f6ff6e3d6398fa8c4 * FIX: create printer issue Jira: 5034 5059 5053 5034 create printer but filament is repeat 5039 create successful dialog remove to center 5053 create existing printer copywriting adjustments and preset updates Delete printer secondary confirmation dialog Change-Id: Ifb3822d1e168459d2af11e02b31ecaf3719d338a Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH:just don't check the nozzle diameter jira:[for nozzle check] Change-Id: I678e7d62832eaa14b9be47d6dce70f29ebd601f6 * NEW:p1 and x1 series added motor noise calibration JIRA: 5085 Change-Id: Id73cc2d34b6130f215d81ffcdc39ba6b241445bf * ci: update build version to 01.08.00.52 Change-Id: I93d800b413f2751d132fac53fbd9b191603d0352 * FIX: ObjectSetting changed when search plate JIRA: STUDIO-5095 Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Change-Id: I39b1ad997d51ac4224ff5ad2b3555f56da4bd911 * FIX: invalid support params in 3rd party profiles Many params are not right.Just use default jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I5c4a1e8b046940e174f5681a79031b5f20fcafc5 * ENH: update A1 mini start gcode Change x-axis freq sweep amp 5->10 jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I2e731cc6392c0204d5e4467bf4b933ab233bc157 * FIX: [STUDIO-4946] use utf8 path to create sub process Change-Id: I5873c114e8cd36978a7d50bf13c3aa7bf8b740ca Jira: STUDIO-4946 * FIX: fix a plate state not correct issue JIRA: no-jira the object and instance states lost after undo Change-Id: I527df9a7d426d994501a4ed5bbb198c7bbac810b * FIX: some translation Jira: 5096 5089 5036 5004 Change-Id: I4f1bd6e352b11451f5caf02cbc4eeb31dfa03eee Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: [STUDIO-4935] plate name edit in object list Change-Id: I271fa217281d0c7ceca61166497136628a66681e Jira: STUDIO-4935 * FIX: take custom root as AMS sync candicate Change-Id: I9c71babcd74238d1533b15d77a978b19997c70c0 Jira: none * FIX: modify some default support params in code 1. Modify default values of some supports params, so 3rd party profiles are easier to setup. 3. Fix a bug that organic support may cause crash. Jira: none Change-Id: Icae348d8fe5985f4287404e96089198a499283f2 (cherry picked from commit 8889cfc703b72e142f288e525b89c87619f2213c) * FIX: do not generate sheath for normal support Jira: none Change-Id: I8f3f3e39171055f8d18c06ceee8e245594273238 (cherry picked from commit 93bc7ecf4346f179f502bebc3cf47b0030b56e2c) * FIX: push_notification on GUI thread Change-Id: Iaec347f5684fe0f65d6418759518189b67033c42 Jira: STUDIO-5106 * ENH: CLI: add some params to support more functions 1. uptodate_filaments to support update the original filaments to newest config 2. allow_rotations/avoid_extrusion_cali_region for auto-arrange 3. skip_modified_gcodes to support skip modified gcodes JIRA: STUDIO-5112 Change-Id: I95c09af1b5462cce3bf27aea32228d6d1d1d201d * FIX: missed manually entered values for secondary processing Jira: STUDIO-4964 Change-Id: I5cf0da1ae77cccd34de05b4a0318a751ac9f6753 * FIX: Z hop is still enabled when upper boundary is zero. Jira: STUDIO-4893 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: I5f46a02e1fbb15ff43e253e3a184aa6cc38e7598 * ENH: update default filaments for Bambu printers jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ic6380f39e546854ad0b7dc36929a8605c9ab3acc * ENH: dailytips modification 1. modify closing behavior 2. dailytips can adjust self size according to the canvas size. And also adjust GodeViewer legend window size 3. fix a button text encoding bug 4. support vertical/horizontal layout(horizontal layout currently not used) jira: new Change-Id: I8e0b6e85c455d0608d7388fb441829c1991ad01f * FIX: [4857 5097] export list and del preset two confirm issue Jira: 4857 5097 Change-Id: If7cc4967a663f575527a227e9c4ac31e0491930c * FIX: UUID conflict issue when referencing volume Jira: XXXX 3mf file standard Change-Id: I953a87294684ea85d03a95e7d2843c096904aeae Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: [4483 5003 5109] create printer and edit filament issue Jira: 4483 5003 5109 4483 dialog blink 5003 preset list too long 5109 encode Change-Id: I190e12272ca09f36b841f2f85a6cf60f2c2614bd Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: cloud use presets limit notify Change-Id: I6cc7b4e560cb83db0fc30921633b10531957128e Jira: STUDIO-5091, STUDIO-5104 * FIX: do user preset sync later on startup Change-Id: I0653a0438477b1c803ce1cddc66ef47f95616dae Jira: STUDIO-5106 * FIX: linux: pressing enter in height range will crash jira: STUDIO-4391 Change-Id: I6bf990951d1456f5b2605b8d62a05bceb3cc4c10 * FIX: failed to limit the max width of DropDown Jira: STUDIO-4503 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: Id9352d16f4bc016daade72a9c8d3d90164a1cb3d * FIX: not jump to preview after first wizard Change-Id: I8c94d66a91aa15a7874441a300b40438638bd33b Jira: STUDIO-5018 * ENH: CLI: clear custom gcodes when skip_modified_gcodes JIRA: STUDIO-5112 Change-Id: I2e7346d2ac57519029a4e80e5492c34d3d91ed77 * FIX: [4492 4851 4883 5121] create printer issue Jira: 4492 4851 4883 5121 Change-Id: If252b5f30be0403f79410aa8c00fc14b066d5bbd Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH: add 'edit preset' and 'delete preset' btn for each preset Jira: 5200 5113 Change-Id: I208ad63eb4b895306fa76db424da2e1df10a582e Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: add skip label before tool change Jira: 5074 github: 2776 Signed-off-by: qing.zhang <qing.zhang@bambulab.com> Change-Id: Icaafd3b45da1e78c1a82e7d17d7505d9439b9100 * FIX:Network test dark mode adaptation JIRA:STUDIO-2468 Change-Id: I20cb7f1fd8eca3ce852acb563c1cc87978e216dc * FIX:n1 external feed prompt pop-up without retry button JIRA: STUDIO-4696 Change-Id: I31069c72e29d3398469d71cdbc2a344a5430fc2c * FIX: not show device page when switch printer preset Change-Id: I00d8524625a4682b6a39876ddb66bf8bd928dbef Jira: none * ENH: Check the nozzle diameter when sending calibration Jira: 4977 Change-Id: Iabbba44583bbd9fbaaa889ca546ee0ccbb2aa77f * FIX: Generate UUID from objectID and volumeIndex Jira: XXXX Change-Id: I65147ef9b695f8af8de260d722e604b0e0bab563 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: disable filament_typep Jira: XXXX Change-Id: Ib605b33e4474525fbe49e70596fc09aa356f478a Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ci: update build version to 01.08.00.53 Change-Id: I1d574fa2cf2a4d0eb63a38eb8ced7587d06a4272 * ENH: refine display logic of param 1. Refine the display logic of "support_interface_not_for_body".Only toggle if support_filament is default and support_interface_filament is specified jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ia2af030c4531ad6b04a198bfe8a1677b3d20a800 * FIX: user preset sync token Change-Id: Id2aa865b778ee9ac4cfddb68ceef0374507b519b Jira: none * FIX: Bitmap cache not take effect Change-Id: I972098fdbda0b4542c6c759a8f5e1f0f2a30852b Jira: STUDIO-4991 * NEW: Open HotModel Link With GetParam-From bambustudio JIRA: NO JIRA Change-Id: I4ac49bac5ee0c50988c76a38b00b7ba7dc3201f5 * NEW:AmsMaterialsSetting Support for user-preset JIRA: STUDIO-5135 Change-Id: If848047cd5dbd059d440de30989c505c361305a7 * FIX: upload custom root preset fail Change-Id: I621c8d542dd604b07cc5df63d97d7a31558d3aba Jira: none * FIX: show custom filament in AMS filament list Change-Id: I79b9f8f2f08db8c52bbed76f1ea133baff383c00 Jira: none * FIX: dailytips window and gcodeviwer legend window size issue reset to original logic of dailytips and legend window size jira: new Change-Id: Iacb016bb222ba3f87317cfbe1f2b003802d773a5 * ENH: add text translation jira: new Change-Id: I780cfb8a0a64d806b5e0a414b6598e3b7bdf52dc * FIX: Delete and search object outside the plate JIRA: 1. STUDIO-5132 Deleting object outside the plate will crash 2. STUDIO-5146 The search function cannot search for object outside the plate Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Change-Id: I84cb3fe990a9c2a182e7434c262466a70545280e * FIX: [5149 5142 5141 5140 5136] create printer and filament issue Jira: 5149 5142 5141 5140 5136 5149 process preset name can not show all 5142 improt configs combobox not update 5141 disable modify filament_vendor 5140 disable input Bambu and Generic vendor 5136 preset list window adjust Change-Id: I111a23996146cc16cc7f533c8616d50223d34c40 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ci: update build version to 01.08.00.54 Change-Id: Ifd69c01a82f627a9c6cf4fe0d48a759563ee90e7 * FIX: print model from sdcard with p1p Change-Id: If85383ba762022ead3dd754ae02a08817b891114 Jira: none * FIX: dailytips text translation jira: STUDIO-2556 Change-Id: If44e503615b09ee1692f42ba1f998918ec5bd691 * FIX: clone shortcut key conflict with quit in macos jira: STUDIO-5166 Change-Id: I548f275bb68d3b0e6bb3cfad6fe93df09d507da3 * FIX:User preset material settings dependent on firmware JIRA: 5167 Change-Id: I82cf26848594b01155883ad0aa2e9ee77d371fb2 * ENH:update the description of nozzle detection Change-Id: Id27b25c69dc11fcf66fc82053af705906ae8c370 * FIX: [5159 5165 5171 5172] create printer and filament issue Jira: 5159 5165 5171 5172 5159 create printer dialog no refresh 5165 create printer 2 step dialog no refersh 5171 change font 5172 edit filament dialog darkUI issue input special character is prohibited '/' in preset name translate to '-' update printer combobox Change-Id: I5fa27836dab7f604f1a065c65efa099c7a2f0f96 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ci: update build version to 01.08.00.55 Change-Id: If1865d561cf274719204662314de163497759e89 * FIX:fix GLmodel deconstruction causing section not to be rendered Jira: STUDIO-5156 Change-Id: Ibb2f459920989ee54f7b827352dc8893424b4de6 * FIX: missing unlock cause device or resource busy Change-Id: I87563312ea9c6ce4e4e471da7ce7a02b53b64762 * FIX: some translation Change-Id: I9758cbc758030b5a3945697a50ca4898af9fcb1b * ci: update build version to 01.08.00.56 Change-Id: Id5ee53dd2ebb0b37b6927dc58b3cca94a1f66a83 * ENH: remove PLA GLOW in A1 mini jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Id99c1bbd4248e28df9150a85eecec831f6f32856 * ci: update build version to 01.08.00.57 Change-Id: Ib4dfa60f097128b76b95bb14ca04978619021b56 * Allow line width of nozzle diameter * 2.5 As it were, 1 mm would be disallowed but 0.99 would be allowed for 0.4 nozzle. 1 mm is the sane maximum and 0.99 is unnecessary tedious to write. * Russian translation update Russian translation Bambu Studio_v1.8.0 Beta * FIX: scale problem in needs_retraction jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Idfbe954b22fa6aa5769c55e46874fa6a80ecbf45 (cherry picked from commit 4e853f50b748e3af11e2d64862b6ee557fda361b) * ENH: CLI: support load_assemble_list JIRA: STUDIO-4848 Change-Id: Ife11533740988331ea71eac86c370e625970cb8b * FIX: align to Y not working This is a bug introduced in 7fbb650 when solving jira STUDIO-4695. Now we use a more decent way to solve it. Change-Id: I92deffcb9fe53e8a24c93fe973446ae37df07375 (cherry picked from commit bd98430dbd15eb6c9bb4b447990e0dcf8a50eef0) * ENH: Add buried points for cut and meshboolean JIRA: NONE Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Change-Id: I67ce498d0c335dd7a8582f56b880c2c8314f8541 * FIX: 5092 edit filament add scrolled window Jira: 5092 Change-Id: I53ae996b04e4e2f1b1ddce6a858d505001b11615 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: can not select user preset when create filament Jira: XXXX github: 1936 and fix add preset for printer dialog can not show selected printer Change-Id: Id4308c6bdca17d52d4aa321db359941aa87e0e45 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH: template filament don't be show in filament list and sort Jira: 5160 5179 Change-Id: I56a7e1897e1ef3c061dc66d318896413ca25b76b Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: [5174] export configs dialog issue filament name too long to can not show all Jira: 5174 Change-Id: I92018c9d7f86009b78b533592d899b4b5d78c3c8 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH: add filament Bambu TPU 95A HF 1.As title jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I752ec43da6297a6c172679997ce68f2318a7b8fb * ENH: modify some params with filaments 1.Modify the PEI bed temperature of PLA Basic, Matte, and Tough to 65 in A1 mini. Set the bed temperature for the first layer of Bambu PETG-CF to 65 and 70 for the other layers jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ia902bbb7f824082d5346709d781cac64296f47a8 * ENH: add more status during printing JIRA: STUDIO-5195 Change-Id: I85b3107839c6e2fdecbc10d90a876463e284468c Signed-off-by: Stone Li <stone.li@bambulab.com> * FIX:cut imgui has overlapping rendering on Apple Jira: STUDIO-5150 Change-Id: I7969e19dc189cd617026a183067dad628208955c * FIX:not TakeSnapshot for m_start_dragging_m Jira: STUDIO-5176 Change-Id: Ia03e3e2c2664dbdcffa19ec8d0fa97dfd95e6d35 * FIX: rendered color changes Jira: STUDIO-4956 during the drag processin connectors editing state Change-Id: I3027176ea9f93a9ba9d6a2052f41aaa4adef79f1 * FIX: merge the patch from Prusa Thanks for PrusaSlicer and YuSanka Jira:STUDIO-5175 commit 510d59687b3b19c4a0f4e6540620d0694dd1b7ac Author: YuSanka <yusanka@gmail.com> Date: Thu Oct 5 14:13:14 2023 +0200 Follow-up 1b451cdf: Fixed #11415 - Connectors disappear when slicing => only when using multiple cut planes AND excluding parts Change-Id: I9ccd5b85f482d723d21fccf5e104c9e0a9cc4849 * FIX:Press ESC directly to exit after entering the profile rendering rendering is not normal,Code from PrusaSlicer,thanks for PrusaSlicer and enricoturri1966 commit a078627552f54497ed0518dc7bc349d243576d19 Author: enricoturri1966 <enricoturri@seznam.cz> Date: Mon Jan 30 14:00:02 2023 +0100 Follow-up of 1218103fd620b319c56fd08116f81b581c537188 - Fixed gizmo missbehavior when closing a gizmo by resetting the selection clicking on the scene Jira: STUDIO-5164 Change-Id: I261da9dba2a5ac37f3e263c175fbccd80d8045bd * FIX: correct the strings and move create printer dialog center Jira: 5221 5183 Change-Id: Ida4eba63f0e962ffcc8000fcc04cf20849577217 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH: CLI: skip layer height limit validate when slicing for existing models JIRA: no jira Change-Id: I1444a28b500ca7d08ed2606eecfa5cfaf261105e * ENH:update the translation of auto refill jira:[for translation] Change-Id: Iaa7b4f3d7cd88c8b4f69a3db721ebd8ca8986eea * FIX: icon issue for copying Jira: STUDIO-4168 Icon issue when filling bed with copies Change-Id: I61a03ecae02b75602c236ed2810e9c9cfe5a19f9 (cherry picked from commit b5079f8a2e79f19f65803f23ef4fd83aff17c84a) * ENH: update some filament params 1. Modify texture bed temp to 65 2. Modify max-v-speed for ABS 3. Modify some params in Generic PA 4. Modify PLA,PVA params jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I42584a6015b8526f6bbb93024316968198bd76ce * FIX: 3770 printable checkbox incorrect display in darkUI Jira: 3770 Change-Id: I97f67d7a0ffc41f6ee625abeecc52ee4e73cf318 * FIX:Display garbled code in AmsMaterialsSetting pop-up Change-Id: I50531e939afa7715ae376bac47172ccf7b248114 * ENH:Modifying the Line Color of Transparent Materials JIRA: STUDIO-4311,5088,4983 Change-Id: I9e63413dc9cd7d523f0f7f1a2e32c4537a84467a * FIX: crash when async delete printer file Change-Id: I92c5e812d04da263338fb0eea2fd7583cf9ecde0 Jira: STUDIO-5222 * FIX: 3733 backup time not effective Jira: 3733 Change-Id: I50c2ce156fcbd0a17aa8a6777bce04aa6093c830 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: enable edit and delete preset btn and fix issue Jira: XXXX Change-Id: I724d7236b28fcc4746698f094531948a8fbb5d93 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX:send print job,file name displays error JIRA:3137 Change-Id: I1c113025d274a13fba1b845a58aada14058fadd4 * FIX: skip hold user preset from sync Change-Id: I2252246e19bd80903ad82170782ea49535d30d05 Jira: STUDIO-5185 * FIX: 5115 translations Jira: 5115 Change-Id: I21b03bdd4d28c0bb097226143177e763cf8c777f Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: add link for ironing parameter Change-Id: I451f5549db3ac2205aa5703a2e5edc831e946af8 * FIX: scale problem in lift type decide 1. Scale the travel threshhold jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ib594d640fe63b0919bc9318af88577513f7dbf30 * ENH: add small perimeter speed and threshold The original param is added by Prusa. Thanks orca for adding threshold. 1. Re add small perimeter speed and threhold. github: #2221 Change-Id: I35b269b26f085d80f0edca28650bb21fc04898d7 * FIX: modify the picture of pa manual cali Jira: STUDIO-5102 Change-Id: Id87898959ad4461b7bd2505b159271f2aa589c36 * FIX: Filament preset is the same with the first one Jira: STUDIO-4519 Filament preset is the same wit the first one, it should align with the last one. Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: I304d0ff0fbc1c8948d410ea552e4d42b6a4e8fd9 * FIX: scoreDailog dark mode issue Jira: 4570 Change-Id: I8be97b306a1494f73e3bba678ecc864e7ff88ca3 * FIX: CLI: fix the slicing issue while only one object with multicolor using seq-print JIRA: no-jira Change-Id: Iea2d23ff8e484bf2fd58aa2f596a8e4e4292fe39 * ENH: open support wall count for normal support 1. open support wall count for normal support Enabling this option makes normal support stronger and gives better overhang quality, but also more difficult to removal. Jira: STUDIO-5192 2. fix a bug where tree support (hybrid style) may get overlapped extrusions near the walls. 3. fix a bug where raft layers can't be 1 in tree support Jira: STUDIO-5261 Change-Id: Iadc0c67a9b50b5b221c8e83d5aa22ed282018cf8 (cherry picked from commit c0bb0084e386cb70ed6e16edf93190e4b38f5b90) * FIX: compiling error on linux jira: none Change-Id: I1a4563503b5ddf74a1979cc0cee7a15b8aced904 (cherry picked from commit de52c6ca62c9f3a6314ddf5a856c1d8534329886) * ENH: add translation for small perimeter jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I95013649e4e0c07c0f04b89a91488814c8d228cc * FIX: clone shortcut key issue on macos jira: STUDIO-5166 Change-Id: I1967da1d443ed43bd750dad8e11560688d7bd533 * FIX: custom gcode window cannot paste/ navigate jira: STUDIO-5208、STUDIO-5070 Change-Id: I4ecb9d06cf5db0ae53a4678181aae9298bac106b * ENH: modify dailytips collapse & expand interaction jira: STUDIO-5209、STUDIO-5210 Change-Id: Ifb0b998e5004d4b49390ba5a250eaf4743bf3471 * ENH:Add shortcut keys and lists for objects search JIRA: STUDIO-5157 STUDIO-5158 STUDIO-5240 Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Change-Id: Ic7cfaaa9b4bb8a85208bafab7fe3bafdb78f0045 * FIX:Re-calculate button with White Box displayed in dark mode JIRA: STUDIO-5098 Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Change-Id: I07cc6c72d5dbd03b72573cd27dd5938bb0e6a29a * NEW: display plate index when printing a task JIRA: STUDIO-2689 display on the thumbnail of the current task Change-Id: I5f1f46c56e9d1e9120a66d491551908dfad099d6 Signed-off-by: Stone Li <stone.li@bambulab.com> * ENH:fixed incorrect path prefix jira:[for file path prefix] Change-Id: Ie9e3999f02b78272e528ceceb479e746d46a7e6c * FIX: thumbnail is not clear in dark mode JIRA: STUDIO-5087 Change-Id: Ie86493ed71b5554095927f061509a1f551758b61 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> * FIX: translation Jira: XXXX Change-Id: Id03f0d704aa852632a907ea628d1277400112062 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH: first nozzle change to 0.4 and nozzle change to mm Jira: XXXX Change-Id: I14defd36442dbd7008e46782b08415b6244224f1 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * ENH:editing_window_width's value is small on the laptop Jira: STUDIO-5238 STUDIO-5265 apply_selected_connectors should check_and_update_connectors_state Change-Id: I8c2c1c920cc4d197d1908815a3e62f4962335451 * FIX: fix new_bed_shape's calculation process Jira: STUDIO-5122 Change-Id: I5f3e6a301a297123af28692c90bef6759f425b06 * ENH:update some translations jira:[STUDIO-5262] Change-Id: Idb1d3586888043ac325f272bc7a2b788adb3e9e5 * FIX: edit text command resets object settings Jira: STUDIO-4655 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: Iac25c4e40f1d0d32e6d1f40e62226cc22bc96042 * ci: update build version to 01.08.00.58 Change-Id: Iacfec02aa50c617e4c9fe566319b07b30d47dce1 * FIX: remove GetUserAgent Change-Id: I92886e1f0dcb091109231a10da8c19d51178e13b Jira: STUDIO-5205 * FIX: nozzle_diameter_map data structure adjustment Change-Id: Ifb724afc0fcf974e6d331e73ecac723107a102cf * ENH:add _A and _B for perform_with_groove Jira: STUDIO-5267 Change-Id: Iee3310dfa1cd8e6680310f0af0eff5c817490813 * ENH:is_equal for min_z and max_z Jira: STUDIO-5267 Change-Id: I9493883d8be9d44e26ddc9afe62b7e9eb09c5052 * ci: update build version to 01.08.00.59 Change-Id: Ie8ed29ccf5d6c94594eb2ab8b717416fbeace3bd * FIX:Image display unclear in light mode JIRA:5161 Change-Id: I134cc64a2af0dfff60c47d0ff09d78d9c0f86b3f * FIX:fix bugs of non manifold edge Jira: STUDIO-5267 Change-Id: I8ac9a2cf96da0bc07ee00b309e65611b92fd174d * ENH:nozzle type detection jira:[STUDIO-5246] Change-Id: Ic41a2161a0e41d23f56af93ad8ec34cc83ada0e3 * ENH: upadte P1S start gcode 1.turn on MC board fan by default on P1S jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I5b2f7868e350942fb8b7baf7d429e22a0987184a (cherry picked from commit e866a575b6b7d9552f7412f84272f4b48dfc3961) * ENH: improve support style's tooltip jira: none Change-Id: I8ee858d7052f04ce7ea6b226a500c7d1bf8a482f (cherry picked from commit 665f31c4fcde22bd894cbb4a5fb160635947f2a4) * ENH: set layer range error to warning 1. If layer range exceeds maximum/minimum layer range in printer settings,pop up a window to warn jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I0304ee790e557ecf967f355c171993d1f51b5057 * ENH: CLI: remove the warning of layer height JIRA: no jira Change-Id: Idaceee4f52611479fc3f4238d016d891b4dc8cd1 * FIX: the word search is not translated Jira: STUDIO-5224 The world search in the device panel is not translated. Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: Ia3d651c2159a3aad94e10cd0a6da98848f53ee2a (cherry picked from commit 4a46a0a4750d82d49c9523f4b668a1a00c41ed83) * FIX: Bitmap will flash when sending printing task Jira: STUDIO-5278 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: Ib0c8710b8d5d6b98fad043c950f054aa35bea965 * ENH:display the euler angle of rotation plane Jira: STUDIO-5268 Change-Id: I6b7b431931d60f1a9a832908400417781798c472 * ci: update build version to 01.08.00.60 Change-Id: I1c15b5c6437554c43327cd6b537f7a5860dba5a0 * ENH:cancel EnterReturnsTrue for imgui in cut Jira: STUDIO-5269 Change-Id: I2832e1dccaf9755448debe7b2bd56426f90dfe0d * ci: update build version to 01.08.00.61 Change-Id: Ib03e664a20990322c788686550c491d0139d8237 * FIX: some translation problems jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: If9f2af53b4f0bfa9469e84bcba68cc182df4a473 * add: Ukrainian lang support for 1.8 * fix linux * fix some string and colors * fix linux build error 2 * fix .gitignore * FIX: calibration selected preset is null in some case jira: STUDIO-5258 Change-Id: Iee63593c5f833c5a43e3b1d1c9ddb82f8c69569a * FIX: create filament issue Jira: 5296 5297 5295 5302 5311 5276 5296 create filament: list has same printer 5297 create filament: filament combobox has blank options 5298 edit filament: delete last preset prompt users 5302 create filament: filament combox has cili preset 5311 create filament: printer name too long to can not show all 5276 edit filament: PLA Aero filament type filter issue add prusa vendor Revised copy Change-Id: I5dcc615ce0951b1a9953fa12283f6090f5069045 * FIX: some translation Change-Id: Icb8614a0af18f96d15f3b97c17e0f6f708296847 * FIX:cancel is_equal for slicing function Jira: STUDIO-5267 Change-Id: I66d759aa2c968f8a28a6a5d8378929754f2db689 * FIX:UI stuck due to pop-up window with wrong chamber temperature JIRA: 5304 Change-Id: I1a49a7219b7a6f5700243704c348724e7930ce1a * FIX: allow input '+' and hide edit preset btn Change-Id: I97aec7c3ac4cc8b9d6c535f0126aaa1926553d86 * ENH: handle printer direct close and not retry Change-Id: I5dd55f8085cf6383a8420ab41e614ea6ae210c78 Jira: STUDIO-5305 * ci: update build version to 01.08.00.62 Change-Id: I09716bf79354b503197c751272fd3171e0abc8fd * add: new translation to ukr for AirFlow and Prusa * add: Texture Plate name fix * add new feature to localization .de, fix .it (#2876) * FIX:add slice_facet_for_cut_mesh api for cut tool and modify section_vertices_map 's traverse Jira: STUDIO-5267 Change-Id: Ifc4b183a4e4c4fdb4f47742f14f70a1ed93fa056 Change-Id: I52bfaef8926ef967b78a6cb712a1731a1b528a24 * FIX: Make the front smaller for Czech in device panel Jira: STUDIO-5151 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: I315174b55f923da069854fb4fed8cf3937b82074 * FIX: there is no object can be jumped to in notification jira: new Change-Id: Ib81bf49236952ede24a2de126051572d63916e01 * FIX: add height range, modifier in Preview pane will crash jira: STUDIO-5340 1. fix crash at add height range, modifiers in Preview from objectList 2. fix an assert hit when slicing 3. fix an assert hit when enter AssembleView 4. forbidden popup menu by right-click objectList in Preview Change-Id: I444bc76b1a4307999b387e4f60386b2d272bd308 * FIX: Black spot in the sending printing task page Jira: STUDIO-5307 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: I3bd97c063ec5f0faf7d12047da39f60ce55cae4b * FIX: reset_cut_by_contours should update_buffer_data Jira: STUDIO-5376 Change-Id: I5aacb1f7b65822031d7138abd61a45b09c743531 * ENH:editing_window_width's value is small on the laptop Jira: STUDIO-5238 STUDIO-5265 Change-Id: Ia958772bcb081817da621115f99328bb62770cd5 * ENH: bumped version to 1.8.1 Change-Id: I9d25403daa5b7b8ca415c0b364670da9e0f932b0 * FIX: create filament dialog: create btn can not show all Jira: 5310 5331 Change-Id: I185272c90d9ff1c3d6b47abbefbf488d0d965cca * FIX:update custom_texture when new_shape=false Jira: STUDIO-5287 Change-Id: I3add95f9f9345c14a48cc7467513d1b3ce95f4c9 * ENH:editing_window_width's value is small on the laptop Jira: STUDIO-5238 Change-Id: I9044129f4e0c8ca7469db05b3e547fca4754342a * FIX:add slash_to_back_slash for saving file path Jira: STUDIO-5287 Change-Id: I9f3c176cd0831c793958f08601c63efac98176a4 * FIX: a button color didn't response to dark mode change jira: STUDIO-5315 Change-Id: I95489f01ccd1f77b9e95b0d0f69e5398d2e88487 * FIX: height range layers displayed in wrong position jira: STUDIO-5341 Change-Id: I83918b4624f367efa54321f1898e1176cdb04ea9 * FIX: auto arranging issues with locked plates 1. global auto arranging may put items overlap with wipe tower if some plates are locked jira: STUDIO-5329 2. items outside bed may overlap with plate boundary if it's rotated jira: STUDIO-5329 3. plate-wise auto arranging uses wrong min_obj_distance if the plate is by-layer printing but global setting is by-object printing jira: STUDIO-5330 Change-Id: I5dba2f1317e183c9aeec1cb2bd227fbddf4316e6 (cherry picked from commit db1eac41efff5f1e8d5ac0af74c6fc7ab59fc253) * FIX: a mistake in upward machine jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ibdb26c3d904634f322aef0dc0c7b8867d9fb5854 * FIX:a blank pop-up appears JIRA:XXXX Change-Id: Ice92b55204e4897fec024a6d99412eb810bddd4a * FIX:fixed failure in updating nozzle type jira:[STUDIO-5248] Change-Id: Iad37b5d5b76d37cb1723ef21d7c39b1e3fcaf8d7 * FIX:fixed issue with AI monitoring settings jira:[STUDIO-5082] Change-Id: I967fe3c1e9da61a55bcbfaa2a8e067dd5af18f72 * FIX:fixed issue with lan mode jira:[STUDIO-5189] Change-Id: I1d0a05f19dcea154cf3ef2b61ed0546d3581905e * FIX:update text for loading or unloading filaments jira:[STUDIO-5231] Change-Id: Ic7729c3ec012485b3d87e3d01f11e87502c67895 * FIX: Revert "ENH: do not leave a gap for top... Revert "ENH: do not leave a gap for top interface if the top z distance is 0" This reverts commit 79ea32c7cbbdb7e689637980af7c36caf42284c9. Revert reason: the supports are impossible to remove in some cases. jira: STUDIO-5385 Change-Id: I376a6f4dfd78da6dfea68b9ac3d552cddd0b4272 (cherry picked from commit 34e38b705fde7f5d7f9a3a89c96a3627ce0c998e) * ENH: improve normal support's quality 1. Add a base_interface_layer when using Supp.W 2. Fix a bug where base_angle and interface_angle are wong jira: STUDIO-5386 Change-Id: I52ab32c63b3cd1e6e2ba6463b01ae26699cf13d3 (cherry picked from commit 92ddd4a10b793572a1fa009da5b9e44fcdf81de2) * NEW:tracking stl model files jira:[STUDIO-5372] Change-Id: Idb1275b07441f0cd06c24588d5f7c20f81f1556c * FIX: edit filament dialog: preset name too long to del btn nan't show Jira: 5336 5174 and verify string normalization Change-Id: I380c3bed2bf43d01094b68979a8b67f4187c0b93 * FIX: some translation Jira: 5232 5300 5334 Change-Id: Ie474ca823011e81aab82a9809af3d6e42980496b * FIX: some translation Change-Id: Iaabe6087bed3b7d47d911cf4fb51c770804e72fb * ENH: change default tree_support_wall_count to 0 Now normal support also uses this option, so we can't default it to 1, otherwise normal supports will be too hard to remove. jira: none Change-Id: Ic5700af5c17e3a7b265c8915f28b0db35c6e06e6 (cherry picked from commit 6b84a9826da108b76569e686bd9def0b23ae29fd) * FIX:The name of the material in the error prompt is empty JIRA:STUDIO-4907 Change-Id: I3cf44f099256a51f21a44a89c89c000f734d1f36 * ci: update build version to 01.08.01.51 Change-Id: Ib20f5a12b65472102befec0a2adf82744fc29c46 * FIX: imgui textinput cannot paste on macos jira: STUDIO-5070、STUDIO-5365 Change-Id: Iea8f41e12744ecda0fbb95c1a8f2e014a7cdc384 * FIX: not cache printer file list on error Change-Id: I99843aedbf14d3d1d553ccac9b0bd26403274a82 Jira: none * FIX: thread of close BBLUserPresetExceedLimit notify Change-Id: I9698134ba1cc91fc83eac441f900d68c4c4b556a * ENH: Resolve non manifold edges by fixing model interfaces Jira: STUDIO-5124 Change-Id: I7ea86be44acb80b6c4762a76208b4a031acd0b27 * FIX:nozzle type sync jira:[STUDIO-5248] Change-Id: I63d48628832473d8d371ed643dc8528b00382531 * FIX: array bound happen in TriangleSelector::deserialize Jira: STUDIO-5170 Change-Id: I92b72a887845b462fad208f13607293b44d3d333 * FIX:cropping rendering without considering assembly views Jira: STUDIO-5260 Change-Id: Ia56cf80b305ae05f25f06ec277f85b3c5430a6df * FIX: PA for custom filament not available in BL Studio github: 2971 Change-Id: I6ccd36a183e7367d69557300f7242f5403f4bb33 * FIX: Bitmap is way too small on Mac Jira: STUDIO-5393 Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Change-Id: I6b550669fa8cd5fc9bfa6ed96d64d19a949f01b2 * FIX: move shutdown wait to OnExit Change-Id: I70d9a2bb686525ae5273aa9d63e25691da4ab65c Jira: STUDIO-2884 * FIX: calibration manage result dialog issue on macos jira: STUDIO-4949 STUDIO-5378 Change-Id: I00abefd45a0d274a4b68bb1ab18debe8f91d169e * FIX: adjust bed shape dialog button UI style fix that button text is hard to see in dark mode jira: STUDIO-5247 Change-Id: I2cf5b3cdd2eff9b821bdf5525bec4f329fc58dd1 * FIX: 5331 rescale btn Jira: STUDIO-5331 Change-Id: If153424b8480e64b166018e3cd98c17db557d0a8 Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> * FIX: support do not generate jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Ide9709d95203185538e280517f7aa6136beeda44 * FIX: remove not match printer config ota cache Change-Id: Ib73fc2ea31fa2186061cfcb5a170bc59b9db84ca Jira: none * FIX:cancel the variable of "checkbox_size" as a fixed value Jira: STUDIO-5150 Change-Id: I30d876d141b8b35ab4a3fee4889993d87b7c1741 * ENH:add reset_cut_by_contours in on_load function Jira:STUDIO-5269 m_connector_size_tolerance default value is 0.1f Change-Id: I6c67fff3cb0c1190e9141ed6f68fbfa848679f35 * ENH:cancel EnterReturnsTrue for imgui in cut Jira: STUDIO-5269 Change-Id: Ifc4b183a4e4c4fdb4f47742f14f70a1ed93fa056 Signed-off-by: zhou.xu <zhou.xu@bambulab.com> * FIX: dailytips should not change content frequently when slicing all jira: STUDIO-5234 Change-Id: Icb7e9c28404d9db8ebed58d937e13f89c5403b5c * FIX: objectList clone shortcut key issue jira: new Change-Id: Ia75bf58a7d53f962e1af6c2fd97497270b7eea84 * ENH:handling cases without msgs jira:[STUDIO-5401 STUDIO-5399] Change-Id: Iae651d5a19a45b0138a6aa621326a8b4a9649824 * ENH: optimize param description jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: Id0ca9224227a716b21fc0b8430722264dc319344 * ENH: add translation jira:[NEW] Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Change-Id: I3b1f04fee3cd6322793794ad8b8707859f6c7d26 * FIX: close edit preset paramsDialog, mac unresponsive Jira: 5298 Change-Id: I021e00567354cfb1f2f5f1f2bf6ba1fc35b164c5 * ENH:disable AI monitoring on the p1p series Change-Id: I514bb1fb1ced6c03dd619230a9adac3be63f2de2 * ci: update build version to 01.08.01.52 Change-Id: I9f5e30d3fc4b7ef9321c522d3c18fce98f03742f * FIX: close edit preset paramsDialog, mac unresponsive Change-Id: Ic816754a20b7f6a5cdb46475750eb301fec3ad3a * FIX: organic support not work with raft only There is no raft generated when only raft enabled but no support needed. jira: none Change-Id: Ic0c9269e2f98038d85c9bc54e4a85f892dc5d764 * FIX: CLI: add object config when assemble JIRA: no jira Change-Id: I945f820fb58f2f643170b4b0b66742f6bbbdfd29 * FIX: delete preset prompt Jira: XXXX Change-Id: I6511c806c56393d4f6bd72d1c506da59675d49ff * FIX:Reorganize the assignment of variables of "m_editing_window_width" Jira: STUDIO-5238 Change-Id: If369916f3f5c21510f5f297bfd05c1230bdda7a4 * ENH: CLI: re-compute flush_volumes_matrix when it is missed Change-Id: Ie8f53c6bef003b1434de02ea14de5787b376484f * FIX: some translation for delete filament Change-Id: Ib46a8eba33f2e21016476aaab4a57a740e86b1b8 * FIX: scrolled window / del preset / edit filament issue Jira: 5092 GitHub: 1936 edit filament: just one preset, the scroll bar obscures the preset name edit filament: delete selected preset, click no, but preset be deleted from UI edit filament: serial sometimes displays incorrectly Change-Id: Ibc91609e252179de0c05ca065099756da6631165 * ci: update build version to 01.08.01.53 Change-Id: I5563a2c0812ab9a0d7727df27e17e681066ffa08 --------- Signed-off-by: maosheng.wei <maosheng.wei@bambulab.com> Signed-off-by: xun.zhang <xun.zhang@bambulab.com> Signed-off-by: Kunlong Ma <kunlong.ma@bambulab.com> Signed-off-by: wenjie.guo <wenjie.guo@bambulab.com> Signed-off-by: qing.zhang <qing.zhang@bambulab.com> Signed-off-by: Stone Li <stone.li@bambulab.com> Signed-off-by: zhou.xu <zhou.xu@bambulab.com> Co-authored-by: zorro.zhang <zorro.zhang@bambulab.com> Co-authored-by: liz.li <liz.li@bambulab.com> Co-authored-by: maosheng.wei <maosheng.wei@bambulab.com> Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com> Co-authored-by: tao wang <tao.wang@bambulab.com> Co-authored-by: Arthur <arthur.tang@bambulab.com> Co-authored-by: lane.wei <lane.wei@bambulab.com> Co-authored-by: gerrit <gerrit@bambulab.com> Co-authored-by: xun.zhang <xun.zhang@bambulab.com> Co-authored-by: zhou.xu <zhou.xu@bambulab.com> Co-authored-by: hu.wang <hu.wang@bambulab.com> Co-authored-by: Kunlong Ma <kunlong.ma@bambulab.com> Co-authored-by: wenjie.guo <wenjie.guo@bambulab.com> Co-authored-by: qing.zhang <qing.zhang@bambulab.com> Co-authored-by: zhimin.zeng <zhimin.zeng@bambulab.com> Co-authored-by: the Raz <rasmus@abc.se> Co-authored-by: Andy <andylg@yandex.ru> Co-authored-by: Stone Li <stone.li@bambulab.com> Co-authored-by: enricoturri1966 <enricoturri@seznam.cz> Co-authored-by: Dmytro Chystiakov <dlchistyakov@gmail.com> Co-authored-by: Heiko Liebscher <hliebscher@idn.de>
2074 lines
113 KiB
C++
2074 lines
113 KiB
C++
#include "BoundingBox.hpp"
|
|
#include "ClipperUtils.hpp"
|
|
#include "EdgeGrid.hpp"
|
|
#include "Layer.hpp"
|
|
#include "Print.hpp"
|
|
#include "Geometry/VoronoiVisualUtils.hpp"
|
|
#include "MutablePolygon.hpp"
|
|
#include "format.hpp"
|
|
|
|
#include <utility>
|
|
#include <cfloat>
|
|
#include <unordered_set>
|
|
|
|
#include <boost/log/trivial.hpp>
|
|
#include <tbb/parallel_for.h>
|
|
#include <mutex>
|
|
#include <boost/thread/lock_guard.hpp>
|
|
|
|
namespace Slic3r {
|
|
struct ColoredLine {
|
|
Line line;
|
|
int color;
|
|
int poly_idx = -1;
|
|
int local_line_idx = -1;
|
|
};
|
|
}
|
|
|
|
#include <boost/polygon/polygon.hpp>
|
|
namespace boost::polygon {
|
|
template <>
|
|
struct geometry_concept<Slic3r::ColoredLine> { typedef segment_concept type; };
|
|
|
|
template <>
|
|
struct segment_traits<Slic3r::ColoredLine> {
|
|
typedef coord_t coordinate_type;
|
|
typedef Slic3r::Point point_type;
|
|
|
|
static inline point_type get(const Slic3r::ColoredLine& line, const direction_1d& dir) {
|
|
return dir.to_int() ? line.line.b : line.line.a;
|
|
}
|
|
};
|
|
}
|
|
|
|
//#define MMU_SEGMENTATION_DEBUG_GRAPH
|
|
//#define MMU_SEGMENTATION_DEBUG_REGIONS
|
|
//#define MMU_SEGMENTATION_DEBUG_INPUT
|
|
//#define MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
//#define MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
|
|
|
namespace Slic3r {
|
|
bool is_equal(float left, float right, float eps = 1e-3) {
|
|
return abs(left - right) <= eps;
|
|
}
|
|
|
|
bool is_less(float left, float right, float eps = 1e-3) {
|
|
return left + eps < right;
|
|
}
|
|
|
|
// Assumes that is at most same projected_l length or below than projection_l
|
|
static bool project_line_on_line(const Line &projection_l, const Line &projected_l, Line *new_projected)
|
|
{
|
|
const Vec2d v1 = (projection_l.b - projection_l.a).cast<double>();
|
|
const Vec2d va = (projected_l.a - projection_l.a).cast<double>();
|
|
const Vec2d vb = (projected_l.b - projection_l.a).cast<double>();
|
|
const double l2 = v1.squaredNorm(); // avoid a sqrt
|
|
if (l2 == 0.0)
|
|
return false;
|
|
double t1 = va.dot(v1) / l2;
|
|
double t2 = vb.dot(v1) / l2;
|
|
t1 = std::clamp(t1, 0., 1.);
|
|
t2 = std::clamp(t2, 0., 1.);
|
|
assert(t1 >= 0.);
|
|
assert(t2 >= 0.);
|
|
assert(t1 <= 1.);
|
|
assert(t2 <= 1.);
|
|
|
|
Point p1 = projection_l.a + (t1 * v1).cast<coord_t>();
|
|
Point p2 = projection_l.a + (t2 * v1).cast<coord_t>();
|
|
*new_projected = Line(p1, p2);
|
|
return true;
|
|
}
|
|
|
|
struct PaintedLine
|
|
{
|
|
size_t contour_idx;
|
|
size_t line_idx;
|
|
Line projected_line;
|
|
int color;
|
|
};
|
|
|
|
struct PaintedLineVisitor
|
|
{
|
|
PaintedLineVisitor(const EdgeGrid::Grid &grid, std::vector<PaintedLine> &painted_lines, std::mutex &painted_lines_mutex, size_t reserve) : grid(grid), painted_lines(painted_lines), painted_lines_mutex(painted_lines_mutex)
|
|
{
|
|
painted_lines_set.reserve(reserve);
|
|
}
|
|
|
|
void reset() { painted_lines_set.clear(); }
|
|
|
|
bool operator()(coord_t iy, coord_t ix)
|
|
{
|
|
// Called with a row and column of the grid cell, which is intersected by a line.
|
|
auto cell_data_range = grid.cell_data_range(iy, ix);
|
|
const Vec2d v1 = line_to_test.vector().cast<double>();
|
|
const double v1_sqr_norm = v1.squaredNorm();
|
|
const double heuristic_thr_part = line_to_test.length() + append_threshold;
|
|
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
|
|
Line grid_line = grid.line(*it_contour_and_segment);
|
|
const Vec2d v2 = grid_line.vector().cast<double>();
|
|
double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length());
|
|
|
|
// An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other.
|
|
// This helps filter out cases when the following expensive calculations are useless.
|
|
if ((grid_line.a - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
|
(grid_line.b - line_to_test.a).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
|
(grid_line.a - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr ||
|
|
(grid_line.b - line_to_test.b).cast<double>().squaredNorm() > heuristic_thr_sqr)
|
|
continue;
|
|
|
|
// When lines have too different length, it is necessary to normalize them
|
|
if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) {
|
|
// The two vectors are nearly collinear (their mutual angle is lower than 30 degrees)
|
|
if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) {
|
|
if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 ||
|
|
grid_line.distance_to_squared(line_to_test.b) < append_threshold2 ||
|
|
line_to_test.distance_to_squared(grid_line.a) < append_threshold2 ||
|
|
line_to_test.distance_to_squared(grid_line.b) < append_threshold2) {
|
|
Line line_to_test_projected;
|
|
project_line_on_line(grid_line, line_to_test, &line_to_test_projected);
|
|
|
|
if ((line_to_test_projected.a - grid_line.a).cast<double>().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast<double>().squaredNorm())
|
|
line_to_test_projected.reverse();
|
|
|
|
painted_lines_set.insert(*it_contour_and_segment);
|
|
{
|
|
boost::lock_guard<std::mutex> lock(painted_lines_mutex);
|
|
painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Continue traversing the grid along the edge.
|
|
return true;
|
|
}
|
|
|
|
const EdgeGrid::Grid &grid;
|
|
std::vector<PaintedLine> &painted_lines;
|
|
std::mutex &painted_lines_mutex;
|
|
Line line_to_test;
|
|
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> painted_lines_set;
|
|
int color = -1;
|
|
|
|
static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.));
|
|
static inline const double append_threshold = 50 * SCALED_EPSILON;
|
|
static inline const double append_threshold2 = Slic3r::sqr(append_threshold);
|
|
};
|
|
|
|
static Polygon colored_points_to_polygon(const std::vector<ColoredLine> &lines)
|
|
{
|
|
Polygon out;
|
|
out.points.reserve(lines.size());
|
|
for (const ColoredLine &l : lines)
|
|
out.points.emplace_back(l.line.a);
|
|
return out;
|
|
}
|
|
|
|
static Polygons colored_points_to_polygon(const std::vector<std::vector<ColoredLine>> &lines)
|
|
{
|
|
Polygons out;
|
|
out.reserve(lines.size());
|
|
for (const std::vector<ColoredLine> &l : lines)
|
|
out.emplace_back(colored_points_to_polygon(l));
|
|
return out;
|
|
}
|
|
|
|
// Flatten the vector of vectors into a vector.
|
|
static inline std::vector<ColoredLine> to_lines(const std::vector<std::vector<ColoredLine>> &c_lines)
|
|
{
|
|
size_t n_lines = 0;
|
|
for (const auto &c_line : c_lines)
|
|
n_lines += c_line.size();
|
|
std::vector<ColoredLine> lines;
|
|
lines.reserve(n_lines);
|
|
for (const auto &c_line : c_lines)
|
|
lines.insert(lines.end(), c_line.begin(), c_line.end());
|
|
return lines;
|
|
}
|
|
|
|
static bool vertex_equal_to_point(const Voronoi::VD::vertex_type &vertex, const Vec2d &ipt)
|
|
{
|
|
// Convert ipt to doubles, force the 80bit FPU temporary to 64bit and then compare.
|
|
// This should work with any settings of math compiler switches and the C++ compiler
|
|
// shall understand the memcpies as type punning and it shall optimize them out.
|
|
using ulp_cmp_type = boost::polygon::detail::ulp_comparison<double>;
|
|
ulp_cmp_type ulp_cmp;
|
|
static constexpr int ULPS = boost::polygon::voronoi_diagram_traits<double>::vertex_equality_predicate_type::ULPS;
|
|
return ulp_cmp(vertex.x(), ipt.x(), ULPS) == ulp_cmp_type::EQUAL &&
|
|
ulp_cmp(vertex.y(), ipt.y(), ULPS) == ulp_cmp_type::EQUAL;
|
|
}
|
|
|
|
static inline bool vertex_equal_to_point(const Voronoi::VD::vertex_type *vertex, const Vec2d &ipt)
|
|
{
|
|
return vertex_equal_to_point(*vertex, ipt);
|
|
}
|
|
|
|
static std::vector<std::pair<size_t, size_t>> get_segments(const std::vector<ColoredLine> &polygon)
|
|
{
|
|
std::vector<std::pair<size_t, size_t>> segments;
|
|
|
|
size_t segment_end = 0;
|
|
while (segment_end + 1 < polygon.size() && polygon[segment_end].color == polygon[segment_end + 1].color)
|
|
segment_end++;
|
|
|
|
if (segment_end == polygon.size() - 1)
|
|
return {std::make_pair(0, polygon.size() - 1)};
|
|
|
|
size_t first_different_color = (segment_end + 1) % polygon.size();
|
|
for (size_t line_offset_idx = 0; line_offset_idx < polygon.size(); ++line_offset_idx) {
|
|
size_t start_s = (first_different_color + line_offset_idx) % polygon.size();
|
|
size_t end_s = start_s;
|
|
|
|
while (line_offset_idx + 1 < polygon.size() && polygon[start_s].color == polygon[(first_different_color + line_offset_idx + 1) % polygon.size()].color) {
|
|
end_s = (first_different_color + line_offset_idx + 1) % polygon.size();
|
|
line_offset_idx++;
|
|
}
|
|
segments.emplace_back(start_s, end_s);
|
|
}
|
|
return segments;
|
|
}
|
|
|
|
static std::vector<std::vector<std::pair<size_t, size_t>>> get_all_segments(const std::vector<std::vector<ColoredLine>> &color_poly)
|
|
{
|
|
std::vector<std::vector<std::pair<size_t, size_t>>> all_segments(color_poly.size());
|
|
for (size_t poly_idx = 0; poly_idx < color_poly.size(); ++poly_idx) {
|
|
const std::vector<ColoredLine> &c_polygon = color_poly[poly_idx];
|
|
all_segments[poly_idx] = get_segments(c_polygon);
|
|
}
|
|
return all_segments;
|
|
}
|
|
|
|
static std::vector<PaintedLine> filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector<PaintedLine> &painted_lines)
|
|
{
|
|
const int filter_eps_value = scale_(0.1f);
|
|
std::vector<PaintedLine> filtered_lines;
|
|
filtered_lines.emplace_back(painted_lines[start_idx]);
|
|
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
|
|
// line_to_process is already all colored. Skip another possible duplicate coloring.
|
|
if(filtered_lines.back().projected_line.b == line_to_process.b)
|
|
break;
|
|
|
|
PaintedLine &prev = filtered_lines.back();
|
|
const PaintedLine &curr = painted_lines[line_idx];
|
|
|
|
double prev_length = prev.projected_line.length();
|
|
double curr_dist_start = (curr.projected_line.a - prev.projected_line.a).cast<double>().norm();
|
|
double dist_between_lines = curr_dist_start - prev_length;
|
|
|
|
if (dist_between_lines >= 0) {
|
|
if (prev.color == curr.color) {
|
|
if (dist_between_lines <= filter_eps_value) {
|
|
prev.projected_line.b = curr.projected_line.b;
|
|
} else {
|
|
filtered_lines.emplace_back(curr);
|
|
}
|
|
} else {
|
|
filtered_lines.emplace_back(curr);
|
|
}
|
|
} else {
|
|
double curr_dist_end = (curr.projected_line.b - prev.projected_line.a).cast<double>().norm();
|
|
if (curr_dist_end > prev_length) {
|
|
if (prev.color == curr.color)
|
|
prev.projected_line.b = curr.projected_line.b;
|
|
else
|
|
filtered_lines.push_back({curr.contour_idx, curr.line_idx, Line{prev.projected_line.b, curr.projected_line.b}, curr.color});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (double dist_to_start = (filtered_lines.front().projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start <= filter_eps_value)
|
|
filtered_lines.front().projected_line.a = line_to_process.a;
|
|
|
|
if (double dist_to_end = (filtered_lines.back().projected_line.b - line_to_process.b).cast<double>().norm(); dist_to_end <= filter_eps_value)
|
|
filtered_lines.back().projected_line.b = line_to_process.b;
|
|
|
|
return filtered_lines;
|
|
}
|
|
|
|
static std::vector<std::vector<PaintedLine>> post_process_painted_lines(const std::vector<EdgeGrid::Contour> &contours, std::vector<PaintedLine> &&painted_lines)
|
|
{
|
|
if (painted_lines.empty())
|
|
return {};
|
|
|
|
auto comp = [&contours](const PaintedLine &first, const PaintedLine &second) {
|
|
Point first_start_p = contours[first.contour_idx].segment_start(first.line_idx);
|
|
return first.contour_idx < second.contour_idx ||
|
|
(first.contour_idx == second.contour_idx &&
|
|
(first.line_idx < second.line_idx ||
|
|
(first.line_idx == second.line_idx &&
|
|
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() < (second.projected_line.a - first_start_p).cast<double>().squaredNorm() ||
|
|
((first.projected_line.a - first_start_p).cast<double>().squaredNorm() == (second.projected_line.a - first_start_p).cast<double>().squaredNorm() &&
|
|
(first.projected_line.b - first.projected_line.a).cast<double>().squaredNorm() < (second.projected_line.b - second.projected_line.a).cast<double>().squaredNorm())))));
|
|
};
|
|
std::sort(painted_lines.begin(), painted_lines.end(), comp);
|
|
|
|
std::vector<std::vector<PaintedLine>> filtered_painted_lines(contours.size());
|
|
size_t prev_painted_line_idx = 0;
|
|
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_lines.size(); ++curr_painted_line_idx) {
|
|
size_t next_painted_line_idx = curr_painted_line_idx + 1;
|
|
if (next_painted_line_idx >= painted_lines.size() || painted_lines[curr_painted_line_idx].contour_idx != painted_lines[next_painted_line_idx].contour_idx || painted_lines[curr_painted_line_idx].line_idx != painted_lines[next_painted_line_idx].line_idx) {
|
|
const PaintedLine &start_line = painted_lines[prev_painted_line_idx];
|
|
const Line &line_to_process = contours[start_line.contour_idx].get_segment(start_line.line_idx);
|
|
Slic3r::append(filtered_painted_lines[painted_lines[curr_painted_line_idx].contour_idx], filter_painted_lines(line_to_process, prev_painted_line_idx, curr_painted_line_idx, painted_lines));
|
|
prev_painted_line_idx = next_painted_line_idx;
|
|
}
|
|
}
|
|
|
|
return filtered_painted_lines;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
static bool are_lines_connected(const std::vector<ColoredLine> &colored_lines)
|
|
{
|
|
for (size_t line_idx = 1; line_idx < colored_lines.size(); ++line_idx)
|
|
if (colored_lines[line_idx - 1].line.b != colored_lines[line_idx].line.a)
|
|
return false;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static std::vector<ColoredLine> colorize_line(const Line &line_to_process,
|
|
const size_t start_idx,
|
|
const size_t end_idx,
|
|
const std::vector<PaintedLine> &painted_contour)
|
|
{
|
|
assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx);
|
|
assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; }));
|
|
|
|
const int filter_eps_value = scale_(0.1f);
|
|
std::vector<ColoredLine> final_lines;
|
|
const PaintedLine &first_line = painted_contour[start_idx];
|
|
if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast<double>().norm(); dist_to_start > filter_eps_value)
|
|
final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0});
|
|
final_lines.push_back({first_line.projected_line, first_line.color});
|
|
|
|
for (size_t line_idx = start_idx + 1; line_idx <= end_idx; ++line_idx) {
|
|
ColoredLine &prev = final_lines.back();
|
|
const PaintedLine &curr = painted_contour[line_idx];
|
|
|
|
double line_dist = (curr.projected_line.a - prev.line.b).cast<double>().norm();
|
|
if (line_dist <= filter_eps_value) {
|
|
if (prev.color == curr.color) {
|
|
prev.line.b = curr.projected_line.b;
|
|
} else {
|
|
prev.line.b = curr.projected_line.a;
|
|
final_lines.push_back({curr.projected_line, curr.color});
|
|
}
|
|
} else {
|
|
final_lines.push_back({Line(prev.line.b, curr.projected_line.a), 0});
|
|
final_lines.push_back({curr.projected_line, curr.color});
|
|
}
|
|
}
|
|
|
|
// If there is non-painted space, then inserts line painted by a default color.
|
|
if (double dist_to_end = (final_lines.back().line.b - line_to_process.b).cast<double>().norm(); dist_to_end > filter_eps_value)
|
|
final_lines.push_back({Line(final_lines.back().line.b, line_to_process.b), 0});
|
|
|
|
// Make sure all the lines are connected.
|
|
assert(are_lines_connected(final_lines));
|
|
|
|
for (size_t line_idx = 2; line_idx < final_lines.size(); ++line_idx) {
|
|
const ColoredLine &line_0 = final_lines[line_idx - 2];
|
|
ColoredLine &line_1 = final_lines[line_idx - 1];
|
|
const ColoredLine &line_2 = final_lines[line_idx - 0];
|
|
|
|
if (line_0.color == line_2.color && line_0.color != line_1.color)
|
|
if (line_1.line.length() <= scale_(0.2)) line_1.color = line_0.color;
|
|
}
|
|
|
|
std::vector<ColoredLine> colored_lines_simple;
|
|
colored_lines_simple.emplace_back(final_lines.front());
|
|
for (size_t line_idx = 1; line_idx < final_lines.size(); ++line_idx) {
|
|
const ColoredLine &line_0 = final_lines[line_idx];
|
|
|
|
if (colored_lines_simple.back().color == line_0.color)
|
|
colored_lines_simple.back().line.b = line_0.line.b;
|
|
else
|
|
colored_lines_simple.emplace_back(line_0);
|
|
}
|
|
|
|
final_lines = colored_lines_simple;
|
|
|
|
if (final_lines.size() > 1)
|
|
if (final_lines.front().color != final_lines[1].color && final_lines.front().line.length() <= scale_(0.2)) {
|
|
final_lines[1].line.a = final_lines.front().line.a;
|
|
final_lines.erase(final_lines.begin());
|
|
}
|
|
|
|
if (final_lines.size() > 1)
|
|
if (final_lines.back().color != final_lines[final_lines.size() - 2].color && final_lines.back().line.length() <= scale_(0.2)) {
|
|
final_lines[final_lines.size() - 2].line.b = final_lines.back().line.b;
|
|
final_lines.pop_back();
|
|
}
|
|
|
|
return final_lines;
|
|
}
|
|
|
|
static std::vector<ColoredLine> filter_colorized_polygon(std::vector<ColoredLine> &&new_lines) {
|
|
for (size_t line_idx = 2; line_idx < new_lines.size(); ++line_idx) {
|
|
const ColoredLine &line_0 = new_lines[line_idx - 2];
|
|
ColoredLine &line_1 = new_lines[line_idx - 1];
|
|
const ColoredLine &line_2 = new_lines[line_idx - 0];
|
|
|
|
if (line_0.color == line_2.color && line_0.color != line_1.color && line_0.color >= 1) {
|
|
if (line_1.line.length() <= scale_(0.5)) line_1.color = line_0.color;
|
|
}
|
|
}
|
|
|
|
for (size_t line_idx = 3; line_idx < new_lines.size(); ++line_idx) {
|
|
const ColoredLine &line_0 = new_lines[line_idx - 3];
|
|
ColoredLine &line_1 = new_lines[line_idx - 2];
|
|
ColoredLine &line_2 = new_lines[line_idx - 1];
|
|
const ColoredLine &line_3 = new_lines[line_idx - 0];
|
|
|
|
if (line_0.color == line_3.color && (line_0.color != line_1.color || line_0.color != line_2.color) && line_0.color >= 1 && line_3.color >= 1) {
|
|
if ((line_1.line.length() + line_2.line.length()) <= scale_(0.5)) {
|
|
line_1.color = line_0.color;
|
|
line_2.color = line_0.color;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<size_t, size_t>> segments = get_segments(new_lines);
|
|
auto segment_length = [&new_lines](const std::pair<size_t, size_t> &segment) {
|
|
double total_length = 0;
|
|
for (size_t seg_start_idx = segment.first; seg_start_idx != segment.second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
|
total_length += new_lines[seg_start_idx].line.length();
|
|
total_length += new_lines[segment.second].line.length();
|
|
return total_length;
|
|
};
|
|
|
|
if (segments.size() >= 2)
|
|
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
|
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
|
assert(curr_idx != next_idx);
|
|
|
|
int color0 = new_lines[segments[curr_idx].first].color;
|
|
int color1 = new_lines[segments[next_idx].first].color;
|
|
|
|
double seg0l = segment_length(segments[curr_idx]);
|
|
double seg1l = segment_length(segments[next_idx]);
|
|
|
|
if (color0 != color1 && seg0l >= scale_(0.1) && seg1l <= scale_(0.2)) {
|
|
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
|
new_lines[seg_start_idx].color = color0;
|
|
new_lines[segments[next_idx].second].color = color0;
|
|
}
|
|
}
|
|
|
|
segments = get_segments(new_lines);
|
|
if (segments.size() >= 2)
|
|
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
|
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
|
assert(curr_idx != next_idx);
|
|
|
|
int color0 = new_lines[segments[curr_idx].first].color;
|
|
int color1 = new_lines[segments[next_idx].first].color;
|
|
double seg1l = segment_length(segments[next_idx]);
|
|
|
|
if (color0 >= 1 && color0 != color1 && seg1l <= scale_(0.2)) {
|
|
for (size_t seg_start_idx = segments[next_idx].first; seg_start_idx != segments[next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
|
new_lines[seg_start_idx].color = color0;
|
|
new_lines[segments[next_idx].second].color = color0;
|
|
}
|
|
}
|
|
|
|
segments = get_segments(new_lines);
|
|
if (segments.size() >= 3)
|
|
for (size_t curr_idx = 0; curr_idx < segments.size(); ++curr_idx) {
|
|
size_t next_idx = next_idx_modulo(curr_idx, segments.size());
|
|
size_t next_next_idx = next_idx_modulo(next_idx, segments.size());
|
|
|
|
int color0 = new_lines[segments[curr_idx].first].color;
|
|
int color1 = new_lines[segments[next_idx].first].color;
|
|
int color2 = new_lines[segments[next_next_idx].first].color;
|
|
|
|
if (color0 > 0 && color0 == color2 && color0 != color1 && segment_length(segments[next_idx]) <= scale_(0.5)) {
|
|
for (size_t seg_start_idx = segments[next_next_idx].first; seg_start_idx != segments[next_next_idx].second; seg_start_idx = (seg_start_idx + 1 < new_lines.size()) ? seg_start_idx + 1 : 0)
|
|
new_lines[seg_start_idx].color = color0;
|
|
new_lines[segments[next_next_idx].second].color = color0;
|
|
}
|
|
}
|
|
|
|
return std::move(new_lines);
|
|
}
|
|
|
|
static std::vector<ColoredLine> colorize_contour(const EdgeGrid::Contour &contour, const std::vector<PaintedLine> &painted_contour) {
|
|
assert(painted_contour.empty() || std::all_of(painted_contour.begin(), painted_contour.end(), [&painted_contour](const auto &p_line) { return painted_contour.front().contour_idx == p_line.contour_idx; }));
|
|
|
|
std::vector<ColoredLine> colorized_contour;
|
|
if (painted_contour.empty()) {
|
|
// Appends contour with default color for lines before the first PaintedLine.
|
|
colorized_contour.reserve(contour.num_segments());
|
|
for (const Line &line : contour.get_segments())
|
|
colorized_contour.emplace_back(ColoredLine{line, 0});
|
|
return colorized_contour;
|
|
}
|
|
|
|
colorized_contour.reserve(contour.num_segments() + painted_contour.size());
|
|
for (size_t idx = 0; idx < painted_contour.front().line_idx; ++idx)
|
|
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
|
|
|
size_t prev_painted_line_idx = 0;
|
|
for (size_t curr_painted_line_idx = 0; curr_painted_line_idx < painted_contour.size(); ++curr_painted_line_idx) {
|
|
size_t next_painted_line_idx = curr_painted_line_idx + 1;
|
|
if (next_painted_line_idx >= painted_contour.size() || painted_contour[curr_painted_line_idx].line_idx != painted_contour[next_painted_line_idx].line_idx) {
|
|
const std::vector<PaintedLine> &painted_contour_copy = painted_contour;
|
|
Slic3r::append(colorized_contour, colorize_line(contour.get_segment(painted_contour[prev_painted_line_idx].line_idx), prev_painted_line_idx, curr_painted_line_idx, painted_contour_copy));
|
|
|
|
// Appends contour with default color for lines between the current and the next PaintedLine.
|
|
if (next_painted_line_idx < painted_contour.size())
|
|
for (size_t idx = painted_contour[curr_painted_line_idx].line_idx + 1; idx < painted_contour[next_painted_line_idx].line_idx; ++idx)
|
|
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
|
|
|
prev_painted_line_idx = next_painted_line_idx;
|
|
}
|
|
}
|
|
|
|
// Appends contour with default color for lines after the last PaintedLine.
|
|
for (size_t idx = painted_contour.back().line_idx + 1; idx < contour.num_segments(); ++idx)
|
|
colorized_contour.emplace_back(ColoredLine{contour.get_segment(idx), 0});
|
|
|
|
assert(!colorized_contour.empty());
|
|
return filter_colorized_polygon(std::move(colorized_contour));
|
|
}
|
|
|
|
static std::vector<std::vector<ColoredLine>> colorize_contours(const std::vector<EdgeGrid::Contour> &contours, const std::vector<std::vector<PaintedLine>> &painted_contours)
|
|
{
|
|
assert(contours.size() == painted_contours.size());
|
|
std::vector<std::vector<ColoredLine>> colorized_contours(contours.size());
|
|
for (const std::vector<PaintedLine> &painted_contour : painted_contours) {
|
|
size_t contour_idx = &painted_contour - &painted_contours.front();
|
|
colorized_contours[contour_idx] = colorize_contour(contours[contour_idx], painted_contours[contour_idx]);
|
|
}
|
|
return colorized_contours;
|
|
}
|
|
|
|
using boost::polygon::voronoi_diagram;
|
|
|
|
static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return {coord_t(point->x()), coord_t(point->y())}; }
|
|
|
|
static inline Point mk_point(const Voronoi::Internal::point_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
|
|
|
|
static inline Point mk_point(const voronoi_diagram<double>::vertex_type &point) { return {coord_t(point.x()), coord_t(point.y())}; }
|
|
|
|
static inline Point mk_point(const Vec2d &point) { return {coord_t(std::round(point.x())), coord_t(std::round(point.y()))}; }
|
|
|
|
static inline Vec2d mk_vec2(const voronoi_diagram<double>::vertex_type *point) { return {point->x(), point->y()}; }
|
|
|
|
struct MMU_Graph
|
|
{
|
|
enum class ARC_TYPE { BORDER, NON_BORDER };
|
|
|
|
struct Arc
|
|
{
|
|
size_t from_idx;
|
|
size_t to_idx;
|
|
int color;
|
|
ARC_TYPE type;
|
|
|
|
bool operator==(const Arc &rhs) const { return (from_idx == rhs.from_idx) && (to_idx == rhs.to_idx) && (color == rhs.color) && (type == rhs.type); }
|
|
bool operator!=(const Arc &rhs) const { return !operator==(rhs); }
|
|
};
|
|
|
|
struct Node
|
|
{
|
|
Vec2d point;
|
|
std::list<size_t> arc_idxs;
|
|
|
|
void remove_edge(const size_t to_idx, MMU_Graph &graph)
|
|
{
|
|
for (auto arc_it = this->arc_idxs.begin(); arc_it != this->arc_idxs.end(); ++arc_it) {
|
|
MMU_Graph::Arc &arc = graph.arcs[*arc_it];
|
|
if (arc.to_idx == to_idx) {
|
|
assert(arc.type != ARC_TYPE::BORDER);
|
|
this->arc_idxs.erase(arc_it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
std::vector<MMU_Graph::Node> nodes;
|
|
std::vector<MMU_Graph::Arc> arcs;
|
|
size_t all_border_points{};
|
|
|
|
std::vector<size_t> polygon_idx_offset;
|
|
std::vector<size_t> polygon_sizes;
|
|
|
|
void remove_edge(const size_t from_idx, const size_t to_idx)
|
|
{
|
|
nodes[from_idx].remove_edge(to_idx, *this);
|
|
nodes[to_idx].remove_edge(from_idx, *this);
|
|
}
|
|
|
|
[[nodiscard]] size_t get_global_index(const size_t poly_idx, const size_t point_idx) const { return polygon_idx_offset[poly_idx] + point_idx; }
|
|
|
|
void append_edge(const size_t &from_idx, const size_t &to_idx, int color = -1, ARC_TYPE type = ARC_TYPE::NON_BORDER)
|
|
{
|
|
// Don't append duplicate edges between the same nodes.
|
|
for (const size_t &arc_idx : this->nodes[from_idx].arc_idxs)
|
|
if (arcs[arc_idx].to_idx == to_idx)
|
|
return;
|
|
for (const size_t &arc_idx : this->nodes[to_idx].arc_idxs)
|
|
if (arcs[arc_idx].to_idx == from_idx)
|
|
return;
|
|
|
|
this->nodes[from_idx].arc_idxs.push_back(this->arcs.size());
|
|
this->arcs.push_back({from_idx, to_idx, color, type});
|
|
|
|
// Always insert only one directed arc for the input polygons.
|
|
// Two directed arcs in both directions are inserted if arcs aren't between points of the input polygons.
|
|
if (type == ARC_TYPE::NON_BORDER) {
|
|
this->nodes[to_idx].arc_idxs.push_back(this->arcs.size());
|
|
this->arcs.push_back({to_idx, from_idx, color, type});
|
|
}
|
|
}
|
|
|
|
// It assumes that between points of the input polygons is always only one directed arc,
|
|
// with the same direction as lines of the input polygon.
|
|
[[nodiscard]] MMU_Graph::Arc get_border_arc(size_t idx) const {
|
|
assert(idx < this->all_border_points);
|
|
return this->arcs[idx];
|
|
}
|
|
|
|
[[nodiscard]] size_t nodes_count() const { return this->nodes.size(); }
|
|
|
|
void remove_nodes_with_one_arc()
|
|
{
|
|
std::queue<size_t> update_queue;
|
|
for (const MMU_Graph::Node &node : this->nodes) {
|
|
size_t node_idx = &node - &this->nodes.front();
|
|
// Skip nodes that represent points of input polygons.
|
|
if (node.arc_idxs.size() == 1 && node_idx >= this->all_border_points)
|
|
update_queue.emplace(&node - &this->nodes.front());
|
|
}
|
|
|
|
while (!update_queue.empty()) {
|
|
size_t node_from_idx = update_queue.front();
|
|
MMU_Graph::Node &node_from = this->nodes[update_queue.front()];
|
|
update_queue.pop();
|
|
if (node_from.arc_idxs.empty())
|
|
continue;
|
|
|
|
assert(node_from.arc_idxs.size() == 1);
|
|
size_t node_to_idx = arcs[node_from.arc_idxs.front()].to_idx;
|
|
MMU_Graph::Node &node_to = this->nodes[node_to_idx];
|
|
this->remove_edge(node_from_idx, node_to_idx);
|
|
if (node_to.arc_idxs.size() == 1 && node_to_idx >= this->all_border_points)
|
|
update_queue.emplace(node_to_idx);
|
|
}
|
|
}
|
|
|
|
void add_contours(const std::vector<std::vector<ColoredLine>> &color_poly)
|
|
{
|
|
this->all_border_points = nodes.size();
|
|
this->polygon_sizes = std::vector<size_t>(color_poly.size());
|
|
for (size_t polygon_idx = 0; polygon_idx < color_poly.size(); ++polygon_idx)
|
|
this->polygon_sizes[polygon_idx] = color_poly[polygon_idx].size();
|
|
this->polygon_idx_offset = std::vector<size_t>(color_poly.size());
|
|
this->polygon_idx_offset[0] = 0;
|
|
for (size_t polygon_idx = 1; polygon_idx < color_poly.size(); ++polygon_idx) {
|
|
this->polygon_idx_offset[polygon_idx] = this->polygon_idx_offset[polygon_idx - 1] + color_poly[polygon_idx - 1].size();
|
|
}
|
|
|
|
size_t poly_idx = 0;
|
|
for (const std::vector<ColoredLine> &color_lines : color_poly) {
|
|
size_t line_idx = 0;
|
|
for (const ColoredLine &color_line : color_lines) {
|
|
size_t from_idx = this->get_global_index(poly_idx, line_idx);
|
|
size_t to_idx = this->get_global_index(poly_idx, (line_idx + 1) % color_lines.size());
|
|
this->append_edge(from_idx, to_idx, color_line.color, ARC_TYPE::BORDER);
|
|
++line_idx;
|
|
}
|
|
++poly_idx;
|
|
}
|
|
}
|
|
|
|
// Nodes 0..all_border_points are only one with are on countour. Other vertexis are consider as not on coouter. So we check if base on attach index
|
|
inline bool is_vertex_on_contour(const Voronoi::VD::vertex_type *vertex) const
|
|
{
|
|
assert(vertex != nullptr);
|
|
return vertex->color() < this->all_border_points;
|
|
}
|
|
|
|
[[nodiscard]] inline bool is_edge_attach_to_contour(const voronoi_diagram<double>::const_edge_iterator &edge_iterator) const
|
|
{
|
|
return this->is_vertex_on_contour(edge_iterator->vertex0()) || this->is_vertex_on_contour(edge_iterator->vertex1());
|
|
}
|
|
|
|
[[nodiscard]] inline bool is_edge_connecting_two_contour_vertices(const voronoi_diagram<double>::const_edge_iterator &edge_iterator) const
|
|
{
|
|
return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1());
|
|
}
|
|
|
|
// All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges.
|
|
// Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them.
|
|
void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) {
|
|
bbox.offset(SCALED_EPSILON);
|
|
|
|
struct CPoint
|
|
{
|
|
CPoint() = delete;
|
|
CPoint(const Vec2d &point, size_t contour_idx, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(contour_idx) {}
|
|
CPoint(const Vec2d &point, size_t point_idx) : m_point_double(point), m_point(mk_point(point)), m_point_idx(point_idx), m_contour_idx(0) {}
|
|
const Vec2d m_point_double;
|
|
const Point m_point;
|
|
size_t m_point_idx;
|
|
size_t m_contour_idx;
|
|
|
|
[[nodiscard]] const Vec2d &point_double() const { return m_point_double; }
|
|
[[nodiscard]] const Point &point() const { return m_point; }
|
|
bool operator==(const CPoint &rhs) const { return this->m_point_double == rhs.m_point_double && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; }
|
|
};
|
|
struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }};
|
|
typedef ClosestPointInRadiusLookup<CPoint, CPointAccessor> CPointLookupType;
|
|
|
|
CPointLookupType closest_voronoi_point(coord_t(SCALED_EPSILON));
|
|
CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON));
|
|
for (const Polygon &polygon : color_poly_tmp)
|
|
for (const Point &pt : polygon.points)
|
|
closest_contour_point.insert(CPoint(Vec2d(pt.x(), pt.y()), &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front()));
|
|
|
|
for (const voronoi_diagram<double>::vertex_type &vertex : vd.vertices()) {
|
|
vertex.color(-1);
|
|
Vec2d vertex_point_double = Vec2d(vertex.x(), vertex.y());
|
|
Point vertex_point = mk_point(vertex);
|
|
|
|
const Vec2d &first_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point;
|
|
const Vec2d &second_point_double = this->nodes[this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point;
|
|
|
|
if (vertex_equal_to_point(&vertex, first_point_double)) {
|
|
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
|
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
|
vertex.color(this->get_border_arc(vertex.incident_edge()->cell()->source_index()).from_idx);
|
|
} else if (vertex_equal_to_point(&vertex, second_point_double)) {
|
|
assert(vertex.color() != vertex.incident_edge()->cell()->source_index());
|
|
assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index());
|
|
vertex.color(this->get_border_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx);
|
|
} else if (bbox.contains(vertex_point)) {
|
|
if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < Slic3r::sqr(3 * SCALED_EPSILON)) {
|
|
vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx));
|
|
} else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= Slic3r::sqr(SCALED_EPSILON / 10.0)) {
|
|
closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
|
|
vertex.color(this->nodes_count());
|
|
this->nodes.push_back({vertex_point_double});
|
|
} else {
|
|
// Boost Voronoi diagram generator sometimes creates two very closed points instead of one point.
|
|
// For the example points (146872.99999999997, -146872.99999999997) and (146873, -146873), this example also included in Voronoi generator test cases.
|
|
std::vector<std::pair<const CPoint *, double>> all_closes_c_points = closest_voronoi_point.find_all(vertex_point);
|
|
int merge_to_point = -1;
|
|
for (const std::pair<const CPoint *, double> &c_point : all_closes_c_points)
|
|
if ((vertex_point_double - c_point.first->point_double()).squaredNorm() <= Slic3r::sqr(EPSILON)) {
|
|
merge_to_point = int(c_point.first->m_point_idx);
|
|
break;
|
|
}
|
|
|
|
if (merge_to_point != -1) {
|
|
vertex.color(merge_to_point);
|
|
} else {
|
|
closest_voronoi_point.insert(CPoint(vertex_point_double, this->nodes_count()));
|
|
vertex.color(this->nodes_count());
|
|
this->nodes.push_back({vertex_point_double});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void garbage_collect()
|
|
{
|
|
std::vector<int> nodes_map(this->nodes.size(), -1);
|
|
int nodes_count = 0;
|
|
size_t arcs_count = 0;
|
|
for (const MMU_Graph::Node &node : this->nodes)
|
|
if (size_t node_idx = &node - &this->nodes.front(); !node.arc_idxs.empty()) {
|
|
nodes_map[node_idx] = nodes_count++;
|
|
arcs_count += node.arc_idxs.size();
|
|
}
|
|
|
|
std::vector<MMU_Graph::Node> new_nodes;
|
|
std::vector<MMU_Graph::Arc> new_arcs;
|
|
new_nodes.reserve(nodes_count);
|
|
new_arcs.reserve(arcs_count);
|
|
for (const MMU_Graph::Node &node : this->nodes)
|
|
if (size_t node_idx = &node - &this->nodes.front(); nodes_map[node_idx] >= 0) {
|
|
new_nodes.push_back({node.point});
|
|
for (const size_t &arc_idx : node.arc_idxs) {
|
|
const Arc &arc = this->arcs[arc_idx];
|
|
new_nodes.back().arc_idxs.emplace_back(new_arcs.size());
|
|
new_arcs.push_back({size_t(nodes_map[arc.from_idx]), size_t(nodes_map[arc.to_idx]), arc.color, arc.type});
|
|
}
|
|
}
|
|
|
|
this->nodes = std::move(new_nodes);
|
|
this->arcs = std::move(new_arcs);
|
|
}
|
|
};
|
|
|
|
static inline void mark_processed(const voronoi_diagram<double>::const_edge_iterator &edge_iterator)
|
|
{
|
|
edge_iterator->color(true);
|
|
edge_iterator->twin()->color(true);
|
|
}
|
|
|
|
// Return true, if "p" is closer to line.a, then line.b
|
|
static inline bool is_point_closer_to_beginning_of_line(const Line &line, const Point &p)
|
|
{
|
|
return (p - line.a).cast<double>().squaredNorm() < (p - line.b).cast<double>().squaredNorm();
|
|
}
|
|
|
|
static inline bool has_same_color(const ColoredLine &cl1, const ColoredLine &cl2) { return cl1.color == cl2.color; }
|
|
|
|
// Determines if the line points from the point between two contour lines is pointing inside polygon or outside.
|
|
static inline bool points_inside(const Line &contour_first, const Line &contour_second, const Point &new_point)
|
|
{
|
|
// Used in points_inside for decision if line leading thought the common point of two lines is pointing inside polygon or outside
|
|
auto three_points_inward_normal = [](const Point &left, const Point &middle, const Point &right) -> Vec2d {
|
|
assert(left != middle);
|
|
assert(middle != right);
|
|
return (perp(Point(middle - left)).cast<double>().normalized() + perp(Point(right - middle)).cast<double>().normalized()).normalized();
|
|
};
|
|
|
|
assert(contour_first.b == contour_second.a);
|
|
Vec2d inward_normal = three_points_inward_normal(contour_first.a, contour_first.b, contour_second.b);
|
|
Vec2d edge_norm = (new_point - contour_first.b).cast<double>().normalized();
|
|
double side = inward_normal.dot(edge_norm);
|
|
// assert(side != 0.);
|
|
return side > 0.;
|
|
}
|
|
|
|
static inline bool line_intersection_with_epsilon(const Line &line_to_extend, const Line &other, Point *intersection)
|
|
{
|
|
Line extended_line = line_to_extend;
|
|
extended_line.extend(15 * SCALED_EPSILON);
|
|
return extended_line.intersection(other, intersection);
|
|
}
|
|
|
|
// For every ColoredLine in lines_colored_out, assign the index of the polygon to which belongs and also the index of this line inside of the polygon.
|
|
static inline void init_polygon_indices(const MMU_Graph &graph,
|
|
const std::vector<std::vector<ColoredLine>> &color_poly,
|
|
std::vector<ColoredLine> &lines_colored_out)
|
|
{
|
|
size_t poly_idx = 0;
|
|
for (const std::vector<ColoredLine> &color_lines : color_poly) {
|
|
size_t line_idx = 0;
|
|
for (size_t color_line_idx = 0; color_line_idx < color_lines.size(); ++color_line_idx) {
|
|
size_t from_idx = graph.get_global_index(poly_idx, line_idx);
|
|
lines_colored_out[from_idx].poly_idx = int(poly_idx);
|
|
lines_colored_out[from_idx].local_line_idx = int(line_idx);
|
|
++line_idx;
|
|
}
|
|
++poly_idx;
|
|
}
|
|
}
|
|
|
|
// Voronoi edges produced by Voronoi generator cloud have coordinates that don't fit inside coord_t (int32_t).
|
|
// Because of that, this function tries to clip edges that have one endpoint of the edge inside the BoundingBox.
|
|
static inline Line clip_finite_voronoi_edge(const Voronoi::VD::edge_type &edge, const BoundingBoxf &bbox)
|
|
{
|
|
assert(edge.is_finite());
|
|
Vec2d v0 = mk_vec2(edge.vertex0());
|
|
Vec2d v1 = mk_vec2(edge.vertex1());
|
|
bool contains_v0 = bbox.contains(v0);
|
|
bool contains_v1 = bbox.contains(v1);
|
|
if ((contains_v0 && contains_v1) || (!contains_v0 && !contains_v1))
|
|
return {mk_point(edge.vertex0()), mk_point(edge.vertex1())};
|
|
|
|
Vec2d vector = (v1 - v0).normalized() * bbox.size().norm();
|
|
if (!contains_v0)
|
|
v0 = (v1 - vector);
|
|
else
|
|
v1 = (v0 + vector);
|
|
|
|
return {v0.cast<coord_t>(), v1.cast<coord_t>()};
|
|
}
|
|
|
|
static MMU_Graph build_graph(size_t layer_idx, const std::vector<std::vector<ColoredLine>> &color_poly)
|
|
{
|
|
Geometry::VoronoiDiagram vd;
|
|
std::vector<ColoredLine> lines_colored = to_lines(color_poly);
|
|
const Polygons color_poly_tmp = colored_points_to_polygon(color_poly);
|
|
const Points points = to_points(color_poly_tmp);
|
|
const Lines lines = to_lines(color_poly_tmp);
|
|
|
|
// The algorithm adds edges to the graph that are between two different colors.
|
|
// If a polygon is colored entirely with one color, we need to add at least one edge from that polygon artificially.
|
|
// Adding this edge is necessary for cases where the expolygon has an outer contour colored whole with one color
|
|
// and a hole colored with a different color. If an edge wasn't added to the graph,
|
|
// the entire expolygon would be colored with single random color instead of two different.
|
|
std::vector<bool> force_edge_adding(color_poly.size());
|
|
|
|
// For each polygon, check if it is all colored with the same color. If it is, we need to force adding one edge to it.
|
|
for (const std::vector<ColoredLine> &c_poly : color_poly) {
|
|
bool force_edge = true;
|
|
for (const ColoredLine &c_line : c_poly)
|
|
if (c_line.color != c_poly.front().color) {
|
|
force_edge = false;
|
|
break;
|
|
}
|
|
force_edge_adding[&c_poly - &color_poly.front()] = force_edge;
|
|
}
|
|
|
|
boost::polygon::construct_voronoi(lines_colored.begin(), lines_colored.end(), &vd);
|
|
MMU_Graph graph;
|
|
graph.nodes.reserve(points.size() + vd.vertices().size());
|
|
for (const Point &point : points)
|
|
graph.nodes.push_back({Vec2d(double(point.x()), double(point.y()))});
|
|
|
|
graph.add_contours(color_poly);
|
|
init_polygon_indices(graph, color_poly, lines_colored);
|
|
|
|
assert(graph.nodes.size() == lines_colored.size());
|
|
BoundingBox bbox = get_extents(color_poly_tmp);
|
|
graph.append_voronoi_vertices(vd, color_poly_tmp, bbox);
|
|
|
|
auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram<double>::const_edge_iterator &edge_it) -> ColoredLine {
|
|
size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx;
|
|
size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size();
|
|
size_t contour_prev_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx,
|
|
(contour_line_local_idx > 0) ? contour_line_local_idx - 1 : contour_line_size - 1);
|
|
return lines_colored[contour_prev_idx];
|
|
};
|
|
|
|
auto get_next_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram<double>::const_edge_iterator &edge_it) -> ColoredLine {
|
|
size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx;
|
|
size_t contour_line_size = color_poly[lines_colored[edge_it->cell()->source_index()].poly_idx].size();
|
|
size_t contour_next_idx = graph.get_global_index(lines_colored[edge_it->cell()->source_index()].poly_idx,
|
|
(contour_line_local_idx + 1) % contour_line_size);
|
|
return lines_colored[contour_next_idx];
|
|
};
|
|
|
|
bbox.offset(scale_(10.));
|
|
const BoundingBoxf bbox_clip(bbox.min.cast<double>(), bbox.max.cast<double>());
|
|
const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y()));
|
|
|
|
// Make a copy of the input segments with the double type.
|
|
std::vector<Voronoi::Internal::segment_type> segments;
|
|
for (const Line &line : lines)
|
|
segments.emplace_back(Voronoi::Internal::point_type(double(line.a(0)), double(line.a(1))),
|
|
Voronoi::Internal::point_type(double(line.b(0)), double(line.b(1))));
|
|
|
|
for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) {
|
|
// Skip second half-edge
|
|
if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color())
|
|
continue;
|
|
|
|
if (edge_it->is_infinite() && (edge_it->vertex0() != nullptr || edge_it->vertex1() != nullptr)) {
|
|
// Infinite edge is leading through a point on the counter, but there are no Voronoi vertices.
|
|
// So we could fix this case by computing the intersection between the contour line and infinity edge.
|
|
std::vector<Voronoi::Internal::point_type> samples;
|
|
Voronoi::Internal::clip_infinite_edge(points, segments, *edge_it, bbox_dim_max, &samples);
|
|
if (samples.empty())
|
|
continue;
|
|
|
|
const Line edge_line(mk_point(samples[0]), mk_point(samples[1]));
|
|
const ColoredLine &contour_line = lines_colored[edge_it->cell()->source_index()];
|
|
Point contour_intersection;
|
|
|
|
if (line_intersection_with_epsilon(contour_line.line, edge_line, &contour_intersection)) {
|
|
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_it->cell()->source_index());
|
|
const size_t from_idx = (edge_it->vertex1() != nullptr) ? edge_it->vertex1()->color() : edge_it->vertex0()->color();
|
|
size_t to_idx = ((contour_line.line.a - contour_intersection).cast<double>().squaredNorm() <
|
|
(contour_line.line.b - contour_intersection).cast<double>().squaredNorm()) ?
|
|
graph_arc.from_idx :
|
|
graph_arc.to_idx;
|
|
if (from_idx != to_idx && from_idx < graph.nodes_count() && to_idx < graph.nodes_count()) {
|
|
graph.append_edge(from_idx, to_idx);
|
|
mark_processed(edge_it);
|
|
}
|
|
}
|
|
} else if (edge_it->is_finite()) {
|
|
// Both points are on contour, so skip them. In cases of duplicate Voronoi vertices, skip edges between the same two points.
|
|
if (graph.is_edge_connecting_two_contour_vertices(edge_it) || (edge_it->vertex0()->color() == edge_it->vertex1()->color()))
|
|
continue;
|
|
|
|
const Line edge_line = clip_finite_voronoi_edge(*edge_it, bbox_clip);
|
|
const Line contour_line = lines_colored[edge_it->cell()->source_index()].line;
|
|
const ColoredLine colored_line = lines_colored[edge_it->cell()->source_index()];
|
|
const ColoredLine contour_line_prev = get_prev_contour_line(edge_it);
|
|
const ColoredLine contour_line_next = get_next_contour_line(edge_it);
|
|
|
|
if (edge_it->vertex0()->color() >= graph.nodes_count() || edge_it->vertex1()->color() >= graph.nodes_count()) {
|
|
enum class Vertex { VERTEX0, VERTEX1 };
|
|
auto append_edge_if_intersects_with_contour = [&graph, &lines_colored, &edge_line, &contour_line](const voronoi_diagram<double>::const_edge_iterator &edge_iterator, const Vertex vertex) {
|
|
Point intersection;
|
|
Line contour_line_twin = lines_colored[edge_iterator->twin()->cell()->source_index()].line;
|
|
if (line_intersection_with_epsilon(contour_line_twin, edge_line, &intersection)) {
|
|
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->twin()->cell()->source_index());
|
|
const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line_twin, intersection) ? graph_arc.from_idx :
|
|
graph_arc.to_idx;
|
|
graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l);
|
|
} else if (line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
|
|
const MMU_Graph::Arc &graph_arc = graph.get_border_arc(edge_iterator->cell()->source_index());
|
|
const size_t to_idx_l = is_point_closer_to_beginning_of_line(contour_line, intersection) ? graph_arc.from_idx : graph_arc.to_idx;
|
|
graph.append_edge(vertex == Vertex::VERTEX0 ? edge_iterator->vertex0()->color() : edge_iterator->vertex1()->color(), to_idx_l);
|
|
}
|
|
mark_processed(edge_iterator);
|
|
};
|
|
|
|
if (edge_it->vertex0()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex0()))
|
|
append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX0);
|
|
|
|
if (edge_it->vertex1()->color() < graph.nodes_count() && !graph.is_vertex_on_contour(edge_it->vertex1()))
|
|
append_edge_if_intersects_with_contour(edge_it, Vertex::VERTEX1);
|
|
} else if (graph.is_edge_attach_to_contour(edge_it)) {
|
|
mark_processed(edge_it);
|
|
// Skip edges witch connection two points on a contour
|
|
if (graph.is_edge_connecting_two_contour_vertices(edge_it))
|
|
continue;
|
|
|
|
const size_t from_idx = edge_it->vertex0()->color();
|
|
const size_t to_idx = edge_it->vertex1()->color();
|
|
if (graph.is_vertex_on_contour(edge_it->vertex0())) {
|
|
if (is_point_closer_to_beginning_of_line(contour_line, edge_line.a)) {
|
|
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.b)) {
|
|
graph.append_edge(from_idx, to_idx);
|
|
force_edge_adding[colored_line.poly_idx] = false;
|
|
}
|
|
} else {
|
|
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.b)) {
|
|
graph.append_edge(from_idx, to_idx);
|
|
force_edge_adding[colored_line.poly_idx] = false;
|
|
}
|
|
}
|
|
} else {
|
|
assert(graph.is_vertex_on_contour(edge_it->vertex1()));
|
|
if (is_point_closer_to_beginning_of_line(contour_line, edge_line.b)) {
|
|
if ((!has_same_color(contour_line_prev, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line_prev.line, contour_line, edge_line.a)) {
|
|
graph.append_edge(from_idx, to_idx);
|
|
force_edge_adding[colored_line.poly_idx] = false;
|
|
}
|
|
} else {
|
|
if ((!has_same_color(contour_line_next, colored_line) || force_edge_adding[colored_line.poly_idx]) && points_inside(contour_line, contour_line_next.line, edge_line.a)) {
|
|
graph.append_edge(from_idx, to_idx);
|
|
force_edge_adding[colored_line.poly_idx] = false;
|
|
}
|
|
}
|
|
}
|
|
} else if (Point intersection; line_intersection_with_epsilon(contour_line, edge_line, &intersection)) {
|
|
mark_processed(edge_it);
|
|
Vec2d real_v0_double = graph.nodes[edge_it->vertex0()->color()].point;
|
|
Vec2d real_v1_double = graph.nodes[edge_it->vertex1()->color()].point;
|
|
Point real_v0 = Point(coord_t(real_v0_double.x()), coord_t(real_v0_double.y()));
|
|
Point real_v1 = Point(coord_t(real_v1_double.x()), coord_t(real_v1_double.y()));
|
|
|
|
if (is_point_closer_to_beginning_of_line(contour_line, intersection)) {
|
|
Line first_part(intersection, real_v0);
|
|
Line second_part(intersection, real_v1);
|
|
|
|
if (!has_same_color(contour_line_prev, colored_line)) {
|
|
if (points_inside(contour_line_prev.line, contour_line, first_part.b))
|
|
graph.append_edge(edge_it->vertex0()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx);
|
|
|
|
if (points_inside(contour_line_prev.line, contour_line, second_part.b))
|
|
graph.append_edge(edge_it->vertex1()->color(), graph.get_border_arc(edge_it->cell()->source_index()).from_idx);
|
|
}
|
|
} else {
|
|
const size_t int_point_idx = graph.get_border_arc(edge_it->cell()->source_index()).to_idx;
|
|
const Vec2d int_point_double = graph.nodes[int_point_idx].point;
|
|
const Point int_point = Point(coord_t(int_point_double.x()), coord_t(int_point_double.y()));
|
|
|
|
const Line first_part(int_point, real_v0);
|
|
const Line second_part(int_point, real_v1);
|
|
|
|
if (!has_same_color(contour_line_next, colored_line)) {
|
|
if (points_inside(contour_line, contour_line_next.line, first_part.b))
|
|
graph.append_edge(edge_it->vertex0()->color(), int_point_idx);
|
|
|
|
if (points_inside(contour_line, contour_line_next.line, second_part.b))
|
|
graph.append_edge(edge_it->vertex1()->color(), int_point_idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto edge_it = vd.edges().begin(); edge_it != vd.edges().end(); ++edge_it) {
|
|
// Skip second half-edge and processed edges
|
|
if (edge_it->cell()->source_index() > edge_it->twin()->cell()->source_index() || edge_it->color())
|
|
continue;
|
|
|
|
if (edge_it->is_finite() && !bool(edge_it->color()) && edge_it->vertex0()->color() < graph.nodes_count() &&
|
|
edge_it->vertex1()->color() < graph.nodes_count()) {
|
|
// Skip cases, when the edge is between two same vertices, which is in cases two near vertices were merged together.
|
|
if (edge_it->vertex0()->color() == edge_it->vertex1()->color())
|
|
continue;
|
|
|
|
size_t from_idx = edge_it->vertex0()->color();
|
|
size_t to_idx = edge_it->vertex1()->color();
|
|
graph.append_edge(from_idx, to_idx);
|
|
}
|
|
mark_processed(edge_it);
|
|
}
|
|
|
|
graph.remove_nodes_with_one_arc();
|
|
return graph;
|
|
}
|
|
|
|
static inline Polygon to_polygon(const std::vector<std::pair<size_t, Linef>> &id_to_lines)
|
|
{
|
|
std::vector<Linef> lines;
|
|
for (auto id_to_line : id_to_lines)
|
|
lines.emplace_back(id_to_line.second);
|
|
|
|
Polygon poly_out;
|
|
poly_out.points.reserve(lines.size());
|
|
for (const Linef &line : lines)
|
|
poly_out.points.emplace_back(mk_point(line.a));
|
|
return poly_out;
|
|
}
|
|
|
|
|
|
static std::vector<std::vector<const MMU_Graph::Arc *>> get_all_next_arcs(
|
|
const MMU_Graph &graph,
|
|
std::vector<bool> &used_arcs,
|
|
const Linef &process_line,
|
|
const MMU_Graph::Arc &original_arc,
|
|
const int color)
|
|
{
|
|
std::vector<std::vector<const MMU_Graph::Arc *>> all_next_arcs;
|
|
for (const size_t &arc_idx : graph.nodes[original_arc.to_idx].arc_idxs) {
|
|
std::vector<const MMU_Graph::Arc *> next_continue_arc;
|
|
|
|
const MMU_Graph::Arc & arc = graph.arcs[arc_idx];
|
|
if (graph.nodes[arc.to_idx].point == process_line.a || used_arcs[arc_idx])
|
|
continue;
|
|
|
|
if (original_arc.type == MMU_Graph::ARC_TYPE::BORDER && original_arc.color != color)
|
|
continue;
|
|
|
|
if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color != color)
|
|
continue;
|
|
|
|
Vec2d arc_line = graph.nodes[arc.to_idx].point - graph.nodes[arc.from_idx].point;
|
|
next_continue_arc.emplace_back(&arc);
|
|
all_next_arcs.emplace_back(next_continue_arc);
|
|
}
|
|
return all_next_arcs;
|
|
}
|
|
|
|
// two points that are very close are considered as one point
|
|
// std::vector contain the close points
|
|
static std::vector<const MMU_Graph::Arc *> get_next_arc(
|
|
const MMU_Graph &graph,
|
|
std::vector<bool> &used_arcs,
|
|
const Linef &process_line,
|
|
const MMU_Graph::Arc &original_arc,
|
|
const int color)
|
|
{
|
|
std::vector<const MMU_Graph::Arc *> res;
|
|
|
|
std::vector<std::vector<const MMU_Graph::Arc *>> all_next_arcs = get_all_next_arcs(graph, used_arcs, process_line, original_arc, color);
|
|
if (all_next_arcs.empty()) {
|
|
res.emplace_back(&original_arc);
|
|
return res;
|
|
}
|
|
|
|
std::vector<std::pair<std::vector<const MMU_Graph::Arc *>, double>> sorted_arcs;
|
|
for (auto next_arc : all_next_arcs) {
|
|
if (next_arc.empty())
|
|
continue;
|
|
|
|
Vec2d process_line_vec_n = (process_line.a - process_line.b).normalized();
|
|
Vec2d neighbour_line_vec_n = (graph.nodes[next_arc.back()->to_idx].point - graph.nodes[next_arc.back()->from_idx].point).normalized();
|
|
|
|
double angle = ::acos(std::clamp(neighbour_line_vec_n.dot(process_line_vec_n), -1.0, 1.0));
|
|
if (Slic3r::cross2(neighbour_line_vec_n, process_line_vec_n) < 0.0)
|
|
angle = 2.0 * (double) PI - angle;
|
|
|
|
sorted_arcs.emplace_back(next_arc, angle);
|
|
}
|
|
|
|
std::sort(sorted_arcs.begin(), sorted_arcs.end(),
|
|
[](std::pair<std::vector<const MMU_Graph::Arc *>, double> &l, std::pair<std::vector<const MMU_Graph::Arc *>, double> &r) -> bool {
|
|
return l.second < r.second;
|
|
});
|
|
|
|
// Try to return left most edge witch is unused
|
|
for (auto &sorted_arc : sorted_arcs) {
|
|
if (size_t arc_idx = sorted_arc.first.back() - &graph.arcs.front(); !used_arcs[arc_idx])
|
|
return sorted_arc.first;
|
|
}
|
|
|
|
if (sorted_arcs.empty()) {
|
|
res.emplace_back(&original_arc);
|
|
return res;
|
|
}
|
|
|
|
return sorted_arcs.front().first;
|
|
}
|
|
|
|
static bool is_profile_self_interaction(Polygon poly)
|
|
{
|
|
auto lines = poly.lines();
|
|
Point intersection;
|
|
for (int i = 0; i < lines.size(); ++i) {
|
|
for (int j = i + 2; j < std::min(lines.size(), lines.size() + i - 1); ++j) {
|
|
if (lines[i].intersection(lines[j], &intersection))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns list of polygons and assigned colors.
|
|
// It iterates through all nodes on the border between two different colors, and from this point,
|
|
// start selection always left most edges for every node to construct CCW polygons.
|
|
// Assumes that graph is planar (without self-intersection edges)
|
|
static std::vector<ExPolygons> extract_colored_segments(const MMU_Graph &graph, const size_t num_extruders)
|
|
{
|
|
std::vector<bool> used_arcs(graph.arcs.size(), false);
|
|
|
|
auto all_arc_used = [&used_arcs](const MMU_Graph::Node &node) -> bool {
|
|
return std::all_of(node.arc_idxs.cbegin(), node.arc_idxs.cend(), [&used_arcs](const size_t &arc_idx) -> bool { return used_arcs[arc_idx]; });
|
|
};
|
|
|
|
std::vector<ExPolygons> expolygons_segments(num_extruders + 1);
|
|
for (size_t node_idx = 0; node_idx < graph.all_border_points; ++node_idx) {
|
|
const MMU_Graph::Node &node = graph.nodes[node_idx];
|
|
|
|
for (const size_t &arc_idx : node.arc_idxs) {
|
|
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
|
|
if (arc.type == MMU_Graph::ARC_TYPE::NON_BORDER || used_arcs[arc_idx])
|
|
continue;
|
|
|
|
Linef process_line(graph.nodes[arc.from_idx].point, graph.nodes[arc.to_idx].point);
|
|
used_arcs[arc_idx] = true;
|
|
|
|
std::vector<std::pair<size_t, Linef>> arc_id_to_face_lines;
|
|
arc_id_to_face_lines.emplace_back(std::make_pair(arc_idx, process_line));
|
|
Vec2d start_p = process_line.a;
|
|
|
|
Linef p_vec = process_line;
|
|
const MMU_Graph::Arc *p_arc = &arc;
|
|
bool flag = false;
|
|
do {
|
|
std::vector<const MMU_Graph::Arc *> nexts = get_next_arc(graph, used_arcs, p_vec, *p_arc, arc.color);
|
|
for (auto next : nexts) {
|
|
size_t next_arc_idx = next - &graph.arcs.front();
|
|
if (used_arcs[next_arc_idx]) {
|
|
flag = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flag)
|
|
break;
|
|
|
|
for (auto next : nexts) {
|
|
size_t next_arc_idx = next - &graph.arcs.front();
|
|
arc_id_to_face_lines.emplace_back(std::make_pair(next_arc_idx, Linef(graph.nodes[next->from_idx].point, graph.nodes[next->to_idx].point)));
|
|
used_arcs[next_arc_idx] = true;
|
|
}
|
|
|
|
p_vec = Linef(graph.nodes[nexts.back()->from_idx].point, graph.nodes[nexts.back()->to_idx].point);
|
|
p_arc = nexts.back();
|
|
|
|
} while (graph.nodes[p_arc->to_idx].point != start_p || !all_arc_used(graph.nodes[p_arc->to_idx]));
|
|
|
|
if (Polygon poly = to_polygon(arc_id_to_face_lines); poly.is_counter_clockwise() && poly.is_valid()) {
|
|
expolygons_segments[arc.color].emplace_back(std::move(poly));
|
|
} else{
|
|
while (arc_id_to_face_lines.size() > 1)
|
|
{
|
|
auto id_to_line = arc_id_to_face_lines.back();
|
|
used_arcs[id_to_line.first] = false;
|
|
arc_id_to_face_lines.pop_back();
|
|
Linef add_line(arc_id_to_face_lines.back().second.b, arc_id_to_face_lines.front().second.a);
|
|
arc_id_to_face_lines.emplace_back(std::make_pair(-1, add_line));
|
|
Polygon poly = to_polygon(arc_id_to_face_lines);
|
|
if (!is_profile_self_interaction(poly) && poly.is_counter_clockwise() && poly.is_valid()) {
|
|
expolygons_segments[arc.color].emplace_back(std::move(poly));
|
|
break;
|
|
}
|
|
arc_id_to_face_lines.pop_back();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return expolygons_segments;
|
|
}
|
|
|
|
// Used in remove_multiple_edges_in_vertices()
|
|
// Returns length of edge with is connected to contour. To this length is include other edges with follows it if they are almost straight (with the
|
|
// tolerance of 15) And also if node between two subsequent edges is connected only to these two edges.
|
|
static inline double compute_edge_length(const MMU_Graph &graph, const size_t start_idx, const size_t &start_arc_idx)
|
|
{
|
|
assert(start_arc_idx < graph.arcs.size());
|
|
std::vector<bool> used_arcs(graph.arcs.size(), false);
|
|
|
|
used_arcs[start_arc_idx] = true;
|
|
const MMU_Graph::Arc *arc = &graph.arcs[start_arc_idx];
|
|
size_t idx = start_idx;
|
|
double line_total_length = (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
|
|
while (graph.nodes[arc->to_idx].arc_idxs.size() == 2) {
|
|
bool found = false;
|
|
for (const size_t &arc_idx : graph.nodes[arc->to_idx].arc_idxs) {
|
|
if (const MMU_Graph::Arc &arc_n = graph.arcs[arc_idx]; arc_n.type == MMU_Graph::ARC_TYPE::NON_BORDER && !used_arcs[arc_idx] && arc_n.to_idx != idx) {
|
|
Linef first_line(graph.nodes[idx].point, graph.nodes[arc->to_idx].point);
|
|
Linef second_line(graph.nodes[arc->to_idx].point, graph.nodes[arc_n.to_idx].point);
|
|
|
|
Vec2d first_line_vec = (first_line.a - first_line.b);
|
|
Vec2d second_line_vec = (second_line.b - second_line.a);
|
|
Vec2d first_line_vec_n = first_line_vec.normalized();
|
|
Vec2d second_line_vec_n = second_line_vec.normalized();
|
|
double angle = ::acos(std::clamp(first_line_vec_n.dot(second_line_vec_n), -1.0, 1.0));
|
|
if (Slic3r::cross2(first_line_vec_n, second_line_vec_n) < 0.0)
|
|
angle = 2.0 * (double) PI - angle;
|
|
|
|
if (std::abs(angle - PI) >= (PI / 12))
|
|
continue;
|
|
|
|
idx = arc->to_idx;
|
|
arc = &arc_n;
|
|
|
|
line_total_length += (graph.nodes[arc->to_idx].point - graph.nodes[idx].point).norm();
|
|
used_arcs[arc_idx] = true;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
break;
|
|
}
|
|
|
|
return line_total_length;
|
|
}
|
|
|
|
// Used for fixing double Voronoi edges for concave parts of the polygon.
|
|
static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vector<std::vector<ColoredLine>> &color_poly)
|
|
{
|
|
std::vector<std::vector<std::pair<size_t, size_t>>> colored_segments = get_all_segments(color_poly);
|
|
for (const std::vector<std::pair<size_t, size_t>> &colored_segment_p : colored_segments) {
|
|
size_t poly_idx = &colored_segment_p - &colored_segments.front();
|
|
for (const std::pair<size_t, size_t> &colored_segment : colored_segment_p) {
|
|
size_t first_idx = graph.get_global_index(poly_idx, colored_segment.first);
|
|
size_t second_idx = graph.get_global_index(poly_idx, (colored_segment.second + 1) % graph.polygon_sizes[poly_idx]);
|
|
Linef seg_line(graph.nodes[first_idx].point, graph.nodes[second_idx].point);
|
|
|
|
if (graph.nodes[first_idx].arc_idxs.size() >= 3) {
|
|
std::vector<std::pair<MMU_Graph::Arc *, double>> arc_to_check;
|
|
for (const size_t &arc_idx : graph.nodes[first_idx].arc_idxs) {
|
|
MMU_Graph::Arc &n_arc = graph.arcs[arc_idx];
|
|
if (n_arc.type == MMU_Graph::ARC_TYPE::NON_BORDER) {
|
|
double total_len = compute_edge_length(graph, first_idx, arc_idx);
|
|
arc_to_check.emplace_back(&n_arc, total_len);
|
|
}
|
|
}
|
|
std::sort(arc_to_check.begin(), arc_to_check.end(),
|
|
[](std::pair<MMU_Graph::Arc *, double> &l, std::pair<MMU_Graph::Arc *, double> &r) -> bool { return l.second > r.second; });
|
|
|
|
while (arc_to_check.size() > 1) {
|
|
graph.remove_edge(first_idx, arc_to_check.back().first->to_idx);
|
|
arc_to_check.pop_back();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cut_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
|
|
std::vector<std::vector<ExPolygons>> &segmented_regions,
|
|
const float cut_width,
|
|
const float interlocking_depth,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - begin";
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, segmented_regions.size()),
|
|
[&segmented_regions, &input_expolygons, &cut_width, &interlocking_depth, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
|
throw_on_cancel_callback();
|
|
const float region_cut_width = ((layer_idx % 2 == 0) && (interlocking_depth != 0.f)) ? interlocking_depth : cut_width;
|
|
const size_t num_extruders_plus_one = segmented_regions[layer_idx].size();
|
|
if (region_cut_width > 0.f) {
|
|
std::vector<ExPolygons> segmented_regions_cuts(num_extruders_plus_one); // Indexed by extruder_id
|
|
for (size_t extruder_idx = 0; extruder_idx < num_extruders_plus_one; ++extruder_idx)
|
|
if (const ExPolygons &ex_polygons = segmented_regions[layer_idx][extruder_idx]; !ex_polygons.empty())
|
|
segmented_regions_cuts[extruder_idx] = diff_ex(ex_polygons, offset_ex(input_expolygons[layer_idx], -region_cut_width));
|
|
segmented_regions[layer_idx] = std::move(segmented_regions_cuts);
|
|
}
|
|
}
|
|
}); // end of parallel_for
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
|
|
}
|
|
|
|
static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d &trafo)
|
|
{
|
|
const Transform3f trafo_f = trafo.cast<float>();
|
|
for (const stl_vertex &vertex : its.vertices)
|
|
if ((trafo_f * vertex).z() < SINKING_Z_THRESHOLD) return true;
|
|
return false;
|
|
}
|
|
|
|
//#define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
|
|
// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo
|
|
static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
|
const std::vector<ExPolygons> &input_expolygons,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
// BBS
|
|
const size_t num_extruders = print_object.print()->config().filament_colour.size() + 1;
|
|
const size_t num_layers = input_expolygons.size();
|
|
const ConstLayerPtrsAdaptor layers = print_object.layers();
|
|
|
|
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
|
|
int max_top_layers = 0;
|
|
int max_bottom_layers = 0;
|
|
int granularity = 1;
|
|
for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) {
|
|
const PrintRegionConfig &config = print_object.printing_region(i).config();
|
|
max_top_layers = std::max(max_top_layers, config.top_shell_layers.value);
|
|
max_bottom_layers = std::max(max_bottom_layers, config.bottom_shell_layers.value);
|
|
granularity = std::max(granularity, std::max(config.top_shell_layers.value, config.bottom_shell_layers.value) - 1);
|
|
}
|
|
|
|
// Project upwards pointing painted triangles over top surfaces,
|
|
// project downards pointing painted triangles over bottom surfaces.
|
|
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
|
|
std::vector<float> zs = zs_from_layers(layers);
|
|
Transform3d object_trafo = print_object.trafo_centered();
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
static int iRun = 0;
|
|
#endif // NDEBUG
|
|
|
|
if (max_top_layers > 0 || max_bottom_layers > 0) {
|
|
for (const ModelVolume *mv : print_object.model_object()->volumes)
|
|
if (mv->is_model_part()) {
|
|
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
|
|
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
|
|
const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx));
|
|
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
{
|
|
static int iRun = 0;
|
|
its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str());
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
if (! painted.indices.empty()) {
|
|
std::vector<Polygons> top, bottom;
|
|
if (!zs.empty() && is_volume_sinking(painted, volume_trafo)) {
|
|
std::vector<float> zs_sinking = {0.f};
|
|
Slic3r::append(zs_sinking, zs);
|
|
slice_mesh_slabs(painted, zs_sinking, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback);
|
|
|
|
MeshSlicingParams slicing_params;
|
|
slicing_params.trafo = volume_trafo;
|
|
Polygons bottom_slice = slice_mesh(painted, zs[0], slicing_params);
|
|
|
|
top.erase(top.begin());
|
|
bottom.erase(bottom.begin());
|
|
|
|
bottom[0] = union_(bottom[0], bottom_slice);
|
|
} else
|
|
slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback);
|
|
auto merge = [](std::vector<Polygons> &&src, std::vector<Polygons> &dst) {
|
|
auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); });
|
|
if (it_src != src.end()) {
|
|
if (dst.empty()) {
|
|
dst = std::move(src);
|
|
} else {
|
|
assert(src.size() == dst.size());
|
|
auto it_dst = dst.begin() + (it_src - src.begin());
|
|
for (; it_src != src.end(); ++ it_src, ++ it_dst)
|
|
if (! it_src->empty()) {
|
|
if (it_dst->empty())
|
|
*it_dst = std::move(*it_src);
|
|
else
|
|
append(*it_dst, std::move(*it_src));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
merge(std::move(top), top_raw[extruder_idx]);
|
|
merge(std::move(bottom), bottom_raw[extruder_idx]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto filter_out_small_polygons = [&num_extruders, &num_layers](std::vector<std::vector<Polygons>> &raw_surfaces, double min_area) -> void {
|
|
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx)
|
|
if (!raw_surfaces[extruder_idx].empty())
|
|
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx)
|
|
if (!raw_surfaces[extruder_idx][layer_idx].empty())
|
|
remove_small(raw_surfaces[extruder_idx][layer_idx], min_area);
|
|
};
|
|
|
|
// Filter out polygons less than 0.1mm^2, because they are unprintable and causing dimples on outer primers (#7104)
|
|
filter_out_small_polygons(top_raw, Slic3r::sqr(scale_(0.1f)));
|
|
filter_out_small_polygons(bottom_raw, Slic3r::sqr(scale_(0.1f)));
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
{
|
|
const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" };
|
|
static int iRun = 0;
|
|
for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) {
|
|
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> svg;
|
|
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
|
|
if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty())
|
|
if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) {
|
|
const char *color = colors[extruder_idx];
|
|
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color });
|
|
}
|
|
if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty())
|
|
if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) {
|
|
const char *color = colors[extruder_idx + 8];
|
|
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color });
|
|
}
|
|
}
|
|
SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg);
|
|
}
|
|
++ iRun;
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
|
|
|
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
|
|
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
|
|
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
|
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
|
|
|
// BBS: use shell_triangles_by_color_bottom & shell_triangles_by_color_top to save the top and bottom embedded layers's color information
|
|
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_bottom(num_extruders);
|
|
std::vector<std::vector<ExPolygons>> shell_triangles_by_color_top(num_extruders);
|
|
shell_triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
|
shell_triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
|
|
|
struct LayerColorStat {
|
|
// Number of regions for a queried color.
|
|
int num_regions { 0 };
|
|
// Maximum perimeter extrusion width for a queried color.
|
|
float extrusion_width { 0.f };
|
|
// Minimum radius of a region to be printable. Used to filter regions by morphological opening.
|
|
float small_region_threshold { 0.f };
|
|
// Maximum number of top layers for a queried color.
|
|
int top_shell_layers { 0 };
|
|
// Maximum number of bottom layers for a queried color.
|
|
int bottom_shell_layers { 0 };
|
|
//BBS: spacing according to width and layer height
|
|
float extrusion_spacing{ 0.f };
|
|
};
|
|
auto layer_color_stat = [&layers = std::as_const(layers), &print_object](const size_t layer_idx, const size_t color_idx) -> LayerColorStat {
|
|
LayerColorStat out;
|
|
const Layer &layer = *layers[layer_idx];
|
|
for (const LayerRegion *region : layer.regions())
|
|
if (const PrintRegionConfig &config = region->region().config();
|
|
// color_idx == 0 means "don't know" extruder aka the underlying extruder.
|
|
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
|
color_idx == 0 || config.wall_filament == int(color_idx)) {
|
|
//BBS: the extrusion line width is outer wall rather than inner wall
|
|
const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0);
|
|
double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
|
out.extrusion_width = std::max<float>(out.extrusion_width, outer_wall_line_width);
|
|
out.top_shell_layers = std::max<int>(out.top_shell_layers, config.top_shell_layers);
|
|
out.bottom_shell_layers = std::max<int>(out.bottom_shell_layers, config.bottom_shell_layers);
|
|
out.small_region_threshold = config.gap_infill_speed.value > 0 ?
|
|
// Gap fill enabled. Enable a single line of 1/2 extrusion width.
|
|
0.5f * outer_wall_line_width :
|
|
// Gap fill disabled. Enable two lines slightly overlapping.
|
|
outer_wall_line_width + 0.7f * Flow::rounded_rectangle_extrusion_spacing(outer_wall_line_width, float(layer.height));
|
|
out.small_region_threshold = scaled<float>(out.small_region_threshold * 0.5f);
|
|
out.extrusion_spacing = Flow::rounded_rectangle_extrusion_spacing(float(outer_wall_line_width), float(layer.height));
|
|
++ out.num_regions;
|
|
}
|
|
assert(out.num_regions > 0);
|
|
out.extrusion_width = scaled<float>(out.extrusion_width);
|
|
out.extrusion_spacing = scaled<float>(out.extrusion_spacing);
|
|
return out;
|
|
};
|
|
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&granularity, &num_layers, &num_extruders, &layer_color_stat, &top_raw, &triangles_by_color_top,
|
|
&throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
|
|
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
|
size_t group_idx = range.begin() / granularity;
|
|
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
|
for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
|
|
throw_on_cancel_callback();
|
|
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
|
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
|
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
|
|
// Clean up thin projections. They are not printable anyways.
|
|
top_ex = opening_ex(top_ex, stat.small_region_threshold);
|
|
if (! top_ex.empty()) {
|
|
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
|
float offset = 0.f;
|
|
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
|
for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
|
|
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
|
//offset -= stat.extrusion_width ;
|
|
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
|
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
|
ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
|
if (last.empty())
|
|
break;
|
|
append(shell_triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
|
}
|
|
}
|
|
}
|
|
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
|
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
|
|
// Clean up thin projections. They are not printable anyways.
|
|
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
|
|
if (! bottom_ex.empty()) {
|
|
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
|
|
float offset = 0.f;
|
|
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
|
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
|
|
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
|
|
//offset -= stat.extrusion_width;
|
|
offset -= (stat.extrusion_spacing + stat.extrusion_width);
|
|
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
|
ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
|
|
if (last.empty())
|
|
break;
|
|
append(shell_triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
|
|
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&triangles_by_color_merged, &triangles_by_color_bottom, &triangles_by_color_top, &num_layers, &throw_on_cancel_callback,
|
|
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
|
throw_on_cancel_callback();
|
|
ExPolygons painted_exploys;
|
|
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
|
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
|
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
|
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
|
|
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
|
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
|
|
self = union_ex(self);
|
|
|
|
append(painted_exploys, self);
|
|
}
|
|
|
|
painted_exploys = union_ex(painted_exploys);
|
|
|
|
//BBS: merge the top and bottom shell layers
|
|
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
|
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
|
|
|
auto top_area = diff_ex(union_ex(shell_triangles_by_color_top[color_idx][layer_idx],
|
|
shell_triangles_by_color_top[color_idx][layer_idx + num_layers]),
|
|
painted_exploys);
|
|
|
|
auto bottom_area = diff_ex(union_ex(shell_triangles_by_color_bottom[color_idx][layer_idx],
|
|
shell_triangles_by_color_bottom[color_idx][layer_idx + num_layers]),
|
|
painted_exploys);
|
|
|
|
append(self, top_area);
|
|
append(self, bottom_area);
|
|
self = union_ex(self);
|
|
}
|
|
// Trim one region by the other if some of the regions overlap.
|
|
ExPolygons painted_regions;
|
|
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
|
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx], painted_regions);
|
|
append(painted_regions, triangles_by_color_merged[color_idx][layer_idx]);
|
|
}
|
|
triangles_by_color_merged[0][layer_idx] = diff_ex(triangles_by_color_merged[0][layer_idx], painted_regions);
|
|
}
|
|
});
|
|
|
|
return triangles_by_color_merged;
|
|
}
|
|
|
|
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(
|
|
const std::vector<std::vector<ExPolygons>> &segmented_regions,
|
|
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
|
|
const size_t num_extruders,
|
|
const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
const size_t num_layers = segmented_regions.size();
|
|
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
|
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_extruders));
|
|
assert(num_extruders + 1 == top_and_bottom_layers.size());
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - begin";
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
|
assert(segmented_regions[layer_idx].size() == num_extruders + 1);
|
|
// Zero is skipped because it is the default color of the volume
|
|
for (size_t extruder_id = 1; extruder_id < num_extruders + 1; ++extruder_id) {
|
|
throw_on_cancel_callback();
|
|
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
|
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
|
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers)
|
|
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty())
|
|
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
|
|
|
|
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
|
|
}
|
|
|
|
if (!top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
|
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
|
|
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
|
|
|
|
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
|
if (!was_top_and_bottom_empty)
|
|
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
|
}
|
|
}
|
|
}
|
|
}); // end of parallel_for
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - merging segmented layers in parallel - end";
|
|
|
|
return segmented_regions_merged;
|
|
}
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
|
|
static void export_regions_to_svg(const std::string &path, const std::vector<ExPolygons> ®ions, const ExPolygons &lslices)
|
|
{
|
|
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
|
|
coordf_t stroke_width = scale_(0.05);
|
|
BoundingBox bbox = get_extents(lslices);
|
|
bbox.offset(scale_(1.));
|
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
|
|
|
svg.draw_outline(lslices, "green", "lime", stroke_width);
|
|
for (const ExPolygons &by_extruder : regions) {
|
|
size_t extrude_idx = &by_extruder - ®ions.front();
|
|
if (extrude_idx >= 0 && extrude_idx < int(colors.size()))
|
|
svg.draw(by_extruder, colors[extrude_idx], stroke_width);
|
|
else
|
|
svg.draw(by_extruder, "black", stroke_width);
|
|
}
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_REGIONS
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_GRAPH
|
|
static void export_graph_to_svg(const std::string &path, const MMU_Graph &graph, const ExPolygons &lslices)
|
|
{
|
|
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
|
coordf_t stroke_width = scale_(0.05);
|
|
BoundingBox bbox = get_extents(lslices);
|
|
bbox.offset(scale_(1.));
|
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
|
for (const MMU_Graph::Node &node : graph.nodes)
|
|
for (const size_t &arc_idx : node.arc_idxs) {
|
|
const MMU_Graph::Arc &arc = graph.arcs[arc_idx];
|
|
Line arc_line(mk_point(node.point), mk_point(graph.nodes[arc.to_idx].point));
|
|
if (arc.type == MMU_Graph::ARC_TYPE::BORDER && arc.color >= 0 && arc.color < int(colors.size()))
|
|
svg.draw(arc_line, colors[arc.color], stroke_width);
|
|
else
|
|
svg.draw(arc_line, "black", stroke_width);
|
|
}
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_GRAPH
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_INPUT
|
|
void export_processed_input_expolygons_to_svg(const std::string &path, const LayerRegionPtrs ®ions, const ExPolygons &processed_input_expolygons)
|
|
{
|
|
coordf_t stroke_width = scale_(0.05);
|
|
BoundingBox bbox = get_extents(regions);
|
|
bbox.merge(get_extents(processed_input_expolygons));
|
|
bbox.offset(scale_(1.));
|
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
|
|
|
for (LayerRegion *region : regions)
|
|
svg.draw_outline(region->slices.surfaces, "blue", "cyan", stroke_width);
|
|
|
|
svg.draw_outline(processed_input_expolygons, "red", "pink", stroke_width);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_INPUT
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
static void export_painted_lines_to_svg(const std::string &path, const std::vector<std::vector<PaintedLine>> &all_painted_lines, const ExPolygons &lslices)
|
|
{
|
|
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "yellow"};
|
|
coordf_t stroke_width = scale_(0.05);
|
|
BoundingBox bbox = get_extents(lslices);
|
|
bbox.offset(scale_(1.));
|
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
|
|
|
for (const Line &line : to_lines(lslices))
|
|
svg.draw(line, "green", stroke_width);
|
|
|
|
for (const std::vector<PaintedLine> &painted_lines : all_painted_lines)
|
|
for (const PaintedLine &painted_line : painted_lines)
|
|
svg.draw(painted_line.projected_line, painted_line.color < int(colors.size()) ? colors[painted_line.color] : "black", stroke_width);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
|
static void export_colorized_polygons_to_svg(const std::string &path, const std::vector<std::vector<ColoredLine>> &colorized_polygons, const ExPolygons &lslices)
|
|
{
|
|
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
|
coordf_t stroke_width = scale_(0.05);
|
|
BoundingBox bbox = get_extents(lslices);
|
|
bbox.offset(scale_(1.));
|
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
|
|
|
for (const std::vector<ColoredLine> &colorized_polygon : colorized_polygons)
|
|
for (const ColoredLine &colorized_line : colorized_polygon)
|
|
svg.draw(colorized_line.line, colorized_line.color < int(colors.size())? colors[colorized_line.color] : "black", stroke_width);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
|
|
|
// Check if all ColoredLine representing a single layer uses the same color.
|
|
static bool has_layer_only_one_color(const std::vector<std::vector<ColoredLine>> &colored_polygons)
|
|
{
|
|
assert(!colored_polygons.empty());
|
|
assert(!colored_polygons.front().empty());
|
|
int first_line_color = colored_polygons.front().front().color;
|
|
for (const std::vector<ColoredLine> &colored_polygon : colored_polygons)
|
|
for (const ColoredLine &colored_line : colored_polygon)
|
|
if (first_line_color != colored_line.color)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback)
|
|
{
|
|
const size_t num_extruders = print_object.print()->config().filament_colour.size();
|
|
const size_t num_layers = print_object.layers().size();
|
|
std::vector<std::vector<ExPolygons>> segmented_regions(num_layers);
|
|
segmented_regions.assign(num_layers, std::vector<ExPolygons>(num_extruders + 1));
|
|
std::vector<std::vector<PaintedLine>> painted_lines(num_layers);
|
|
std::array<std::mutex, 64> painted_lines_mutex;
|
|
std::vector<EdgeGrid::Grid> edge_grids(num_layers);
|
|
const ConstLayerPtrsAdaptor layers = print_object.layers();
|
|
std::vector<ExPolygons> input_expolygons(num_layers);
|
|
|
|
throw_on_cancel_callback();
|
|
|
|
// Merge all regions and remove small holes
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - begin";
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
|
throw_on_cancel_callback();
|
|
ExPolygons ex_polygons;
|
|
for (LayerRegion *region : layers[layer_idx]->regions())
|
|
for (const Surface &surface : region->slices.surfaces)
|
|
Slic3r::append(ex_polygons, offset_ex(surface.expolygon, float(10 * SCALED_EPSILON)));
|
|
// All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON
|
|
// to ensure that very close polygons will be merged.
|
|
ex_polygons = union_ex(ex_polygons);
|
|
// Remove all expolygons and holes with an area less than 0.1mm^2
|
|
remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f)));
|
|
// Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams
|
|
// and consequently with the extraction of colored segments by function extract_colored_segments.
|
|
// Calling simplify_polygons removes these self-intersections.
|
|
// Also, occasionally input polygons contained several points very close together (distance between points is 1 or so).
|
|
// Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices.
|
|
// This consequently leads to issues with the extraction of colored segments by function extract_colored_segments.
|
|
// Calling expolygons_simplify fixed these issues.
|
|
input_expolygons[layer_idx] = remove_duplicates(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), scaled<coord_t>(0.01), PI / 6);
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_INPUT
|
|
{
|
|
static int iRun = 0;
|
|
export_processed_input_expolygons_to_svg(debug_out_path("mm-input-%d-%d.svg", layer_idx, iRun++), layers[layer_idx]->regions(), input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_INPUT
|
|
}
|
|
}); // end of parallel_for
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end";
|
|
|
|
std::vector<BoundingBox> layer_bboxes(num_layers);
|
|
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
|
throw_on_cancel_callback();
|
|
layer_bboxes[layer_idx] = get_extents(layers[layer_idx]->regions());
|
|
layer_bboxes[layer_idx].merge(get_extents(input_expolygons[layer_idx]));
|
|
}
|
|
|
|
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
|
throw_on_cancel_callback();
|
|
BoundingBox bbox = layer_bboxes[layer_idx];
|
|
// Projected triangles could, in rare cases (as in GH issue #7299), belongs to polygons printed in the previous or the next layer.
|
|
// Let's merge the bounding box of the current layer with bounding boxes of the previous and the next layer to ensure that
|
|
// every projected triangle will be inside the resulting bounding box.
|
|
if (layer_idx > 1) bbox.merge(layer_bboxes[layer_idx - 1]);
|
|
if (layer_idx < num_layers - 1) bbox.merge(layer_bboxes[layer_idx + 1]);
|
|
// Projected triangles may slightly exceed the input polygons.
|
|
bbox.offset(20 * SCALED_EPSILON);
|
|
edge_grids[layer_idx].set_bbox(bbox);
|
|
edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.)));
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin";
|
|
for (const ModelVolume *mv : print_object.model_object()->volumes) {
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) {
|
|
throw_on_cancel_callback();
|
|
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
|
|
if (!mv->is_model_part() || custom_facets.indices.empty())
|
|
continue;
|
|
|
|
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, custom_facets.indices.size()), [&tr, &custom_facets, &print_object, &layers, &edge_grids, &input_expolygons, &painted_lines, &painted_lines_mutex, &extruder_idx](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t facet_idx = range.begin(); facet_idx < range.end(); ++facet_idx) {
|
|
float min_z = std::numeric_limits<float>::max();
|
|
float max_z = std::numeric_limits<float>::lowest();
|
|
|
|
std::array<Vec3f, 3> facet;
|
|
for (int p_idx = 0; p_idx < 3; ++p_idx) {
|
|
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
|
|
max_z = std::max(max_z, facet[p_idx].z());
|
|
min_z = std::min(min_z, facet[p_idx].z());
|
|
}
|
|
|
|
// Sort the vertices by z-axis for simplification of projected_facet on slices
|
|
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
|
|
|
|
// Find lowest slice not below the triangle.
|
|
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
|
|
[](float z, const Layer *l1) { return z < l1->slice_z; });
|
|
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z + EPSILON),
|
|
[](float z, const Layer *l1) { return z < l1->slice_z; });
|
|
--last_layer;
|
|
|
|
for (auto layer_it = first_layer; layer_it != (last_layer + 1); ++layer_it) {
|
|
const Layer *layer = *layer_it;
|
|
size_t layer_idx = layer_it - layers.begin();
|
|
if (input_expolygons[layer_idx].empty() || is_less(layer->slice_z, facet[0].z()) || is_less(facet[2].z(), layer->slice_z))
|
|
continue;
|
|
|
|
// https://kandepet.com/3d-printing-slicing-3d-objects/
|
|
float t = (float(layer->slice_z) - facet[0].z()) / (facet[2].z() - facet[0].z());
|
|
Vec3f line_start_f = facet[0] + t * (facet[2] - facet[0]);
|
|
Vec3f line_end_f;
|
|
|
|
// BBS: When one side of a triangle coincides with the slice_z.
|
|
if ((is_equal(facet[0].z(), facet[1].z()) && is_equal(facet[1].z(), layer->slice_z))
|
|
|| (is_equal(facet[1].z(), facet[2].z()) && is_equal(facet[1].z(), layer->slice_z))) {
|
|
line_end_f = facet[1];
|
|
}
|
|
else if (facet[1].z() > layer->slice_z) {
|
|
// [P0, P2] and [P0, P1]
|
|
float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z());
|
|
line_end_f = facet[0] + t1 * (facet[1] - facet[0]);
|
|
} else {
|
|
// [P0, P2] and [P1, P2]
|
|
float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z());
|
|
line_end_f = facet[1] + t2 * (facet[2] - facet[1]);
|
|
}
|
|
|
|
Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())),
|
|
Point(scale_(line_end_f.x()), scale_(line_end_f.y())));
|
|
line_to_test.translate(-print_object.center_offset());
|
|
|
|
// BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could
|
|
// be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618).
|
|
// To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases
|
|
// when any of the endpoints of the line are outside the EdgeGrid's BoundingBox.
|
|
if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) {
|
|
// If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line.
|
|
if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) ||
|
|
!line_to_test.clip_with_bbox(edge_grid_bbox))
|
|
continue;
|
|
}
|
|
|
|
size_t mutex_idx = layer_idx & 0x3F;
|
|
assert(mutex_idx < painted_lines_mutex.size());
|
|
|
|
PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16);
|
|
visitor.line_to_test = line_to_test;
|
|
visitor.color = int(extruder_idx);
|
|
edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor);
|
|
}
|
|
}
|
|
}); // end of parallel_for
|
|
}
|
|
}); // end of parallel_for
|
|
}
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - end";
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - painted layers count: "
|
|
<< std::count_if(painted_lines.begin(), painted_lines.end(), [](const std::vector<PaintedLine> &pl) { return !pl.empty(); });
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - begin";
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&edge_grids, &input_expolygons, &painted_lines, &segmented_regions, &num_extruders, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
|
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
|
throw_on_cancel_callback();
|
|
if (!painted_lines[layer_idx].empty()) {
|
|
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
{
|
|
static int iRun = 0;
|
|
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-%d-%d.svg", layer_idx, iRun++), {painted_lines[layer_idx]}, input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
|
|
std::vector<std::vector<PaintedLine>> post_processed_painted_lines = post_process_painted_lines(edge_grids[layer_idx].contours(), std::move(painted_lines[layer_idx]));
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
{
|
|
static int iRun = 0;
|
|
export_painted_lines_to_svg(debug_out_path("mm-painted-lines-post-processed-%d-%d.svg", layer_idx, iRun++), post_processed_painted_lines, input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_PAINTED_LINES
|
|
|
|
std::vector<std::vector<ColoredLine>> color_poly = colorize_contours(edge_grids[layer_idx].contours(), post_processed_painted_lines);
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
|
{
|
|
static int iRun = 0;
|
|
export_colorized_polygons_to_svg(debug_out_path("mm-colorized_polygons-%d-%d.svg", layer_idx, iRun++), color_poly, input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_COLORIZED_POLYGONS
|
|
|
|
assert(!color_poly.empty());
|
|
assert(!color_poly.front().empty());
|
|
if (has_layer_only_one_color(color_poly)) {
|
|
// If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
|
|
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
|
|
} else {
|
|
MMU_Graph graph = build_graph(layer_idx, color_poly);
|
|
remove_multiple_edges_in_vertices(graph, color_poly);
|
|
graph.remove_nodes_with_one_arc();
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_GRAPH
|
|
{
|
|
static int iRun = 0;
|
|
export_graph_to_svg(debug_out_path("mm-graph-final-%d-%d.svg", layer_idx, iRun++), graph, input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_GRAPH
|
|
|
|
segmented_regions[layer_idx] = extract_colored_segments(graph, num_extruders);
|
|
}
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
|
|
{
|
|
static int iRun = 0;
|
|
export_regions_to_svg(debug_out_path("mm-regions-sides-%d-%d.svg", layer_idx, iRun++), segmented_regions[layer_idx], input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_REGIONS
|
|
}
|
|
}
|
|
}); // end of parallel_for
|
|
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - layers segmentation in parallel - end";
|
|
throw_on_cancel_callback();
|
|
|
|
if (auto max_width = print_object.config().mmu_segmented_region_max_width, interlocking_depth = print_object.config().mmu_segmented_region_interlocking_depth; max_width > 0.f || interlocking_depth > 0.f) {
|
|
cut_segmented_layers(input_expolygons, segmented_regions, float(scale_(max_width)), float(scale_(interlocking_depth)), throw_on_cancel_callback);
|
|
throw_on_cancel_callback();
|
|
}
|
|
|
|
// The first index is extruder number (includes default extruder), and the second one is layer number
|
|
std::vector<std::vector<ExPolygons>> top_and_bottom_layers = mmu_segmentation_top_and_bottom_layers(print_object, input_expolygons, throw_on_cancel_callback);
|
|
throw_on_cancel_callback();
|
|
|
|
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_extruders, throw_on_cancel_callback);
|
|
throw_on_cancel_callback();
|
|
|
|
#ifdef MMU_SEGMENTATION_DEBUG_REGIONS
|
|
{
|
|
static int iRun = 0;
|
|
for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx)
|
|
export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun++), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]);
|
|
}
|
|
#endif // MMU_SEGMENTATION_DEBUG_REGIONS
|
|
|
|
return segmented_regions_merged;
|
|
}
|
|
|
|
} // namespace Slic3r
|