Files
OrcaSlicer-KX/src/libslic3r/MultiMaterialSegmentation.cpp
SoftFever cea319805f Feature/bs1.8beta (#2844)
* 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>
2023-12-01 10:42:45 +00:00

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> &regions, 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 - &regions.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 &regions, 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