Compare commits
2791 Commits
321b70115b
...
4dae6cbf60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dae6cbf60 | ||
|
|
e2126c2dd6 | ||
|
|
e622401599 | ||
|
|
7cf3922707 | ||
|
|
9ee10a8779 | ||
|
|
1ba81655e2 | ||
|
|
a3a8333d20 | ||
|
|
6669357c81 | ||
|
|
80e4155cbc | ||
|
|
bc5bd1b42b | ||
|
|
b386f52acc | ||
|
|
f8bbfad152 | ||
|
|
cea5b97cad | ||
|
|
4803e7fc84 | ||
|
|
8a47852be2 | ||
|
|
86b02224ae | ||
|
|
725b8524f2 | ||
|
|
635bb1e484 | ||
|
|
15f943938b | ||
|
|
7863412687 | ||
|
|
5414f7379d | ||
|
|
2a07f3a0d5 | ||
|
|
1602ddd56c | ||
|
|
478488972c | ||
|
|
27faaa27f6 | ||
|
|
7499a4dea4 | ||
|
|
6fef5a07ab | ||
|
|
5a56f08aad | ||
|
|
734273a33a | ||
|
|
a3949b9f01 | ||
|
|
56ea84fef8 | ||
|
|
5bf5213d67 | ||
|
|
53f8706805 | ||
|
|
6874949556 | ||
|
|
859e13ece5 | ||
|
|
52a7d7ed09 | ||
|
|
c624d6bb0a | ||
|
|
4d405977dd | ||
|
|
7fb6e2aa03 | ||
|
|
bc5640eef4 | ||
|
|
3a19b81cef | ||
|
|
099d59ad27 | ||
|
|
29dd305aaa | ||
|
|
c657654c02 | ||
|
|
6079fed951 | ||
|
|
b2cf576bf3 | ||
|
|
2d97d8e7fe | ||
|
|
2cd0c64b96 | ||
|
|
0ec06e6981 | ||
|
|
a4b42f44f3 | ||
|
|
0faaef76e8 | ||
|
|
f8664fce71 | ||
|
|
303ed0e6c9 | ||
|
|
6cf508e124 | ||
|
|
756064810c | ||
|
|
b134fb8c39 | ||
|
|
07815891dc | ||
|
|
af3d07bb05 | ||
|
|
5f02669d2d | ||
|
|
a194d28b14 | ||
|
|
7426291e9f | ||
|
|
43e8fe32aa | ||
|
|
69705b5e66 | ||
|
|
a54e587751 | ||
|
|
7b6a0ba9ad | ||
|
|
49cd06407a | ||
|
|
fba1bf7afc | ||
|
|
44220530cb | ||
|
|
a02ea39525 | ||
|
|
085110c4d9 | ||
|
|
efe6a29032 | ||
|
|
dc9a46b269 | ||
|
|
7b4870d1cb | ||
|
|
6cafc7f121 | ||
|
|
7b1187992c | ||
|
|
96d9879d72 | ||
|
|
bf2fd54578 | ||
|
|
f4a687703c | ||
|
|
95047c1953 | ||
|
|
2b5a1f3e60 | ||
|
|
7dfe2e2595 | ||
|
|
b6455b66bd | ||
|
|
766d1d52a9 | ||
|
|
aef0c489e3 | ||
|
|
2e061994d4 | ||
|
|
a8500d6bae | ||
|
|
ff86407840 | ||
|
|
73452fd79d | ||
|
|
ce6a23ef3b | ||
|
|
f6ef28becc | ||
|
|
66b4620d9b | ||
|
|
8192580b5f | ||
|
|
e79037c44d | ||
|
|
2dee4d1a83 | ||
|
|
b2b95d590f | ||
|
|
92875709e1 | ||
|
|
c6e44509e0 | ||
|
|
40bb0b6f55 | ||
|
|
a8254e0053 | ||
|
|
4830593cac | ||
|
|
f262ec9094 | ||
|
|
5392008916 | ||
|
|
fe3f5471e7 | ||
|
|
1f79713c15 | ||
|
|
489bce5a18 | ||
|
|
adcf869eee | ||
|
|
d05d3cb652 | ||
|
|
e65fac5e84 | ||
|
|
1343a22dc6 | ||
|
|
03518d2ff2 | ||
|
|
03fea392d5 | ||
|
|
d74b85f3fe | ||
|
|
9729c71691 | ||
|
|
af8e869880 | ||
|
|
548f773074 | ||
|
|
fa60917580 | ||
|
|
ac47ba5864 | ||
|
|
adca3035f9 | ||
|
|
676210d6f4 | ||
|
|
95e7d96f52 | ||
|
|
34a944bd1c | ||
|
|
109dde00b2 | ||
|
|
78c32bdf9a | ||
|
|
7c9d594ff6 | ||
|
|
16a5029d58 | ||
|
|
8911cf6051 | ||
|
|
ac3408a4ab | ||
|
|
7d56b38e40 | ||
|
|
2e710289eb | ||
|
|
56f0c8fe93 | ||
|
|
dab2652cb5 | ||
|
|
4df0b94b79 | ||
|
|
364134515b | ||
|
|
d6c444fefa | ||
|
|
bdaa1cbdfd | ||
|
|
a6c3acdf02 | ||
|
|
2bccb43122 | ||
|
|
94d608c6c1 | ||
|
|
2d24bf5f73 | ||
|
|
6bf009edee | ||
|
|
276533e236 | ||
|
|
cf8e7475ca | ||
|
|
30a3b2179b | ||
|
|
8bdbe41505 | ||
|
|
db22b2f504 | ||
|
|
2f773a89df | ||
|
|
b95a6f2626 | ||
|
|
df14a3c399 | ||
|
|
5ee5465f94 | ||
|
|
c3b1eca2c7 | ||
|
|
ece27ac6f8 | ||
|
|
5c745485e1 | ||
|
|
38d0c964d8 | ||
|
|
363a964ebb | ||
|
|
cb5f5c65b0 | ||
|
|
549351bbb4 | ||
|
|
e4aff5b08a | ||
|
|
c72ecb382d | ||
|
|
db260a669c | ||
|
|
aacdcd4add | ||
|
|
994222c317 | ||
|
|
951e8528b4 | ||
|
|
d5268fdc97 | ||
|
|
a8311bd1bd | ||
|
|
bfe4350a89 | ||
|
|
c51ce63b9b | ||
|
|
455076231b | ||
|
|
bdbc86167c | ||
|
|
132a67edb2 | ||
|
|
f121817501 | ||
|
|
c2e38fc6fe | ||
|
|
70664122af | ||
|
|
95795f249a | ||
|
|
f31c55ceed | ||
|
|
157a34bcd9 | ||
|
|
1d1c69f300 | ||
|
|
751b41b94b | ||
|
|
6519b1dde8 | ||
|
|
486180c422 | ||
|
|
a4261b11d4 | ||
|
|
90c50b281a | ||
|
|
a9499ae45c | ||
|
|
91b9b8aebf | ||
|
|
b36243ba10 | ||
|
|
b4beb7aae9 | ||
|
|
c5c90620ca | ||
|
|
37ab7594fc | ||
|
|
076b293fec | ||
|
|
85b6784dcb | ||
|
|
e8ab724382 | ||
|
|
360b34944d | ||
|
|
306d77559e | ||
|
|
e8247c5646 | ||
|
|
6ea81279ff | ||
|
|
ae394ca97d | ||
|
|
507bd1a567 | ||
|
|
4e29147756 | ||
|
|
36b337b7e7 | ||
|
|
81fb408671 | ||
|
|
eef1d500ec | ||
|
|
ca3ea125e2 | ||
|
|
e2f44a231e | ||
|
|
451c58d58f | ||
|
|
59af3fb866 | ||
|
|
369d027544 | ||
|
|
b694f1ac89 | ||
|
|
512b6dfd88 | ||
|
|
6b4fe7975f | ||
|
|
7b4aeef40c | ||
|
|
2fab254ff6 | ||
|
|
5697754c5e | ||
|
|
ee5a5df2a7 | ||
|
|
3307dbc0fb | ||
|
|
1e8a0b69b2 | ||
|
|
0a328789ef | ||
|
|
8917f18ef1 | ||
|
|
d4e939fd04 | ||
|
|
33f21422e6 | ||
|
|
7f8265e2be | ||
|
|
a43e72f696 | ||
|
|
4f4649d046 | ||
|
|
4723a99b15 | ||
|
|
e1930d57f3 | ||
|
|
fd00ea0ca7 | ||
|
|
98ae20c3df | ||
|
|
a54672fb54 | ||
|
|
c5f1acfbfb | ||
|
|
fe21ca5510 | ||
|
|
404fdbcfdf | ||
|
|
97b3c38148 | ||
|
|
839c0451f7 | ||
|
|
4ac17daad2 | ||
|
|
af360d7097 | ||
|
|
3d09f2a980 | ||
|
|
07817c8ee5 | ||
|
|
68a570221b | ||
|
|
c7e4d1d06f | ||
|
|
53b42bf921 | ||
|
|
11a00b025f | ||
|
|
1caeab913b | ||
|
|
ae53c7cb2e | ||
|
|
0f035d0bae | ||
|
|
bf7b9eb3e7 | ||
|
|
3fdc5e20a7 | ||
|
|
c4478ccffa | ||
|
|
7cff6ef6db | ||
|
|
84a0712c05 | ||
|
|
3fb96ff636 | ||
|
|
64b905820d | ||
|
|
308a0b5709 | ||
|
|
3291cbfdad | ||
|
|
67f7ec2d20 | ||
|
|
ad5eab8fd7 | ||
|
|
53ddf724e1 | ||
|
|
01b6bc1dc8 | ||
|
|
05dcda8ef7 | ||
|
|
deabeaaa7f | ||
|
|
fd43e53aeb | ||
|
|
a7fd1b6741 | ||
|
|
effaef024d | ||
|
|
a7fc57a176 | ||
|
|
5e1e43f478 | ||
|
|
5fc8fdee11 | ||
|
|
99293011e6 | ||
|
|
1e0a8de5b1 | ||
|
|
0584990b65 | ||
|
|
5224acad59 | ||
|
|
6c6b8ccc42 | ||
|
|
3f08ef70f1 | ||
|
|
f34250ea6a | ||
|
|
255bec7432 | ||
|
|
651c4ab0ae | ||
|
|
d56b543ff8 | ||
|
|
d85fd5501c | ||
|
|
19221b749c | ||
|
|
5fd2164612 | ||
|
|
e783e00578 | ||
|
|
de0b8226e1 | ||
|
|
f7702c05f2 | ||
|
|
c9dc54bed7 | ||
|
|
9b4afb77d1 | ||
|
|
7ca9f46b9c | ||
|
|
f26bce2538 | ||
|
|
37c498d6dc | ||
|
|
35bf4aee7d | ||
|
|
96c7c4a58b | ||
|
|
2cb108fbf8 | ||
|
|
15f35a9e9e | ||
|
|
74b0ed4b23 | ||
|
|
d98dec3a7c | ||
|
|
c5dc47c647 | ||
|
|
e3d84407e0 | ||
|
|
6f792b7ffb | ||
|
|
24b5d61eb3 | ||
|
|
4a4f4db6a5 | ||
|
|
e13564cb19 | ||
|
|
ee4f95bce5 | ||
|
|
36ce8e395e | ||
|
|
b6b7945830 | ||
|
|
77578f4a3e | ||
|
|
cc52654db3 | ||
|
|
7a6d83e7eb | ||
|
|
853ebf6ad2 | ||
|
|
e329f6f5f2 | ||
|
|
687c91d6e9 | ||
|
|
b8fe48c563 | ||
|
|
6d98c2b1ce | ||
|
|
0b4a61a8ba | ||
|
|
a97226ae54 | ||
|
|
5c44453ced | ||
|
|
41c51d7614 | ||
|
|
2b4829a4b9 | ||
|
|
4b87f71bbc | ||
|
|
868b400120 | ||
|
|
7645e9cb7a | ||
|
|
c60bf694ef | ||
|
|
43784f3409 | ||
|
|
a612b5b648 | ||
|
|
f0d1888ca9 | ||
|
|
1f62978251 | ||
|
|
75f1f832aa | ||
|
|
7519e34507 | ||
|
|
f4303ebdb8 | ||
|
|
e31244e8a5 | ||
|
|
328eba5610 | ||
|
|
0155f4a7f7 | ||
|
|
986630c2dc | ||
|
|
a12e3c1cc9 | ||
|
|
0c1655b884 | ||
|
|
3bc8d7517a | ||
|
|
b51197b52c | ||
|
|
b07e16a3e7 | ||
|
|
a73cb45792 | ||
|
|
1fd59144c7 | ||
|
|
32063cbe23 | ||
|
|
1edd2d01f0 | ||
|
|
5fb81bacd5 | ||
|
|
e90bc3fcab | ||
|
|
4cf74d6dcf | ||
|
|
b6db3767a2 | ||
|
|
4a99fe6d84 | ||
|
|
ae1a015494 | ||
|
|
60121025e2 | ||
|
|
613bcf8055 | ||
|
|
d53127fa44 | ||
|
|
8c4c392cb8 | ||
|
|
03c6efe0d6 | ||
|
|
d89f8128cd | ||
|
|
e91ebddeea | ||
|
|
0fc422544b | ||
|
|
d620961c34 | ||
|
|
9af6a89f20 | ||
|
|
5e231bf874 | ||
|
|
98d9ce31de | ||
|
|
754bfd926b | ||
|
|
5024fc4be7 | ||
|
|
86155ae4c0 | ||
|
|
bd4061c3b0 | ||
|
|
8eb9ddc2eb | ||
|
|
c579ec7f5f | ||
|
|
ec8b8a6a98 | ||
|
|
c488550636 | ||
|
|
678cbd9d47 | ||
|
|
19977edae2 | ||
|
|
81bfd8ce7e | ||
|
|
471f90659f | ||
|
|
6d88d0d39b | ||
|
|
dd4669d1a0 | ||
|
|
af4e0308ae | ||
|
|
717fbc1196 | ||
|
|
d19b1162b3 | ||
|
|
20d9bda87e | ||
|
|
20b83c5b53 | ||
|
|
97ea02d974 | ||
|
|
a02bfdd2bc | ||
|
|
e8d5c939b0 | ||
|
|
8ad6053544 | ||
|
|
4131a92cec | ||
|
|
16d5faac20 | ||
|
|
65f3b52cad | ||
|
|
cb486522a8 | ||
|
|
81636abaa9 | ||
|
|
3deeda0f73 | ||
|
|
e2e4310322 | ||
|
|
65679975ce | ||
|
|
9d9bcfe03f | ||
|
|
97a948fa52 | ||
|
|
e9b83a20d0 | ||
|
|
de0d1f34f3 | ||
|
|
b4e63d47cb | ||
|
|
71b4337036 | ||
|
|
4758b68e55 | ||
|
|
28effac0f1 | ||
|
|
6d34db352d | ||
|
|
2d4cac0018 | ||
|
|
3c13c4f103 | ||
|
|
5624b8afd2 | ||
|
|
4344eaebca | ||
|
|
dd10ccfcef | ||
|
|
43d2027b76 | ||
|
|
a208639ce7 | ||
|
|
bb4c4d9ecf | ||
|
|
9fb8ee9377 | ||
|
|
879d22c7ca | ||
|
|
285ded8bbb | ||
|
|
b67064ef81 | ||
|
|
ad54210c3e | ||
|
|
6d38943222 | ||
|
|
a3d5251b8e | ||
|
|
4811abfa99 | ||
|
|
86e4c7b6ad | ||
|
|
7a8f68cfe6 | ||
|
|
25d47c1da8 | ||
|
|
19f8e0bc63 | ||
|
|
6467513f60 | ||
|
|
7698ba168e | ||
|
|
9548593b57 | ||
|
|
a223655973 | ||
|
|
ad9dca2bd9 | ||
|
|
4d07b63b5a | ||
|
|
b3859c49c1 | ||
|
|
a4df0bdcc3 | ||
|
|
24dc4c0f23 | ||
|
|
fd16357b6e | ||
|
|
cbaf0ccc51 | ||
|
|
269770bbbc | ||
|
|
dce0aa6771 | ||
|
|
03e9da804a | ||
|
|
166ee4c2c8 | ||
|
|
f23f86d91c | ||
|
|
b7bcf27cde | ||
|
|
8096ef6844 | ||
|
|
933c0eb650 | ||
|
|
1a4827ba33 | ||
|
|
bbc3c890ea | ||
|
|
df3e84d580 | ||
|
|
e93391e0f8 | ||
|
|
91db0a6e05 | ||
|
|
eeb436931b | ||
|
|
cfac6c0ebb | ||
|
|
449aff0f62 | ||
|
|
3cd7987af4 | ||
|
|
6c627be4c1 | ||
|
|
0feb4d823f | ||
|
|
60f62a6463 | ||
|
|
62d67d35ec | ||
|
|
a50bde4267 | ||
|
|
fea5603409 | ||
|
|
ccd1c01d0b | ||
|
|
7c7c37a4f4 | ||
|
|
650489dd8a | ||
|
|
ec2d37451b | ||
|
|
8c77b9645c | ||
|
|
92b67fb62e | ||
|
|
1e8d302fd4 | ||
|
|
71d9500b2e | ||
|
|
e31f5fc4b6 | ||
|
|
b0841f78f4 | ||
|
|
973060c728 | ||
|
|
a3c3eb5d2a | ||
|
|
6432ec8a9b | ||
|
|
cd3be74e3b | ||
|
|
a7a8030fea | ||
|
|
ec7e10e068 | ||
|
|
33c0d1dca3 | ||
|
|
9a1dbfa6cf | ||
|
|
f8b1dc5506 | ||
|
|
b8cb936973 | ||
|
|
27f77c7680 | ||
|
|
48bbd2d22a | ||
|
|
e0421a3ba6 | ||
|
|
669b0b68ab | ||
|
|
1e1bf03f61 | ||
|
|
134a083662 | ||
|
|
93a902a757 | ||
|
|
9b5480b7ba | ||
|
|
ab397e5ce1 | ||
|
|
2e61420747 | ||
|
|
d671e06c32 | ||
|
|
bdaf1b01be | ||
|
|
88dadcec78 | ||
|
|
ad4cd05850 | ||
|
|
7083f58326 | ||
|
|
d1580f67df | ||
|
|
c3c9ebdd12 | ||
|
|
8ab62d702c | ||
|
|
f38f0edaaf | ||
|
|
fa97a86751 | ||
|
|
3ca2dfbc1d | ||
|
|
0711f84ea0 | ||
|
|
81c6ad3ab7 | ||
|
|
c9e4c831c2 | ||
|
|
2ef164eeef | ||
|
|
c884f3b213 | ||
|
|
df03b8e4e8 | ||
|
|
61ee633cd2 | ||
|
|
3bedcf4413 | ||
|
|
6286c9ee7c | ||
|
|
5e6cc5ddcb | ||
|
|
98785e47b1 | ||
|
|
a05c440263 | ||
|
|
3b0eb6b786 | ||
|
|
b506aa11fa | ||
|
|
d254c39a77 | ||
|
|
d7dc04eb57 | ||
|
|
37cf839b27 | ||
|
|
d26c8e5336 | ||
|
|
214ad2925b | ||
|
|
7710b541da | ||
|
|
c733e3151b | ||
|
|
6e870e8466 | ||
|
|
a154fd34ee | ||
|
|
5d39126989 | ||
|
|
2726267748 | ||
|
|
c18b28e27c | ||
|
|
b881ae936f | ||
|
|
eb58e29245 | ||
|
|
215c2082d3 | ||
|
|
5d363c1bb9 | ||
|
|
913cdef297 | ||
|
|
56f19f01dd | ||
|
|
b79692c35e | ||
|
|
c5af8bfe78 | ||
|
|
6d25ed2b00 | ||
|
|
7dbb2ed6a3 | ||
|
|
82890ec815 | ||
|
|
659eb3412b | ||
|
|
359e7e4d32 | ||
|
|
68c3749696 | ||
|
|
abe6e8a783 | ||
|
|
64976c249d | ||
|
|
120c1978ae | ||
|
|
7b29ecb15c | ||
|
|
b49b59cbb2 | ||
|
|
acd8d2df98 | ||
|
|
d82505984a | ||
|
|
52a3f4a2bb | ||
|
|
3bc7580e8c | ||
|
|
adbaa42b2b | ||
|
|
a3fc49ee76 | ||
|
|
b062cddeea | ||
|
|
0f6fc689aa | ||
|
|
9ab38f416d | ||
|
|
1a274a253a | ||
|
|
b030791384 | ||
|
|
12b3132b1a | ||
|
|
31ea03feb0 | ||
|
|
723bd22b96 | ||
|
|
aaa8f133c0 | ||
|
|
762306d985 | ||
|
|
da2878958b | ||
|
|
4275b15dcd | ||
|
|
0694fad016 | ||
|
|
e7520c3d18 | ||
|
|
d789b5c0df | ||
|
|
c1d25ba259 | ||
|
|
b961711f28 | ||
|
|
a81f78045b | ||
|
|
9993f2215d | ||
|
|
e92cf311db | ||
|
|
a541f5dfe1 | ||
|
|
f49a7be90c | ||
|
|
fa4462514b | ||
|
|
30a1fea8a0 | ||
|
|
0cca7bb5e7 | ||
|
|
32c4cddb91 | ||
|
|
674ddcd73a | ||
|
|
26511deec0 | ||
|
|
b8a06d728a | ||
|
|
df32817992 | ||
|
|
a8e831e04a | ||
|
|
bbbb5c9a93 | ||
|
|
388deb71ab | ||
|
|
254592c849 | ||
|
|
9ebff9ce00 | ||
|
|
affaeef2ab | ||
|
|
57f6601c9d | ||
|
|
e33db203d8 | ||
|
|
601185f113 | ||
|
|
670061ac33 | ||
|
|
004f9ba1e5 | ||
|
|
90a8ef8e9f | ||
|
|
e4b767e840 | ||
|
|
abe94706f6 | ||
|
|
9dcec6662e | ||
|
|
d1c1dcbe8f | ||
|
|
8422cf93c0 | ||
|
|
e53949f2c8 | ||
|
|
57e47a3296 | ||
|
|
3fcf194e39 | ||
|
|
7253028d79 | ||
|
|
cb9937cde4 | ||
|
|
b4efff1d95 | ||
|
|
e37cbdfcfc | ||
|
|
3a61833d13 | ||
|
|
2b8da333ef | ||
|
|
6053c8f54d | ||
|
|
76beaa6421 | ||
|
|
28b6d222f2 | ||
|
|
6af45362ce | ||
|
|
a866011574 | ||
|
|
4611b5094e | ||
|
|
1e185dacc4 | ||
|
|
4bdfb68ac5 | ||
|
|
2eee3a64b7 | ||
|
|
706dd7020f | ||
|
|
290e3e66c0 | ||
|
|
eb9917536c | ||
|
|
597e8650a6 | ||
|
|
1afe0af343 | ||
|
|
b0840065ed | ||
|
|
1b08bc94f0 | ||
|
|
00324a14b8 | ||
|
|
261f391ee3 | ||
|
|
40bbabb6d0 | ||
|
|
76360d698c | ||
|
|
2f6fbfb338 | ||
|
|
91d6deee03 | ||
|
|
be8acc5ac4 | ||
|
|
719adfbaf3 | ||
|
|
42baeee0ed | ||
|
|
d54425a901 | ||
|
|
e5f23bc11d | ||
|
|
89686b808d | ||
|
|
281732ca38 | ||
|
|
bf295b9039 | ||
|
|
6fd3f38717 | ||
|
|
572008546d | ||
|
|
c73d564004 | ||
|
|
415bbd7860 | ||
|
|
c61ffe9f6b | ||
|
|
1b4dc685f4 | ||
|
|
7951349a1f | ||
|
|
0d2637fede | ||
|
|
4da6085ef8 | ||
|
|
56fef5302c | ||
|
|
7d9e892edc | ||
|
|
c166af5cce | ||
|
|
903a90f37a | ||
|
|
e7edb512b8 | ||
|
|
c6623bb258 | ||
|
|
01c37ac987 | ||
|
|
77f5ed6851 | ||
|
|
53e100b890 | ||
|
|
3fdd182f0c | ||
|
|
1c5970d0a2 | ||
|
|
08a8fe84a4 | ||
|
|
57b61470a3 | ||
|
|
4b8bd48663 | ||
|
|
985b414c64 | ||
|
|
036e41ae69 | ||
|
|
44b711953f | ||
|
|
d7e2305686 | ||
|
|
78208620c0 | ||
|
|
d384ed6e96 | ||
|
|
d634378193 | ||
|
|
48fe019302 | ||
|
|
4173628a31 | ||
|
|
82f4e16a27 | ||
|
|
e7a5bc1afe | ||
|
|
9519fae490 | ||
|
|
ebb2d45761 | ||
|
|
5c9ba1399f | ||
|
|
c425169db0 | ||
|
|
e30405d672 | ||
|
|
1a1be94c99 | ||
|
|
4a179c81d2 | ||
|
|
ac9db81820 | ||
|
|
ec14e94cec | ||
|
|
85d158525f | ||
|
|
78d1d83583 | ||
|
|
65a815a06f | ||
|
|
a32281c268 | ||
|
|
86b79f89ad | ||
|
|
0fc2da5a32 | ||
|
|
a782424d5f | ||
|
|
a923062167 | ||
|
|
f99aaa1191 | ||
|
|
6298a28494 | ||
|
|
1f3f109263 | ||
|
|
6db7e63e1c | ||
|
|
6c038d405b | ||
|
|
99f7bd90db | ||
|
|
557ea9baa6 | ||
|
|
f72bb89521 | ||
|
|
abbfac0404 | ||
|
|
ebf0ef48f1 | ||
|
|
76b280c64c | ||
|
|
349a8a88ae | ||
|
|
e41eff9186 | ||
|
|
bfc9678839 | ||
|
|
f353b098e5 | ||
|
|
34a0f87395 | ||
|
|
0bd9918643 | ||
|
|
7e6887cca8 | ||
|
|
3d6f6530c0 | ||
|
|
b556cec42d | ||
|
|
efeda3dcdc | ||
|
|
9ae2fa6621 | ||
|
|
7f5bfe3ddc | ||
|
|
63a98269bb | ||
|
|
e162c47167 | ||
|
|
a38cbac312 | ||
|
|
1b23f9cd6e | ||
|
|
c88d2780ce | ||
|
|
4f0c6dd879 | ||
|
|
e864238609 | ||
|
|
67009d80fd | ||
|
|
8d4b603572 | ||
|
|
888b480250 | ||
|
|
75230a790c | ||
|
|
d97a8f5740 | ||
|
|
22330c0cae | ||
|
|
c3ec40c3cc | ||
|
|
61ae78432c | ||
|
|
4351187ce5 | ||
|
|
5812ca06d6 | ||
|
|
7c027ee2ff | ||
|
|
a41e55a773 | ||
|
|
b62f721ea6 | ||
|
|
757b0788ae | ||
|
|
ab654f6319 | ||
|
|
b43a21d1be | ||
|
|
37f1429400 | ||
|
|
9f18b639a8 | ||
|
|
639f2bc2f3 | ||
|
|
15b2522ed9 | ||
|
|
de2d08f626 | ||
|
|
08bea91197 | ||
|
|
56c2267d19 | ||
|
|
e680658cf5 | ||
|
|
1969a8b2c1 | ||
|
|
317f651b78 | ||
|
|
5f28b89ae0 | ||
|
|
6d5d38eb6a | ||
|
|
d9e2e37da7 | ||
|
|
30f6e33656 | ||
|
|
cf5dfb9673 | ||
|
|
b3b094eda3 | ||
|
|
14ca4552a0 | ||
|
|
10572717ca | ||
|
|
88cdb2339f | ||
|
|
523b297738 | ||
|
|
d91e35f820 | ||
|
|
e845f3b864 | ||
|
|
0781dd8271 | ||
|
|
7f31f7a3ab | ||
|
|
1ae8684af1 | ||
|
|
729d879b0e | ||
|
|
bd61c233a5 | ||
|
|
5bb736daf2 | ||
|
|
30c859ac7f | ||
|
|
27ab8ff4e1 | ||
|
|
58788ef43a | ||
|
|
79dc862498 | ||
|
|
b5fd4ddb8c | ||
|
|
e8adbd7303 | ||
|
|
d5e136a6d5 | ||
|
|
f55becd43c | ||
|
|
d68804772a | ||
|
|
1c6fa6660e | ||
|
|
50d74dfd20 | ||
|
|
7a2df9f54f | ||
|
|
6e39f61198 | ||
|
|
bdd2d725c8 | ||
|
|
419721ce22 | ||
|
|
58ffb27ff6 | ||
|
|
1fcc7fcdd8 | ||
|
|
27d2247d9d | ||
|
|
8340a71f88 | ||
|
|
9ebb0f27c1 | ||
|
|
418e083781 | ||
|
|
83a2d2af4b | ||
|
|
986ee50437 | ||
|
|
00381988bf | ||
|
|
124c37641d | ||
|
|
107bba02f8 | ||
|
|
4e58c9dab9 | ||
|
|
f0fde26295 | ||
|
|
f5cf181372 | ||
|
|
38dca8396f | ||
|
|
76aa134f66 | ||
|
|
fe59958ea8 | ||
|
|
e1922cb2c5 | ||
|
|
77e142553a | ||
|
|
af281e13db | ||
|
|
dd7712d4d3 | ||
|
|
c52f51b1e4 | ||
|
|
254e311593 | ||
|
|
d0d83526b4 | ||
|
|
3caf54c359 | ||
|
|
b04697d730 | ||
|
|
d0587d6e83 | ||
|
|
d91f59379b | ||
|
|
6dd241921f | ||
|
|
4a90ab1f6a | ||
|
|
e26ccfc247 | ||
|
|
9f7607c064 | ||
|
|
51da42734a | ||
|
|
4547755221 | ||
|
|
aab0ffa044 | ||
|
|
a600dbc06c | ||
|
|
c46eaf36e3 | ||
|
|
7320a87183 | ||
|
|
ca0f6131a1 | ||
|
|
fc05eb898d | ||
|
|
7cfc5204c8 | ||
|
|
79ee7c9a36 | ||
|
|
b897209e0d | ||
|
|
f67d70941e | ||
|
|
6c223c2f84 | ||
|
|
751e86cd4d | ||
|
|
14929e9d15 | ||
|
|
3c64eb9215 | ||
|
|
4058f00275 | ||
|
|
5ea584280c | ||
|
|
5f3ee74e0e | ||
|
|
f43b2760ab | ||
|
|
3d19982d41 | ||
|
|
352d5d0baa | ||
|
|
91b5853aa3 | ||
|
|
802579ad20 | ||
|
|
5aca3045b9 | ||
|
|
6049e0c42c | ||
|
|
df06b4b6b2 | ||
|
|
b2de3dd241 | ||
|
|
f0ec8c0967 | ||
|
|
f750abb9db | ||
|
|
ed7c02d578 | ||
|
|
15dbeff0c9 | ||
|
|
777bcf5865 | ||
|
|
b3e9b82280 | ||
|
|
2a378f6590 | ||
|
|
e1421da5e8 | ||
|
|
222368f7e8 | ||
|
|
d2006c8d8e | ||
|
|
1d787a15a0 | ||
|
|
c76c075569 | ||
|
|
b1b16359d9 | ||
|
|
9ea803b000 | ||
|
|
a62ad3323f | ||
|
|
541b51c524 | ||
|
|
751c97c503 | ||
|
|
0e896e48e4 | ||
|
|
6ec1b61cbc | ||
|
|
4e0d104afd | ||
|
|
d5959922f3 | ||
|
|
8cd42966d6 | ||
|
|
0ec68eb35b | ||
|
|
ecac4ab175 | ||
|
|
2a2bdaa0e0 | ||
|
|
0596660dda | ||
|
|
3099c32d08 | ||
|
|
8bb1f93b8d | ||
|
|
7047f4365a | ||
|
|
14d19356f3 | ||
|
|
e05493bd90 | ||
|
|
f3f78ebc5a | ||
|
|
14359e5025 | ||
|
|
eb2d689971 | ||
|
|
8f8d7d3d95 | ||
|
|
69b0caa38f | ||
|
|
bcc68ca450 | ||
|
|
4c90e2bbc6 | ||
|
|
b5d96e4ed6 | ||
|
|
edb756c084 | ||
|
|
d25a472267 | ||
|
|
6765722a3a | ||
|
|
f43d989368 | ||
|
|
27075e479e | ||
|
|
2edb96062e | ||
|
|
69fc99edbb | ||
|
|
1948daec53 | ||
|
|
d61295eb10 | ||
|
|
3e09e93037 | ||
|
|
68c51be130 | ||
|
|
d0df673c82 | ||
|
|
8fb443522b | ||
|
|
baa5726532 | ||
|
|
ce4b5c5252 | ||
|
|
d4f5db39fa | ||
|
|
37fa1f296d | ||
|
|
52c1395626 | ||
|
|
c3d7be5994 | ||
|
|
acce8dbff6 | ||
|
|
30ca38793e | ||
|
|
95965b7b89 | ||
|
|
c6c0878fa9 | ||
|
|
7efe0234a7 | ||
|
|
52eb6f6bcf | ||
|
|
cec12e203a | ||
|
|
bc97184c63 | ||
|
|
d17229efd5 | ||
|
|
3d805a0f43 | ||
|
|
5f5a59328b | ||
|
|
d59bb027eb | ||
|
|
5a02bde170 | ||
|
|
81eff20ad1 | ||
|
|
916378097c | ||
|
|
de92f45eaf | ||
|
|
5ca0a2f37d | ||
|
|
36601723a2 | ||
|
|
23b1c8f1d2 | ||
|
|
26409cbade | ||
|
|
faca2d027b | ||
|
|
bb7ec485f7 | ||
|
|
23f96e30c3 | ||
|
|
a569de44b6 | ||
|
|
662ea15c23 | ||
|
|
e13a9adff2 | ||
|
|
6ad38f80fb | ||
|
|
4d727263c5 | ||
|
|
6af8805920 | ||
|
|
eeada56c1f | ||
|
|
f5ae470e5e | ||
|
|
d4f1ed0036 | ||
|
|
7375f6a6f5 | ||
|
|
59bda19f3e | ||
|
|
4dbd4d0316 | ||
|
|
c6ff5ccbf4 | ||
|
|
867e867cdd | ||
|
|
f330eb9567 | ||
|
|
ac904b2731 | ||
|
|
8dd5fe83fc | ||
|
|
4d9eac0750 | ||
|
|
12b9a513c1 | ||
|
|
7436d58045 | ||
|
|
53006384d5 | ||
|
|
1167458acd | ||
|
|
7575d53cbf | ||
|
|
66b6447fc1 | ||
|
|
4ee620b57d | ||
|
|
f876946358 | ||
|
|
7014902341 | ||
|
|
39100068c1 | ||
|
|
f1840a52db | ||
|
|
59cee4a3aa | ||
|
|
e0933786e3 | ||
|
|
b695089bc4 | ||
|
|
1fa3ffbf83 | ||
|
|
4a35fd655c | ||
|
|
1489b9901b | ||
|
|
f9cdda7bfd | ||
|
|
b5bdb46268 | ||
|
|
7f51b07f69 | ||
|
|
ff3ae40aeb | ||
|
|
2caba92623 | ||
|
|
b1f5e7e8fa | ||
|
|
3a6436f6f0 | ||
|
|
dcc667cdc7 | ||
|
|
f5f27859e0 | ||
|
|
f38e0f2b4f | ||
|
|
6e80a9111c | ||
|
|
6cf8264362 | ||
|
|
39fae3777c | ||
|
|
bacb36eb10 | ||
|
|
8885f5e344 | ||
|
|
030fc9c320 | ||
|
|
752d089814 | ||
|
|
f4522cd2fc | ||
|
|
a5e48cdf4d | ||
|
|
e7f05f8516 | ||
|
|
e77111bf98 | ||
|
|
81a80ebd61 | ||
|
|
6f92424bab | ||
|
|
47d904a628 | ||
|
|
adc9e749c4 | ||
|
|
0571d22d5f | ||
|
|
77bac4c17a | ||
|
|
3f006dc11a | ||
|
|
badeb2f64c | ||
|
|
d18a200b0f | ||
|
|
abcfd5bad9 | ||
|
|
33553e1c50 | ||
|
|
143f9d7d84 | ||
|
|
43aa1680cb | ||
|
|
28115a847c | ||
|
|
6b14e7cc54 | ||
|
|
0d6a013658 | ||
|
|
407f50a66f | ||
|
|
6ff9021e04 | ||
|
|
6bff67a865 | ||
|
|
824c2567c8 | ||
|
|
593d794655 | ||
|
|
6e14e6ef17 | ||
|
|
f9dd251276 | ||
|
|
a72184684c | ||
|
|
2d3c2dc595 | ||
|
|
ea9920e5d6 | ||
|
|
f20beeab7a | ||
|
|
fe8dfb9c9b | ||
|
|
556c9c236f | ||
|
|
a5c4751718 | ||
|
|
787a5f1715 | ||
|
|
b4483fdcbd | ||
|
|
8932055ed5 | ||
|
|
c9887e13bb | ||
|
|
36bbd6a73f | ||
|
|
4f427f8387 | ||
|
|
d9521fe733 | ||
|
|
e375a0d5ac | ||
|
|
46c39d6ef7 | ||
|
|
33280b7069 | ||
|
|
d414c6e118 | ||
|
|
733c85936b | ||
|
|
54dbc916a8 | ||
|
|
c550ad2268 | ||
|
|
d2d2a3fa8e | ||
|
|
29853a3a45 | ||
|
|
667ffa6101 | ||
|
|
7d29a7b45a | ||
|
|
475f892413 | ||
|
|
893201d3d9 | ||
|
|
f90ea5060d | ||
|
|
8c7a56d4ea | ||
|
|
6ef5e6bd3d | ||
|
|
4056978731 | ||
|
|
4d234e90ae | ||
|
|
fced9a85ec | ||
|
|
f0b035059a | ||
|
|
a1705c093b | ||
|
|
91c7bc43d3 | ||
|
|
09c9f6bdc3 | ||
|
|
1d10a2293a | ||
|
|
ea8b3a5dc0 | ||
|
|
f529269f62 | ||
|
|
a78fa58b51 | ||
|
|
28c929f14d | ||
|
|
8add843ee8 | ||
|
|
be63cb7d12 | ||
|
|
bd113ea882 | ||
|
|
5162cddd5e | ||
|
|
6591620200 | ||
|
|
d34f3ffc83 | ||
|
|
a417cf955d | ||
|
|
d6568f9ce7 | ||
|
|
c63e6b74fa | ||
|
|
636af8933c | ||
|
|
8e0cd35c23 | ||
|
|
bd02174f40 | ||
|
|
2809b4b2b5 | ||
|
|
d3b4dbf8bc | ||
|
|
9e0dd2a96a | ||
|
|
cc1aaceea6 | ||
|
|
f62c66f460 | ||
|
|
2cbde291e4 | ||
|
|
473bd024fe | ||
|
|
a8a4c11b5b | ||
|
|
3567981089 | ||
|
|
bbc9a0abe6 | ||
|
|
129bd898cd | ||
|
|
59432d50ff | ||
|
|
6a744238b9 | ||
|
|
c9839dd7cc | ||
|
|
0f4bec8af0 | ||
|
|
e94491ee8c | ||
|
|
aeca5def00 | ||
|
|
dfcb502ef4 | ||
|
|
ad5fcce6e4 | ||
|
|
16458e070a | ||
|
|
fec1fcdca8 | ||
|
|
60a6e7ba8e | ||
|
|
7012c04005 | ||
|
|
40a6125d73 | ||
|
|
eaac587467 | ||
|
|
e62c17bddf | ||
|
|
ae0688f351 | ||
|
|
69e3ea6581 | ||
|
|
696d420dc8 | ||
|
|
3f57e20235 | ||
|
|
37bbeeb9d0 | ||
|
|
011281df86 | ||
|
|
998157fc9b | ||
|
|
9d98a27b98 | ||
|
|
b292554fd8 | ||
|
|
f8a48f5c13 | ||
|
|
0e4ecfaf56 | ||
|
|
72d1f51146 | ||
|
|
fec05d430b | ||
|
|
02256e900f | ||
|
|
c6bc55e4f9 | ||
|
|
0de0e4ff41 | ||
|
|
d7998870c7 | ||
|
|
4c35d98d7d | ||
|
|
f58d66fe5e | ||
|
|
a056eadc1e | ||
|
|
838a0885fe | ||
|
|
61247fe8d3 | ||
|
|
67f2469e70 | ||
|
|
281fd26e06 | ||
|
|
67b9b1b273 | ||
|
|
d60fac42d6 | ||
|
|
c5e21c1fbf | ||
|
|
180967484e | ||
|
|
5cf9cd5395 | ||
|
|
d9985c0575 | ||
|
|
472946be32 | ||
|
|
48ba7e5f73 | ||
|
|
e71b021b6e | ||
|
|
3addeb57b4 | ||
|
|
9d3ade81fa | ||
|
|
1eef6d3552 | ||
|
|
f5160b7a72 | ||
|
|
c6718c94bf | ||
|
|
4583d62edd | ||
|
|
b7a326a550 | ||
|
|
1bf67b4b62 | ||
|
|
fed5128b7f | ||
|
|
8acd51fc62 | ||
|
|
b2ba4ee34c | ||
|
|
4572fe69de | ||
|
|
7142126609 | ||
|
|
c8d14fb617 | ||
|
|
558a0753c1 | ||
|
|
c49d6a3ec7 | ||
|
|
67c6823dde | ||
|
|
c3468f2ad9 | ||
|
|
0b6bd3cbde | ||
|
|
5a768ddd7b | ||
|
|
a402b1b83d | ||
|
|
b2eb522f55 | ||
|
|
6b81f43206 | ||
|
|
bb61de8379 | ||
|
|
bbfb9a4190 | ||
|
|
88e9ba510b | ||
|
|
4bbe328117 | ||
|
|
d65835f89b | ||
|
|
eaaac8b2a4 | ||
|
|
cdab27d337 | ||
|
|
d30c154e79 | ||
|
|
0fe855cd6d | ||
|
|
08b74f8caf | ||
|
|
38ecfa8467 | ||
|
|
d47dc5da3e | ||
|
|
898c697f13 | ||
|
|
21b4e62e6e | ||
|
|
91e847cb76 | ||
|
|
673e98bc83 | ||
|
|
20234c94ee | ||
|
|
74cb74f1fc | ||
|
|
743fc9dbd0 | ||
|
|
61e6f23ed2 | ||
|
|
e199d0532c | ||
|
|
34a0a2cb5e | ||
|
|
50a45949d1 | ||
|
|
bea9628be0 | ||
|
|
1938828520 | ||
|
|
a0a503e4a8 | ||
|
|
ae5863f5e0 | ||
|
|
679aa2822c | ||
|
|
ca4bd96d5d | ||
|
|
455f9befbc | ||
|
|
98fdb46da9 | ||
|
|
657f2734f1 | ||
|
|
9a80ff57b2 | ||
|
|
8509e4b5f5 | ||
|
|
f0e154d54c | ||
|
|
092d271fa2 | ||
|
|
87c67636df | ||
|
|
75dcdb84b1 | ||
|
|
bc3d184d7c | ||
|
|
2eeca93a97 | ||
|
|
b0f84c5cb2 | ||
|
|
7892dfd53c | ||
|
|
0a2be9d7bf | ||
|
|
c34ec9b7d3 | ||
|
|
8746f84fa2 | ||
|
|
c34fd10e23 | ||
|
|
1244fd09eb | ||
|
|
fb1bebd982 | ||
|
|
2b0b8e6e68 | ||
|
|
8af329e660 | ||
|
|
16bd3fc624 | ||
|
|
73a539780a | ||
|
|
ca0626b168 | ||
|
|
cb84a6cfce | ||
|
|
354408c7e6 | ||
|
|
a0268a1906 | ||
|
|
752d72f58d | ||
|
|
91e1dc639d | ||
|
|
488feb2335 | ||
|
|
d161d4f78c | ||
|
|
cecaf6eabc | ||
|
|
a617e02ae6 | ||
|
|
bff7065360 | ||
|
|
55570119f7 | ||
|
|
3996535e5d | ||
|
|
8807d288d7 | ||
|
|
a733df8f37 | ||
|
|
830da1f8e4 | ||
|
|
2921302fe9 | ||
|
|
3813402aa3 | ||
|
|
6aff27f3ab | ||
|
|
2f54bf5bca | ||
|
|
f754cb422b | ||
|
|
ae118519ab | ||
|
|
da8ffd477d | ||
|
|
6729dc1c6d | ||
|
|
08e81f2765 | ||
|
|
0ddbfccb08 | ||
|
|
0e9e487930 | ||
|
|
9ca63f16bc | ||
|
|
672194b475 | ||
|
|
f58b217369 | ||
|
|
3c0cd3cbc8 | ||
|
|
bb2b180ecc | ||
|
|
b54a15faa2 | ||
|
|
571d654e67 | ||
|
|
5c3ba79c6f | ||
|
|
2312fa845e | ||
|
|
708f416c84 | ||
|
|
9205c8aab4 | ||
|
|
47f193fe2d | ||
|
|
200f176951 | ||
|
|
4628308415 | ||
|
|
bfce6dba9b | ||
|
|
b23b9ea1d2 | ||
|
|
9a0100d6de | ||
|
|
5fb54ed1f3 | ||
|
|
e4614f301c | ||
|
|
71b58e24a9 | ||
|
|
e6ecb77d9a | ||
|
|
b11d9708ed | ||
|
|
dd9e1aff70 | ||
|
|
ee84e1773d | ||
|
|
4ab4a9afe8 | ||
|
|
7551006102 | ||
|
|
df5628422c | ||
|
|
e8b6d92d4d | ||
|
|
95c284c764 | ||
|
|
337f6c5808 | ||
|
|
d564fc95df | ||
|
|
1fee3633a0 | ||
|
|
835e5b71a8 | ||
|
|
857b78ddca | ||
|
|
21633bc0ba | ||
|
|
2455aee97c | ||
|
|
3bc79e80d5 | ||
|
|
5a99e694ce | ||
|
|
ee645007f2 | ||
|
|
7308017ee8 | ||
|
|
fe0bf6ebf3 | ||
|
|
d9d6d996e9 | ||
|
|
af51220f34 | ||
|
|
b9d57483d8 | ||
|
|
746afbd790 | ||
|
|
9a7d1bb566 | ||
|
|
de2c6a2a3d | ||
|
|
67ce4d862b | ||
|
|
38aca5047f | ||
|
|
3731820c48 | ||
|
|
a91d7cb2f7 | ||
|
|
a191fbbec8 | ||
|
|
5a844c91f1 | ||
|
|
a830a3c161 | ||
|
|
e719e48a84 | ||
|
|
eb0117b1c1 | ||
|
|
9898024ce9 | ||
|
|
1958673806 | ||
|
|
a52a04550e | ||
|
|
84d4bf8fdb | ||
|
|
b1e3b0cdf9 | ||
|
|
8089631f10 | ||
|
|
473233019c | ||
|
|
b58756f38b | ||
|
|
e16f827223 | ||
|
|
cd084a33c6 | ||
|
|
630b746cab | ||
|
|
777dc8c48b | ||
|
|
e2a169b0e5 | ||
|
|
83b5b9e660 | ||
|
|
6b2b279889 | ||
|
|
98408bbed0 | ||
|
|
b08d6f1969 | ||
|
|
6d4ec5c989 | ||
|
|
888a904c9b | ||
|
|
100c8f60a5 | ||
|
|
748c1ab1de | ||
|
|
fd3b474a63 | ||
|
|
d903af5373 | ||
|
|
836dd98113 | ||
|
|
93774087f1 | ||
|
|
2c5304a520 | ||
|
|
29d9a1e810 | ||
|
|
b35f5780dc | ||
|
|
b610866d77 | ||
|
|
247070cd82 | ||
|
|
dcbc28fd49 | ||
|
|
38e35cefc7 | ||
|
|
4bd694e205 | ||
|
|
6688d6c590 | ||
|
|
c0a9c67632 | ||
|
|
ff4e8d94c0 | ||
|
|
d5c2c252a5 | ||
|
|
0cb491e135 | ||
|
|
cf2f29a5f1 | ||
|
|
48f826c2c5 | ||
|
|
61b89865ff | ||
|
|
3469bb2ea6 | ||
|
|
81e2c6f2db | ||
|
|
85ffbf1f57 | ||
|
|
d0c43c99c3 | ||
|
|
9620596d59 | ||
|
|
ba1c0b0137 | ||
|
|
e2a685b8bb | ||
|
|
3dbc93c663 | ||
|
|
cb1bc0aaf8 | ||
|
|
428b7d3f9d | ||
|
|
2a3d2fb8c1 | ||
|
|
401059066e | ||
|
|
952ca18bf9 | ||
|
|
94483202ec | ||
|
|
c0f099c2cf | ||
|
|
bb36e78428 | ||
|
|
5673205d2e | ||
|
|
507f2ff45e | ||
|
|
fdc7036fe3 | ||
|
|
a2b876e261 | ||
|
|
3b54b68b59 | ||
|
|
632bf56f29 | ||
|
|
38e713c23c | ||
|
|
8d5e5519f2 | ||
|
|
ed46cfa19d | ||
|
|
1385018724 | ||
|
|
31085fb1d7 | ||
|
|
777023c7a8 | ||
|
|
933d5b261a | ||
|
|
71f99423c5 | ||
|
|
75c72bc59b | ||
|
|
7181f5d163 | ||
|
|
812a2c7cbd | ||
|
|
137aab9631 | ||
|
|
a6ea01a23f | ||
|
|
3b51f64411 | ||
|
|
969e3f4a80 | ||
|
|
7103fa78ff | ||
|
|
afd5d9eff3 | ||
|
|
6bb773d0dd | ||
|
|
aaefb76888 | ||
|
|
ac672d9dc5 | ||
|
|
2f2c0ddc99 | ||
|
|
9c1b1829cf | ||
|
|
81823fe7df | ||
|
|
2c82a327dd | ||
|
|
ed73f0b6ef | ||
|
|
40a882d01e | ||
|
|
bd93d2f334 | ||
|
|
c9325338a8 | ||
|
|
3a813aaef6 | ||
|
|
6f28818f87 | ||
|
|
32213ce679 | ||
|
|
a7153c67e6 | ||
|
|
c159165780 | ||
|
|
c52c31a855 | ||
|
|
c7d16699a4 | ||
|
|
41f50b246c | ||
|
|
7c1350d007 | ||
|
|
774c69e3c6 | ||
|
|
434f538919 | ||
|
|
abcd746774 | ||
|
|
e78839d2fc | ||
|
|
0ad4e9d51f | ||
|
|
bf9027ff2d | ||
|
|
115deee252 | ||
|
|
1158ce41df | ||
|
|
ab21a253e0 | ||
|
|
8160db23cc | ||
|
|
32fa84c5a5 | ||
|
|
39b9341359 | ||
|
|
0454cc95f9 | ||
|
|
c1146e298b | ||
|
|
f0325575c2 | ||
|
|
0bd2bb1e8e | ||
|
|
b12e4689e8 | ||
|
|
7ca02bda0f | ||
|
|
c33ed9144c | ||
|
|
046d5a9cb7 | ||
|
|
cb43f19a90 | ||
|
|
aa54c3402b | ||
|
|
b724d789fd | ||
|
|
a1f6403463 | ||
|
|
b8369ab19a | ||
|
|
2ac981e422 | ||
|
|
f7334f58d3 | ||
|
|
93dce7a2d3 | ||
|
|
c61e098066 | ||
|
|
c431bf5982 | ||
|
|
3e82eb7010 | ||
|
|
a73a1a3f09 | ||
|
|
82bde51c27 | ||
|
|
8a2a9abbd4 | ||
|
|
6cb7583756 | ||
|
|
fdb5ed1fcb | ||
|
|
f7831240e1 | ||
|
|
a4992602ee | ||
|
|
5cae4cc614 | ||
|
|
21ddcb8487 | ||
|
|
f408f08850 | ||
|
|
02ab92ea65 | ||
|
|
881a5c531b | ||
|
|
958c6553e7 | ||
|
|
6ce832e439 | ||
|
|
2bbcd49278 | ||
|
|
66f1ae003f | ||
|
|
e0a24f94c0 | ||
|
|
dd41406a55 | ||
|
|
89dcd3e8b1 | ||
|
|
f9f0940297 | ||
|
|
27003dc0fd | ||
|
|
a15e6127cf | ||
|
|
fe409a76a6 | ||
|
|
8b5f7f0fb2 | ||
|
|
dabcff1c07 | ||
|
|
53d08d1883 | ||
|
|
0816c995a7 | ||
|
|
baf27ff021 | ||
|
|
35acd799cf | ||
|
|
3e764ada0c | ||
|
|
72f348658f | ||
|
|
88c9ae6ca6 | ||
|
|
ad3be1a69e | ||
|
|
b5f38dd23f | ||
|
|
ef73bb404b | ||
|
|
02592378e2 | ||
|
|
ee5ee5f432 | ||
|
|
556f40bf00 | ||
|
|
c8b934f8d3 | ||
|
|
0a692cc497 | ||
|
|
102329c54d | ||
|
|
e1ca1a82fb | ||
|
|
efb1fd2066 | ||
|
|
41a4df0a38 | ||
|
|
e32632b9d9 | ||
|
|
2178180a19 | ||
|
|
0120f3ed92 | ||
|
|
adb36e47ff | ||
|
|
3d6c997012 | ||
|
|
2f4ff6577a | ||
|
|
e000b22578 | ||
|
|
7d64c465c0 | ||
|
|
2713aa1772 | ||
|
|
ca590cb559 | ||
|
|
aad9f61bad | ||
|
|
c9c4105289 | ||
|
|
ff9d565ba7 | ||
|
|
7884fa3d7c | ||
|
|
dfc4717308 | ||
|
|
a99b006b98 | ||
|
|
6fa280be0b | ||
|
|
70db88dd90 | ||
|
|
8bd3dec331 | ||
|
|
11307eb350 | ||
|
|
c28e4cb0f7 | ||
|
|
81701b400c | ||
|
|
e75d851bc4 | ||
|
|
fdff937cb2 | ||
|
|
34747b2015 | ||
|
|
66b619dfa4 | ||
|
|
f27ec3f226 | ||
|
|
cb0a66b743 | ||
|
|
7b6c9b3b3c | ||
|
|
4bc827d1da | ||
|
|
7b152919a6 | ||
|
|
21be680ac2 | ||
|
|
c22b6edeeb | ||
|
|
74346efccb | ||
|
|
60c65f6da7 | ||
|
|
89e34ae1d6 | ||
|
|
281a9c45eb | ||
|
|
ff0412b417 | ||
|
|
3b5f40710c | ||
|
|
46e8259b99 | ||
|
|
805179b9da | ||
|
|
b92aa20cef | ||
|
|
146039f402 | ||
|
|
2f57ee60d1 | ||
|
|
18bb3c3244 | ||
|
|
60528c5c2a | ||
|
|
8a628c451c | ||
|
|
0adc6cc65e | ||
|
|
867619fa40 | ||
|
|
e90279c513 | ||
|
|
72ae3585e4 | ||
|
|
e918ea9c65 | ||
|
|
69c0f55679 | ||
|
|
8040ef8efb | ||
|
|
23be6233c8 | ||
|
|
688fe3e2b2 | ||
|
|
ed2ee2f6f3 | ||
|
|
c40de7e424 | ||
|
|
7ffb3590c4 | ||
|
|
f9023c0603 | ||
|
|
b66bfb41aa | ||
|
|
6f5700a3a6 | ||
|
|
5126c5018a | ||
|
|
74b95e9152 | ||
|
|
555560f63c | ||
|
|
ef2cfdb0d1 | ||
|
|
47543cf82a | ||
|
|
329f0b9cf4 | ||
|
|
bd9ee88e2f | ||
|
|
70fcedb113 | ||
|
|
9d9a6feb96 | ||
|
|
1719952f49 | ||
|
|
8ac1d37b10 | ||
|
|
7b5158f5f2 | ||
|
|
36416d77b8 | ||
|
|
b60ae4745f | ||
|
|
3e347c33dc | ||
|
|
5dc899d64e | ||
|
|
4bbb1f4b63 | ||
|
|
2da3388aa5 | ||
|
|
3ebf0ce7fd | ||
|
|
1fb302d480 | ||
|
|
95b45da57c | ||
|
|
40b75f6cee | ||
|
|
9f7a5c7a6f | ||
|
|
640698d28b | ||
|
|
67126d6f48 | ||
|
|
0dae43e4bc | ||
|
|
6162670bbd | ||
|
|
bc7ff623f8 | ||
|
|
53832aff24 | ||
|
|
b7d3ed26c6 | ||
|
|
a7838cac07 | ||
|
|
cc9460b8fa | ||
|
|
9e0a690d2e | ||
|
|
073d6d2d43 | ||
|
|
1770d031b4 | ||
|
|
1b3c651643 | ||
|
|
3b5d1d0e25 | ||
|
|
1fb3d00932 | ||
|
|
ed495663e8 | ||
|
|
04cd474708 | ||
|
|
6b99cbdc02 | ||
|
|
d8f0c0bdff | ||
|
|
039cd0ac5b | ||
|
|
045de596e2 | ||
|
|
56f845f3c1 | ||
|
|
8e28bd36a2 | ||
|
|
73de247a15 | ||
|
|
7f7d2da5fe | ||
|
|
c32c7fa1dc | ||
|
|
93cab990c7 | ||
|
|
d18e10c7c9 | ||
|
|
e7a920fe16 | ||
|
|
79e6f23fdc | ||
|
|
c96d794604 | ||
|
|
e6fddd364d | ||
|
|
50976e1b5a | ||
|
|
720459183e | ||
|
|
a956186c76 | ||
|
|
336f86c101 | ||
|
|
4426e47e2a | ||
|
|
20796b89c1 | ||
|
|
b5e45bccf9 | ||
|
|
798bca561b | ||
|
|
f200781436 | ||
|
|
52b76930aa | ||
|
|
4331f38912 | ||
|
|
73439b7acb | ||
|
|
1e6cf0cd98 | ||
|
|
edd7cabf68 | ||
|
|
dfba2cb6b2 | ||
|
|
65c024f7cf | ||
|
|
109013bed7 | ||
|
|
32b0428303 | ||
|
|
a4dea4b7fc | ||
|
|
32ebb1e2c7 | ||
|
|
cb1a6eae1e | ||
|
|
8a42c0ad9f | ||
|
|
c23c0ee7d2 | ||
|
|
06f82d1db5 | ||
|
|
d20a9d73d4 | ||
|
|
a9a20003c0 | ||
|
|
f24427cd76 | ||
|
|
fa4df36963 | ||
|
|
5580fd64b3 | ||
|
|
fddd7c620f | ||
|
|
1909c75c21 | ||
|
|
a219ae3d27 | ||
|
|
4de33effdc | ||
|
|
930e6752d9 | ||
|
|
4d00aa1800 | ||
|
|
062a6628e5 | ||
|
|
473624fcd7 | ||
|
|
cd7134e6f6 | ||
|
|
c2ee73d211 | ||
|
|
90a415ae10 | ||
|
|
4287362aa6 | ||
|
|
d430767fa7 | ||
|
|
83a80a9b7d | ||
|
|
73f603d90e | ||
|
|
258252cbf3 | ||
|
|
349cf841d6 | ||
|
|
07fa15806f | ||
|
|
dff5bda202 | ||
|
|
bc19e97d45 | ||
|
|
f8a2087fc6 | ||
|
|
46fc4a0a8e | ||
|
|
80f345d7c1 | ||
|
|
6a90fceaaf | ||
|
|
66493ce821 | ||
|
|
6f11969257 | ||
|
|
4528f35bc0 | ||
|
|
4010dd71f6 | ||
|
|
8a8b49ea02 | ||
|
|
c9465cf7fa | ||
|
|
ee619701d8 | ||
|
|
f0f550783f | ||
|
|
9ea570ea4e | ||
|
|
616788a600 | ||
|
|
2f67a16c6f | ||
|
|
2c932b1bf4 | ||
|
|
1f1b6c8036 | ||
|
|
1b89c08bfc | ||
|
|
5b98f1a068 | ||
|
|
efb02f71f0 | ||
|
|
b7aeeb968b | ||
|
|
121b3c31d2 | ||
|
|
25dfe7278c | ||
|
|
91af2ddd1e | ||
|
|
82aed45816 | ||
|
|
523b7f3ebc | ||
|
|
f7acb20e23 | ||
|
|
a4dd6c7ce5 | ||
|
|
4a84643894 | ||
|
|
9f660738b3 | ||
|
|
8174c05df4 | ||
|
|
d02ee5cf80 | ||
|
|
6649888d1c | ||
|
|
fefba5ff1d | ||
|
|
a8edff1e84 | ||
|
|
873161ea7c | ||
|
|
e2b56c4462 | ||
|
|
4f428fae40 | ||
|
|
6aecae3121 | ||
|
|
2971235299 | ||
|
|
c575a1f1d6 | ||
|
|
ceb72da632 | ||
|
|
716dc2e650 | ||
|
|
26b6e4c619 | ||
|
|
2eea139731 | ||
|
|
3ee32ef153 | ||
|
|
f44df28a96 | ||
|
|
b91b98b21e | ||
|
|
3bb237deee | ||
|
|
ff33fbbb9a | ||
|
|
107c89d37b | ||
|
|
a0548898f3 | ||
|
|
3109a9bc1f | ||
|
|
33f489bba9 | ||
|
|
e93cc13698 | ||
|
|
a65c9ba083 | ||
|
|
c517b0d8f1 | ||
|
|
061a79bfbd | ||
|
|
b8ece2af06 | ||
|
|
27a1a6609f | ||
|
|
b022e51d94 | ||
|
|
8aae5bdb28 | ||
|
|
006173342c | ||
|
|
171148d3a6 | ||
|
|
8cfdc28f2b | ||
|
|
440a5eb2a0 | ||
|
|
6e19921f98 | ||
|
|
a8eb217e4a | ||
|
|
898deb48c4 | ||
|
|
70229be9bc | ||
|
|
89702a5b4e | ||
|
|
31f213d6ca | ||
|
|
17ea15eb6f | ||
|
|
3bfa6416d8 | ||
|
|
2ddabe5fa8 | ||
|
|
4d739c41a0 | ||
|
|
d2d7c5bead | ||
|
|
35490216a7 | ||
|
|
d67de182a0 | ||
|
|
3d11d1aebf | ||
|
|
90028e47e9 | ||
|
|
f5e4026aee | ||
|
|
420e387055 | ||
|
|
ce8973b33a | ||
|
|
fd54956d70 | ||
|
|
e2f8ea2809 | ||
|
|
8d6acd2aec | ||
|
|
61f9414b09 | ||
|
|
152026fa08 | ||
|
|
43757df016 | ||
|
|
eb9f1808c0 | ||
|
|
abda054720 | ||
|
|
98edb4f854 | ||
|
|
db30cee6a9 | ||
|
|
6c4b4f4d12 | ||
|
|
adb218e605 | ||
|
|
b30501b411 | ||
|
|
0aa66b9608 | ||
|
|
88e34ff5de | ||
|
|
61c0ae4e94 | ||
|
|
90d3535520 | ||
|
|
203a965b3d | ||
|
|
3846d9e734 | ||
|
|
bbd63616b1 | ||
|
|
6b1a72aac9 | ||
|
|
e7718b385f | ||
|
|
957803e60c | ||
|
|
64c752ff9d | ||
|
|
e386a2bf72 | ||
|
|
43ac693900 | ||
|
|
aceb87d188 | ||
|
|
8474f77db4 | ||
|
|
7e6390c4b6 | ||
|
|
4256af22ff | ||
|
|
c25ae35737 | ||
|
|
c2ddf537c6 | ||
|
|
817d827f7a | ||
|
|
4ab972b87a | ||
|
|
4e90ae9a28 | ||
|
|
0ca230a197 | ||
|
|
22124e5f61 | ||
|
|
a74aaca681 | ||
|
|
1699864b8a | ||
|
|
c5843988c0 | ||
|
|
40eaf144f0 | ||
|
|
c46b6ca27e | ||
|
|
081c1d681c | ||
|
|
c0b3de6248 | ||
|
|
3985f50c5b | ||
|
|
ff25c0ccc2 | ||
|
|
08351b5e48 | ||
|
|
29b986fa76 | ||
|
|
d5f9db76b3 | ||
|
|
0b90ebd74e | ||
|
|
50cdf8e6d1 | ||
|
|
e016c4e423 | ||
|
|
e6b441eea4 | ||
|
|
4eed07bbab | ||
|
|
a747a87e83 | ||
|
|
02515350f7 | ||
|
|
b851e04c17 | ||
|
|
cbebff495b | ||
|
|
0db4be56a5 | ||
|
|
41fbec9063 | ||
|
|
f0cf7adf84 | ||
|
|
c2ba5901e4 | ||
|
|
c632d08550 | ||
|
|
87964eb57a | ||
|
|
bd3daeed5a | ||
|
|
fa1506c833 | ||
|
|
d9ea3df85f | ||
|
|
1ba03af2da | ||
|
|
bbdaa44acb | ||
|
|
b42c5d6dfa | ||
|
|
aad3d4107f | ||
|
|
bb22f1dc8a | ||
|
|
f63c21f0f3 | ||
|
|
4b2bc29b14 | ||
|
|
07dd06c53c | ||
|
|
7a2572a0fb | ||
|
|
17db5bff8d | ||
|
|
479f716625 | ||
|
|
955bc957ba | ||
|
|
5614e997a4 | ||
|
|
6400cec7ae | ||
|
|
c1f4d06be2 | ||
|
|
56e87e3bdb | ||
|
|
a870b69733 | ||
|
|
ff0a8956ee | ||
|
|
c79e1cc89b | ||
|
|
79ff013ea7 | ||
|
|
ae2e37b4bd | ||
|
|
2cd96cabcd | ||
|
|
852b542913 | ||
|
|
ddea33d93a | ||
|
|
e64dcf5e59 | ||
|
|
6582182e0c | ||
|
|
b2a5a1d22f | ||
|
|
e22d007ab7 | ||
|
|
06540f73f7 | ||
|
|
ce7717e450 | ||
|
|
e5b77e08de | ||
|
|
46b44fc141 | ||
|
|
2d030f3a3c | ||
|
|
1ea958158a | ||
|
|
2ab86a4895 | ||
|
|
d775c6c14c | ||
|
|
333a0b1c0e | ||
|
|
933a0db9ed | ||
|
|
126126cc78 | ||
|
|
5d18657ac5 | ||
|
|
24e0b568b5 | ||
|
|
a26330a292 | ||
|
|
9c0c05631c | ||
|
|
a8930f12cd | ||
|
|
5dc8a0808e | ||
|
|
d8082b9db4 | ||
|
|
9e8dae817d | ||
|
|
d628764da6 | ||
|
|
73ddd3b438 | ||
|
|
9fbd135f14 | ||
|
|
70a9de085b | ||
|
|
e9290252d6 | ||
|
|
86c8207d31 | ||
|
|
3e8cafa857 | ||
|
|
330ffed87e | ||
|
|
7e1af658b6 | ||
|
|
bd618569e1 | ||
|
|
26a8017e99 | ||
|
|
38cb2842ac | ||
|
|
556204fddc | ||
|
|
946b36bb4d | ||
|
|
1d44e92a6e | ||
|
|
5c23ee504c | ||
|
|
ca5ad58ad2 | ||
|
|
85aa802d4b | ||
|
|
792856a505 | ||
|
|
50fe691604 | ||
|
|
bde2ee6a7e | ||
|
|
8b0784f26c | ||
|
|
695c92fb00 | ||
|
|
e93253e270 | ||
|
|
a5b04d5cf3 | ||
|
|
e02755632e | ||
|
|
0d20a81354 | ||
|
|
e67e37c772 | ||
|
|
14df0717ca | ||
|
|
d8be4de6cf | ||
|
|
1a1eaa0810 | ||
|
|
a069c41b20 | ||
|
|
39679f9518 | ||
|
|
ef69474636 | ||
|
|
79f5a16536 | ||
|
|
2085a482c7 | ||
|
|
9772584d78 | ||
|
|
3d47c52b47 | ||
|
|
6a3bdf43dc | ||
|
|
bcda69c1e5 | ||
|
|
c3af189045 | ||
|
|
901ec0ad37 | ||
|
|
bfbe85b5c2 | ||
|
|
e857833a91 | ||
|
|
a36cc6491c | ||
|
|
1589ffb224 | ||
|
|
68e9ec0d41 | ||
|
|
4c407c8a59 | ||
|
|
52de3940fe | ||
|
|
d1d6e907c5 | ||
|
|
50f06a0fe8 | ||
|
|
e6d802a5ff | ||
|
|
7f1704b2ac | ||
|
|
9abe07852e | ||
|
|
7ebc3b0884 | ||
|
|
b116fe287c | ||
|
|
c1af9bda0a | ||
|
|
ec3aeb1e47 | ||
|
|
1070f0c0af | ||
|
|
f4ee87ba24 | ||
|
|
c2d5b32ee2 | ||
|
|
4460b5ce50 | ||
|
|
317e9131e8 | ||
|
|
eb0ab38618 | ||
|
|
e15b5f4587 | ||
|
|
e92501e51e | ||
|
|
b8590180d8 | ||
|
|
443e900e07 | ||
|
|
5aacc6b015 | ||
|
|
2e53c06159 | ||
|
|
eb61373b69 | ||
|
|
22ca927f12 | ||
|
|
5a81731577 | ||
|
|
aac968162b | ||
|
|
e9e55e7ac3 | ||
|
|
bfb336df0c | ||
|
|
dfa3f8d597 | ||
|
|
89cf29080b | ||
|
|
96a1c74d45 | ||
|
|
a10e9c6573 | ||
|
|
6217622865 | ||
|
|
3fc57ba8d8 | ||
|
|
61d82b02b5 | ||
|
|
483a658144 | ||
|
|
f278fa454e | ||
|
|
7b6b609df1 | ||
|
|
12b7818caa | ||
|
|
fad91b86bd | ||
|
|
60ea0561ec | ||
|
|
381c88ce0c | ||
|
|
d530bdba67 | ||
|
|
28ccb51f9b | ||
|
|
a6cf6afb49 | ||
|
|
95ede7c4b8 | ||
|
|
3a31d37d35 | ||
|
|
36f51ff2bf | ||
|
|
0264381f79 | ||
|
|
3d3654707b | ||
|
|
4e66ed81d2 | ||
|
|
34fab1566f | ||
|
|
34248c2fbf | ||
|
|
51c8d73b11 | ||
|
|
741a85187c | ||
|
|
4524ecf66b | ||
|
|
250ff451ba | ||
|
|
cc8b7390ee | ||
|
|
b28d31d0fe | ||
|
|
67c98eca5f | ||
|
|
3574d68465 | ||
|
|
20cd34a3a5 | ||
|
|
1fb57e439e | ||
|
|
15d3e94a66 | ||
|
|
59f3fed9f2 | ||
|
|
978fca6f7c | ||
|
|
9e4edcd8ec | ||
|
|
f788f50b5a | ||
|
|
53907a9cfe | ||
|
|
26349b30c5 | ||
|
|
c0956dbd34 | ||
|
|
7fd06ef311 | ||
|
|
6f4a04c2d7 | ||
|
|
52dc18e350 | ||
|
|
19312d0a1a | ||
|
|
30c1be40da | ||
|
|
e8f73134e6 | ||
|
|
75ee1baf1d | ||
|
|
a7c53c7f5f | ||
|
|
47cc9687a0 | ||
|
|
7d7f093120 | ||
|
|
8f40d9b34e | ||
|
|
c16eca0065 | ||
|
|
ee4b9ab82f | ||
|
|
5268574ade | ||
|
|
5bb37ad2c4 | ||
|
|
ae2bae137a | ||
|
|
b7af7276c9 | ||
|
|
d2635ab799 | ||
|
|
3a81e6bee4 | ||
|
|
b5e24d3527 | ||
|
|
403329db49 | ||
|
|
790b640521 | ||
|
|
e0d1aa8a1a | ||
|
|
8f04f5d5f6 | ||
|
|
6950ce3bcf | ||
|
|
c514e731da | ||
|
|
feb269c97c | ||
|
|
8564ac391c | ||
|
|
088b0dab90 | ||
|
|
edc70e27d9 | ||
|
|
4046552dd1 | ||
|
|
758458e5a0 | ||
|
|
4a13efd081 | ||
|
|
a506030082 | ||
|
|
03b1312f2d | ||
|
|
565146d9d1 | ||
|
|
6e97b9bb73 | ||
|
|
459a42aab6 | ||
|
|
a53bd3dfe9 | ||
|
|
2a5095a1ea | ||
|
|
266e1c4be1 | ||
|
|
b1575b4dcb | ||
|
|
4c67230436 | ||
|
|
55218c8c4d | ||
|
|
17d9c8c9dd | ||
|
|
7d56827e26 | ||
|
|
f73ca007e6 | ||
|
|
15c1edd552 | ||
|
|
620c6c7378 | ||
|
|
a5b7f14dfa | ||
|
|
b2a6f43923 | ||
|
|
6dfe4c0b96 | ||
|
|
068f71847e | ||
|
|
c443f49da3 | ||
|
|
f518e0675c | ||
|
|
73cbb4b5dc | ||
|
|
9a83d4e8d5 | ||
|
|
9fcc8fe9ae | ||
|
|
ca98e2655a | ||
|
|
ab357c75a5 | ||
|
|
bd23fe9c44 | ||
|
|
3e8575c931 | ||
|
|
1c1542dd94 | ||
|
|
5e7aee217e | ||
|
|
7649a1df41 | ||
|
|
e0b9865386 | ||
|
|
ef0d22be30 | ||
|
|
62b440ff6c | ||
|
|
5573faec6b | ||
|
|
2230652218 | ||
|
|
2e1a3eaf16 | ||
|
|
5e17746427 | ||
|
|
7b334bf2e5 | ||
|
|
39dfee79c7 | ||
|
|
bb9c3dd9ce | ||
|
|
08a2775dbd | ||
|
|
2a8c139ca4 | ||
|
|
30f6e4b16f | ||
|
|
00ab6af046 | ||
|
|
ea9b10c764 | ||
|
|
ab9e3b718c | ||
|
|
9e8022f6f6 | ||
|
|
b068616366 | ||
|
|
f2e8c6f3bd | ||
|
|
3d34bed0e3 | ||
|
|
6122056517 | ||
|
|
d8be976356 | ||
|
|
209b919fe6 | ||
|
|
d022493297 | ||
|
|
79c5e0a52d | ||
|
|
42d9db04f2 | ||
|
|
83166a7ff3 | ||
|
|
3a0172888a | ||
|
|
ab5e01d114 | ||
|
|
f3bda8a57a | ||
|
|
23a5edbd11 | ||
|
|
d6b53d18b8 | ||
|
|
f767ce816b | ||
|
|
510ca9f9e2 | ||
|
|
3d73fbf5fd | ||
|
|
4c622c504f | ||
|
|
a54edf8fbc | ||
|
|
3b81bf0e33 | ||
|
|
3ba625da22 | ||
|
|
ed83ff37f8 | ||
|
|
9716ee8eca | ||
|
|
be3e4caf1d | ||
|
|
7da68c91a5 | ||
|
|
f66585c250 | ||
|
|
d392858ee3 | ||
|
|
6c5c9eddba | ||
|
|
bcfbe02c8d | ||
|
|
023310882f | ||
|
|
7d54e28e30 | ||
|
|
c8ff517389 | ||
|
|
33ddb13e5d | ||
|
|
a94402a8a2 | ||
|
|
2e2e4e68d3 | ||
|
|
82b07a02fa | ||
|
|
f006e66549 | ||
|
|
4156b51c18 | ||
|
|
9576973b57 | ||
|
|
36d5272f05 | ||
|
|
fd4f5e40ca | ||
|
|
660b56acb5 | ||
|
|
f5a5eb3736 | ||
|
|
7041bb5bd9 | ||
|
|
e4147a6bed | ||
|
|
bfa12e5a66 | ||
|
|
b744947711 | ||
|
|
412a862677 | ||
|
|
dd879c3ef2 | ||
|
|
dfb1ec99ac | ||
|
|
6dc42ee902 | ||
|
|
5ff7511a14 | ||
|
|
e2aae39f15 | ||
|
|
c12ccd8357 | ||
|
|
92e1f1011a | ||
|
|
1a09ae81db | ||
|
|
7aafa54bf0 | ||
|
|
f4a9fa6569 | ||
|
|
38d9acbbfb | ||
|
|
0964700e6d | ||
|
|
d5ff69b1aa | ||
|
|
ff0a947364 | ||
|
|
6e5938c833 | ||
|
|
b9127e163b | ||
|
|
4fad87e438 | ||
|
|
3acb0514e1 | ||
|
|
5f8eea9adf | ||
|
|
a32937cef2 | ||
|
|
9eabbded2b | ||
|
|
e72a89ec23 | ||
|
|
a9c5874db3 | ||
|
|
5a13c4384f | ||
|
|
571b406bd6 | ||
|
|
83c91a3538 | ||
|
|
f5326c393a | ||
|
|
7c8b71012c | ||
|
|
1a286fc906 | ||
|
|
f7e97f7e9b | ||
|
|
025a508de2 | ||
|
|
6e231a8cae | ||
|
|
70e8db8a0a | ||
|
|
4f32a1cf9d | ||
|
|
795107dfa2 | ||
|
|
bab3404b2d | ||
|
|
2df750a9c9 | ||
|
|
790dbfe9a8 | ||
|
|
667a121ddb | ||
|
|
9dc0514844 | ||
|
|
a2ea191d84 | ||
|
|
fd7795da59 | ||
|
|
4b30d67e54 | ||
|
|
8138fbf349 | ||
|
|
562efc1677 | ||
|
|
b8f0391934 | ||
|
|
44825d91af | ||
|
|
d0ad57b64d | ||
|
|
3ec3aaf960 | ||
|
|
e381100812 | ||
|
|
934bd43e35 | ||
|
|
3c862836f2 | ||
|
|
c73378744f | ||
|
|
21364b7cd1 | ||
|
|
7ecfe195f7 | ||
|
|
c0248dea8c | ||
|
|
37d0558873 | ||
|
|
9bd10b3b5e | ||
|
|
e2f2761a00 | ||
|
|
9f90b2a1bf | ||
|
|
5cfa36f36e | ||
|
|
4913e90e10 | ||
|
|
3fac8cd77e | ||
|
|
32a333f16a | ||
|
|
7e1fac8f76 | ||
|
|
7eb3a70649 | ||
|
|
954e2c9bf0 | ||
|
|
27dcd60c77 | ||
|
|
c16cdb9d85 | ||
|
|
a8091e41a0 | ||
|
|
50b52367a2 | ||
|
|
abff77cf04 | ||
|
|
65d0f92cfe | ||
|
|
4b59490330 | ||
|
|
d0db8914f7 | ||
|
|
e2b203ba8d | ||
|
|
366b364996 | ||
|
|
7523550066 | ||
|
|
1bebe6097b | ||
|
|
4f8a18bbad | ||
|
|
3a9cf91f83 | ||
|
|
ed75219215 | ||
|
|
dfce3a3138 | ||
|
|
5655f786f4 | ||
|
|
5844b956be | ||
|
|
48f1fab49f | ||
|
|
fbc67d9078 | ||
|
|
017ed05911 | ||
|
|
ad4940a1d6 | ||
|
|
f9d1ca8373 | ||
|
|
bd00e7c584 | ||
|
|
78d2240d71 | ||
|
|
4a65671f64 | ||
|
|
cc57432be4 | ||
|
|
0af289ed78 | ||
|
|
2efc759a74 | ||
|
|
128b2623cc | ||
|
|
6fde339164 | ||
|
|
39924ee89c | ||
|
|
c63a5f26ca | ||
|
|
c149420d23 | ||
|
|
1378562e63 | ||
|
|
e48a0fe022 | ||
|
|
624c5e78db | ||
|
|
73b6400129 | ||
|
|
dbcc41e432 | ||
|
|
21a5d6e137 | ||
|
|
870cb3ccc4 | ||
|
|
e50bbc0245 | ||
|
|
c1f95ac173 | ||
|
|
5ba2f72324 | ||
|
|
be0ba4d5a2 | ||
|
|
6bfa2cfaec | ||
|
|
80620a5e94 | ||
|
|
3c43fb8081 | ||
|
|
9febb10bd7 | ||
|
|
321b1a90a2 | ||
|
|
37ecc61d06 | ||
|
|
25a358cd20 | ||
|
|
3cbc35143b | ||
|
|
5d69e732d8 | ||
|
|
9a8724cdd0 | ||
|
|
ca48501f91 | ||
|
|
e7d2be842d | ||
|
|
97bf69ba7f | ||
|
|
9f9b5afedb | ||
|
|
2e9a0f5590 | ||
|
|
61f0a9e4da | ||
|
|
f8d2c69713 | ||
|
|
7c7982d9f3 | ||
|
|
c90ecac48e | ||
|
|
d62f33b0b4 | ||
|
|
32f5538e0d | ||
|
|
40e49613b1 | ||
|
|
ef0050662c | ||
|
|
e1d663c0ce | ||
|
|
dbcd1e2df6 | ||
|
|
c6ef26a457 | ||
|
|
dd5c5eb931 | ||
|
|
eb7464ace6 | ||
|
|
f2c4a66e45 | ||
|
|
06913cc8b8 | ||
|
|
bce3d98d9d | ||
|
|
7a2a1b5446 | ||
|
|
b8e95f40a6 | ||
|
|
b67b903902 | ||
|
|
810d7ee1c0 | ||
|
|
198dc7d3bd | ||
|
|
a25757a66d | ||
|
|
69a71db25b | ||
|
|
c34430c6c4 | ||
|
|
369b99c712 | ||
|
|
1d10e463a3 | ||
|
|
8f7e820a01 | ||
|
|
e8ae2d6ec2 | ||
|
|
d8a94c3936 | ||
|
|
16c38315f7 | ||
|
|
9ee6829ebc | ||
|
|
4295d65115 | ||
|
|
cb8f8a24b0 | ||
|
|
01a2a07ecd | ||
|
|
9438289fc1 | ||
|
|
4e3784a2a9 | ||
|
|
3685f5031d | ||
|
|
05812a0a60 | ||
|
|
b309c61de3 | ||
|
|
fc1a7471cf | ||
|
|
9b21ac877a | ||
|
|
2811af349a | ||
|
|
889a54e946 | ||
|
|
9fcec10737 | ||
|
|
8e0d458d1d | ||
|
|
b31edc0be3 | ||
|
|
5b8ed7367a | ||
|
|
6ac79e3ed6 | ||
|
|
15d2522f3d | ||
|
|
b4515cf695 | ||
|
|
9ac4fc9034 | ||
|
|
0e18b094d1 | ||
|
|
c65c9d876e | ||
|
|
e84ead5291 | ||
|
|
440af2c81c | ||
|
|
b8aecbd56c | ||
|
|
3e739b87da | ||
|
|
0ad4296aaf | ||
|
|
9a17efc480 | ||
|
|
fbd640fdc5 | ||
|
|
b025efe729 | ||
|
|
a6f4c8e567 | ||
|
|
72355a9500 | ||
|
|
ff5747bb60 | ||
|
|
b14290b9f6 | ||
|
|
5571144c0e | ||
|
|
b4019bb438 | ||
|
|
801f629fdc | ||
|
|
ab858f320d | ||
|
|
280f3f38d7 | ||
|
|
b43dd92766 | ||
|
|
76b2e88551 | ||
|
|
bf9cd1b8e6 | ||
|
|
580d28d071 | ||
|
|
249088b4f8 | ||
|
|
f361d8ad43 | ||
|
|
724e668a94 | ||
|
|
3b7cb6722c | ||
|
|
be1f35c516 | ||
|
|
4a39665804 | ||
|
|
3cd6c78044 | ||
|
|
455a1062ef | ||
|
|
be635c69e7 | ||
|
|
6dc3caa8b2 | ||
|
|
539cde8d7a | ||
|
|
bc69d6da81 | ||
|
|
38a9e32a28 | ||
|
|
7a34078f5f | ||
|
|
552430db67 | ||
|
|
4a91ea817a | ||
|
|
e715974688 | ||
|
|
1e23b82e24 | ||
|
|
911bed827c | ||
|
|
eff0620ddf | ||
|
|
52d3a047a0 | ||
|
|
c5f72a633a | ||
|
|
1dc5f5531e | ||
|
|
6018dafc46 | ||
|
|
5ce4d8cfb0 | ||
|
|
ee66392e11 | ||
|
|
854be6a186 | ||
|
|
56b993bb89 | ||
|
|
7b65a35519 | ||
|
|
c37b5c2e87 | ||
|
|
8613e174e7 | ||
|
|
e90f5fde4e | ||
|
|
ca72fd4266 | ||
|
|
03c754ab14 | ||
|
|
b4cc92b9fa | ||
|
|
0a9f1c466a | ||
|
|
c2c321c90b | ||
|
|
1fcfdf4718 | ||
|
|
6c53b14ee4 | ||
|
|
4402dfa6f3 | ||
|
|
6095427926 | ||
|
|
f14c0e2183 | ||
|
|
97c701cdac | ||
|
|
d549393a84 | ||
|
|
5b82f09308 | ||
|
|
fbea32a81c | ||
|
|
23119e3673 | ||
|
|
e26022a2f2 | ||
|
|
17eb50da6d | ||
|
|
29b0e807d4 | ||
|
|
1875825f45 | ||
|
|
7f70da97b4 | ||
|
|
6e280ab8cb | ||
|
|
2f4fa41ce4 | ||
|
|
e58c32bee8 | ||
|
|
13b7316807 | ||
|
|
0d08c1819b | ||
|
|
310212ed30 | ||
|
|
aa8b6afe8b | ||
|
|
ad0a15debe | ||
|
|
b7b017c3fa | ||
|
|
57e6a7becd | ||
|
|
7c31134a66 | ||
|
|
a547645e86 | ||
|
|
42ae8347df | ||
|
|
249a48d68d | ||
|
|
8cbb12aeb6 | ||
|
|
f010354201 | ||
|
|
e0678d3a38 | ||
|
|
a587e11780 | ||
|
|
7cc745969c | ||
|
|
31e0fc7f17 | ||
|
|
70ec433e67 | ||
|
|
c64308a5e7 | ||
|
|
36ba2eb5d6 | ||
|
|
80b169aa75 | ||
|
|
97211f35e7 | ||
|
|
1dc63071ed | ||
|
|
d998d97754 | ||
|
|
63af442e3e | ||
|
|
73e32dfe5d | ||
|
|
0d01348acc | ||
|
|
79ce094e3a | ||
|
|
7d81aee62f | ||
|
|
00acd32120 | ||
|
|
bf541a1fed | ||
|
|
a16dda0885 | ||
|
|
d6d7880507 | ||
|
|
5eb3bc52ef | ||
|
|
be2f46ca68 | ||
|
|
1f8ef2a63c | ||
|
|
901716adc8 | ||
|
|
ce676a7ca7 | ||
|
|
2c13be1fa9 | ||
|
|
21da24e372 | ||
|
|
2d7bfbb805 | ||
|
|
92b980b10e | ||
|
|
13b63d06ed | ||
|
|
8654537e55 | ||
|
|
d8ee9dd5f5 | ||
|
|
3fd182a8f5 | ||
|
|
6cab5668e3 | ||
|
|
25cddfe446 | ||
|
|
0d6376f3e6 | ||
|
|
6185f45815 | ||
|
|
66824fd17e | ||
|
|
d2172b4383 | ||
|
|
095391d702 | ||
|
|
9332c21791 | ||
|
|
c2009af1c6 | ||
|
|
722e94513c | ||
|
|
a3b843b24e | ||
|
|
9d435c8f4d | ||
|
|
70c032868a | ||
|
|
1ab8efba7f | ||
|
|
bb3feedc31 | ||
|
|
70ace8c76f | ||
|
|
45c91b2ae9 | ||
|
|
7a695a4a2f | ||
|
|
e88cf466fe | ||
|
|
2655f3f816 | ||
|
|
69ea88473d | ||
|
|
f8fa73fa4a | ||
|
|
2eca094170 | ||
|
|
edbc11477c | ||
|
|
5021c9605b | ||
|
|
13885a36ec | ||
|
|
7198607420 | ||
|
|
6413d5dcba | ||
|
|
6719afadec | ||
|
|
073681a50a | ||
|
|
d44bf38906 | ||
|
|
09c8563e71 | ||
|
|
a07c48bb30 | ||
|
|
c0f453f83e | ||
|
|
5574e376d6 | ||
|
|
ba6ae12635 | ||
|
|
1180a6d83f | ||
|
|
8851dc7f23 | ||
|
|
32b8eb489c | ||
|
|
43f4806142 | ||
|
|
6eb1fa36ed | ||
|
|
dd17682fac | ||
|
|
df5c2a6f11 | ||
|
|
cef1494852 | ||
|
|
13d1393ae4 | ||
|
|
64144d007c | ||
|
|
4083b33807 | ||
|
|
a68fed3938 | ||
|
|
cc83e9f06d | ||
|
|
f2fa8cb63f | ||
|
|
d6feec808c | ||
|
|
32eff40422 | ||
|
|
d35226b889 | ||
|
|
102785d154 | ||
|
|
0d822f0ec2 | ||
|
|
73eb11b395 | ||
|
|
3ae6f2630e | ||
|
|
bb3bf28e59 | ||
|
|
97b5d76d50 | ||
|
|
8b6b192dac | ||
|
|
07cd25d0ec | ||
|
|
fbcf5319ea | ||
|
|
98417f77f0 | ||
|
|
2e88b088e6 | ||
|
|
84eb37e218 | ||
|
|
62e418d0fc | ||
|
|
059b00a829 | ||
|
|
2d3fdf920b | ||
|
|
8605969dc5 | ||
|
|
e2b1b52679 | ||
|
|
d4ba0f17bb | ||
|
|
e61deb3673 | ||
|
|
fe4f79ba90 | ||
|
|
9ad5d9bb3d | ||
|
|
7652abdf8d | ||
|
|
1d204af6f4 | ||
|
|
3dedae3928 | ||
|
|
b0b9c17c23 | ||
|
|
d5a2714db2 | ||
|
|
b1f1893481 | ||
|
|
8a5a0b6726 | ||
|
|
9abcd5816c | ||
|
|
c264969962 | ||
|
|
a10a554e2a | ||
|
|
0a1f5992ad | ||
|
|
43f57ba2cb | ||
|
|
38d8b1d268 | ||
|
|
a5c0ffe963 | ||
|
|
1bc83e658b | ||
|
|
8cfd2e33d8 | ||
|
|
8791f5a493 | ||
|
|
c9cdae1a96 | ||
|
|
229039d3b8 | ||
|
|
7b980c1dc9 | ||
|
|
fc5437f6d3 | ||
|
|
ba4325411b | ||
|
|
3d500ca317 | ||
|
|
b782351fd3 | ||
|
|
428f831886 | ||
|
|
f2818ddbe0 | ||
|
|
3648bbd6d4 | ||
|
|
90afbc8bd9 | ||
|
|
9f0283f808 | ||
|
|
f11196525b | ||
|
|
4696b46475 | ||
|
|
b646b5c98a | ||
|
|
e9d08ce51f | ||
|
|
e6c022a61c | ||
|
|
357f10732a | ||
|
|
2bbb6c570b | ||
|
|
bfbcbd55d8 | ||
|
|
cf4119e169 | ||
|
|
d781371d66 | ||
|
|
ce1d368037 | ||
|
|
35da87a90a | ||
|
|
89f0c60fc8 | ||
|
|
b77d35f6f1 | ||
|
|
e749f6040f | ||
|
|
aa69ae11a8 | ||
|
|
a616d64971 | ||
|
|
e0a3d2577c | ||
|
|
18e815d032 | ||
|
|
306bc02e29 | ||
|
|
24e8307e68 | ||
|
|
56853319d1 | ||
|
|
2a7f1a8c19 | ||
|
|
ebf17d14f0 | ||
|
|
b518d5d32f | ||
|
|
bf02062a67 | ||
|
|
d1243397fa | ||
|
|
9a7e5327ab | ||
|
|
aa5bafb8be | ||
|
|
9c08dfb50c | ||
|
|
88b62a9923 | ||
|
|
5b074540e6 | ||
|
|
04aa240265 | ||
|
|
3ee0fc5b1c | ||
|
|
d46d5c955b | ||
|
|
9c8f8f8ded | ||
|
|
4b38cffc60 | ||
|
|
4f2e172561 | ||
|
|
90811f6736 | ||
|
|
28d7b0dba6 | ||
|
|
40ce69ce5c | ||
|
|
bb9ceba343 | ||
|
|
02d717b7a3 | ||
|
|
fb08588007 | ||
|
|
0a62d658d9 | ||
|
|
4c7d9dfef5 | ||
|
|
64c9e3af4b | ||
|
|
766b301f78 | ||
|
|
9ec7b43ca1 | ||
|
|
9f4f711017 | ||
|
|
24daa50bfd | ||
|
|
79cb350f2d | ||
|
|
2f255620c6 | ||
|
|
8b11adb883 | ||
|
|
fc47892474 | ||
|
|
c908d4d96e | ||
|
|
406d045ced | ||
|
|
af92e3d49e | ||
|
|
9b9ed91e6e | ||
|
|
d5cab6221d | ||
|
|
a4235f5f44 | ||
|
|
06aaf83cfe | ||
|
|
b085710a4b | ||
|
|
82ec03fc23 | ||
|
|
6962b8dddd | ||
|
|
4688ae2fb6 | ||
|
|
1b766f12ca | ||
|
|
21a660c56c | ||
|
|
0de1c235a9 | ||
|
|
49817aac34 | ||
|
|
d4ae734659 | ||
|
|
39172d5a08 | ||
|
|
8f4cbefd0d | ||
|
|
5e100abe25 | ||
|
|
713fcb5e8e | ||
|
|
f0de57cbe4 | ||
|
|
2562070232 | ||
|
|
7e82159620 | ||
|
|
0f7933c4f9 | ||
|
|
9fd0637990 | ||
|
|
3332282767 | ||
|
|
47e4e8bb66 | ||
|
|
6776d6bc00 | ||
|
|
9507fb91f0 | ||
|
|
9af43bee52 | ||
|
|
3ab4d4b094 | ||
|
|
a6f3e6bfdb | ||
|
|
92e896c4d1 | ||
|
|
6c7c089fc4 | ||
|
|
0775960b9f | ||
|
|
cbc0e270b7 | ||
|
|
16939b80e6 | ||
|
|
af1b705563 | ||
|
|
6438bfc3cb | ||
|
|
84c30c1cf5 | ||
|
|
694268d6c7 | ||
|
|
510b472b51 | ||
|
|
504cbd89db | ||
|
|
b4a6d0acee | ||
|
|
2c0d216c1a | ||
|
|
3b09377a43 | ||
|
|
d46d9079a3 | ||
|
|
73d45dd851 | ||
|
|
d8be67c28b | ||
|
|
69da8b0999 | ||
|
|
93687c1491 | ||
|
|
c06ce3b58c | ||
|
|
29d64107de | ||
|
|
1eac452d71 | ||
|
|
fffdbe0abb | ||
|
|
7fa27c958a | ||
|
|
77d2a8aa8c | ||
|
|
c4832c5342 | ||
|
|
494efe65b2 | ||
|
|
4b7cec28b9 | ||
|
|
9d5f55af77 | ||
|
|
1c0437bc7b | ||
|
|
67d7658c59 | ||
|
|
d38503bf44 | ||
|
|
200130bc10 | ||
|
|
6896e53078 | ||
|
|
13b4237fec | ||
|
|
242dc17680 | ||
|
|
de2fd9721c | ||
|
|
0b77fe743c | ||
|
|
e8dc981774 | ||
|
|
c43049e13b | ||
|
|
b468e68c59 | ||
|
|
5d4d79191a | ||
|
|
959eb60ad0 | ||
|
|
c1e44eb591 | ||
|
|
fbb3462f5b | ||
|
|
efe7d5f857 | ||
|
|
eba19aaba4 | ||
|
|
b126f92f41 | ||
|
|
8b438dc0de | ||
|
|
9803e2fabf | ||
|
|
53e8699765 | ||
|
|
bbb47e087a | ||
|
|
b5b0df2426 | ||
|
|
005f138ce7 | ||
|
|
5dc635b0b1 | ||
|
|
6ac82f1a20 | ||
|
|
5d9ff677c0 | ||
|
|
ce395dfba8 | ||
|
|
4c3fa999f5 | ||
|
|
617fbaa9bd | ||
|
|
dbbc6e7e55 | ||
|
|
6c2a28166a | ||
|
|
cc36aff66a | ||
|
|
70601eeb51 | ||
|
|
5a0f4eac8d | ||
|
|
70f454c693 | ||
|
|
ffff597bfe | ||
|
|
5639132dae | ||
|
|
33f7b08c80 | ||
|
|
19548fe301 | ||
|
|
9dd228df01 | ||
|
|
4848cb7606 | ||
|
|
ea40c4d1b0 | ||
|
|
5d3cd792eb | ||
|
|
350d634433 | ||
|
|
9cb6dc768f | ||
|
|
91bc4d8157 | ||
|
|
c63d5da5c2 | ||
|
|
d47e12f05c | ||
|
|
af446dc7d4 | ||
|
|
25bc8e6f29 | ||
|
|
092b1724ba | ||
|
|
473802ce8c | ||
|
|
a1a88baa68 | ||
|
|
6a939eb250 | ||
|
|
bb907fb405 | ||
|
|
faed500520 | ||
|
|
0ded18207b | ||
|
|
e49cf2c55f | ||
|
|
64061267c8 | ||
|
|
93507bfd49 | ||
|
|
540c2b8705 | ||
|
|
45cc204f74 | ||
|
|
1b582ea66a | ||
|
|
bdce1ded7e | ||
|
|
5cd3ef7b8a | ||
|
|
74f25ef52f | ||
|
|
1485659726 | ||
|
|
583b009d1b | ||
|
|
4fc955a0fd | ||
|
|
7253dc699a | ||
|
|
c21a254480 | ||
|
|
5b51832b62 | ||
|
|
9ac60dca1a | ||
|
|
795c85d30e | ||
|
|
79ac29b435 | ||
|
|
632e3d3067 | ||
|
|
38ecce0ce9 | ||
|
|
34a49086e5 | ||
|
|
a0dda36df0 | ||
|
|
21ea100d0e | ||
|
|
9264db7ecd | ||
|
|
c00061678b | ||
|
|
e79aa2e81c | ||
|
|
d9cffeca4a | ||
|
|
99deffef62 | ||
|
|
a200498eef | ||
|
|
fcfb3b98bc | ||
|
|
a82f95e903 | ||
|
|
a34cd24fa1 | ||
|
|
9cd0a63331 | ||
|
|
74b3be3c06 | ||
|
|
2f2ae75529 | ||
|
|
ac2b6de62b | ||
|
|
16945dad70 | ||
|
|
d1f58cbed5 | ||
|
|
b0aa1260e2 | ||
|
|
e9166a8fe6 | ||
|
|
84760b8d59 | ||
|
|
ac495e974a | ||
|
|
133466a6b5 | ||
|
|
360dee862b | ||
|
|
050f9ff61a | ||
|
|
b28fb2ef17 | ||
|
|
93d9ee9205 | ||
|
|
e8ab9ac13a | ||
|
|
baf070a36d | ||
|
|
829bd7378e | ||
|
|
c8596c5c58 | ||
|
|
80c38b0113 | ||
|
|
d350241da3 | ||
|
|
9a9ba02d85 | ||
|
|
f7026c41c5 | ||
|
|
2d243a39ff | ||
|
|
807d042d11 | ||
|
|
6ce651eb4a | ||
|
|
95f7bcb9fe | ||
|
|
c9e896c669 | ||
|
|
04bcb410a9 | ||
|
|
98c67007d5 | ||
|
|
eb23990d6d | ||
|
|
c7f5753a28 | ||
|
|
1fda9e3d50 | ||
|
|
076d82d8d6 | ||
|
|
e4dd5cf82f | ||
|
|
0d3c4a160f | ||
|
|
80adf9e5d0 | ||
|
|
e8f242ee3f | ||
|
|
98cb9f0e18 | ||
|
|
7a7d00c8d6 | ||
|
|
23848492ce | ||
|
|
11dd67ab34 | ||
|
|
43cbad8867 | ||
|
|
ff9b53260d | ||
|
|
5a382f0200 | ||
|
|
945567d1f3 | ||
|
|
55888ace75 | ||
|
|
634bc09e2c | ||
|
|
5deadc8f12 | ||
|
|
4925b056c2 | ||
|
|
60a76b8cfa | ||
|
|
a1193d28bc | ||
|
|
2c64c3dd5b | ||
|
|
c1e26a70f8 | ||
|
|
3605289bbc | ||
|
|
399fc519e6 | ||
|
|
6010297465 | ||
|
|
989ec5cf4d | ||
|
|
049859e5b1 | ||
|
|
bf0eb1af0c | ||
|
|
28466750e6 | ||
|
|
379cde30e2 | ||
|
|
36825e0134 | ||
|
|
3a12cc5dbf | ||
|
|
33fe53fd7c | ||
|
|
f8986d0ef5 | ||
|
|
875035c09e | ||
|
|
a5df9fb795 | ||
|
|
334086d605 | ||
|
|
a5787cfb04 | ||
|
|
9a4e8f39af | ||
|
|
e5cce32302 | ||
|
|
33cdee1ad6 | ||
|
|
8b6a8e6307 | ||
|
|
6135a9fb8b | ||
|
|
ee3fb7caa2 | ||
|
|
b69caff93c | ||
|
|
948793e570 | ||
|
|
bad0bd8520 | ||
|
|
3e4c572164 | ||
|
|
6b4015f9ac | ||
|
|
eab09866cb | ||
|
|
b8ef6c6c26 | ||
|
|
c97cac5bc9 | ||
|
|
0a351fe47d | ||
|
|
c1d2c4e457 | ||
|
|
9c93e52c8f | ||
|
|
a78be203aa | ||
|
|
67f1cdf76f | ||
|
|
c3d401fb41 | ||
|
|
09fd5b4af4 | ||
|
|
16fce2facb | ||
|
|
d645dabcff | ||
|
|
12ba7201c0 | ||
|
|
59f0c64e1c | ||
|
|
66b5f45f45 | ||
|
|
b6bd527bdc | ||
|
|
f07c4ecadb | ||
|
|
bf3f45604a | ||
|
|
fb2d84b5d3 | ||
|
|
8ad200a352 | ||
|
|
9372abb51e | ||
|
|
06385221a3 | ||
|
|
30b0869595 | ||
|
|
11bd1e68e2 | ||
|
|
a4eef93950 | ||
|
|
d452a16ba8 | ||
|
|
01133fd0be | ||
|
|
268de5c8e5 | ||
|
|
a38ec14cb1 | ||
|
|
ed17c22889 | ||
|
|
366e10d6ec | ||
|
|
e29569a2cd | ||
|
|
36c1a9d20c | ||
|
|
7f57f007cd | ||
|
|
c2e710d092 | ||
|
|
71ec90a1dd | ||
|
|
f82e92f498 | ||
|
|
d34fd844a4 | ||
|
|
167df0ab87 | ||
|
|
33edda0a69 | ||
|
|
106817d13a | ||
|
|
f8967418b9 | ||
|
|
e521475b7e | ||
|
|
4b013d7a48 | ||
|
|
e5aed3a63e | ||
|
|
6b8f03ff1c | ||
|
|
39b41fda12 | ||
|
|
d0a81dca28 | ||
|
|
0d2dcbc85b | ||
|
|
6573ae002a | ||
|
|
24d67c42c6 | ||
|
|
b9f4880b23 |
22
.github/ISSUE_TEMPLATE.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
### Version
|
||||
_Version of Slic3r used goes here_
|
||||
|
||||
_Use `About->About Slic3r` for release versions_
|
||||
|
||||
_For -dev versions, use `git describe --tag` or get the hash value for the version you downloaded or `git rev-parse HEAD`_
|
||||
|
||||
### Operating system type + version
|
||||
_What OS are you using, and state any version #s_
|
||||
|
||||
### Behavior
|
||||
* _Describe the problem_
|
||||
* _Steps needed to reproduce the problem_
|
||||
* _If this is a command-line slicing issue, include the options used_
|
||||
* _Expected Results_
|
||||
* _Actual Results_
|
||||
* _Screenshots from __*Slic3r*__ preview are preferred_
|
||||
|
||||
_Is this a new feature request?_
|
||||
|
||||
#### STL/Config (.ZIP) where problem occurs
|
||||
_Upload a zipped copy of an STL and your config (`File -> Export Config`)_
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,3 +8,6 @@ xs/buildtmp
|
||||
*.o
|
||||
MANIFEST.bak
|
||||
xs/MANIFEST.bak
|
||||
xs/assertlib*
|
||||
.init_bundle.ini
|
||||
local-lib
|
||||
|
||||
12
.travis.yml
12
.travis.yml
@@ -1,12 +0,0 @@
|
||||
language: perl
|
||||
install: true
|
||||
script: perl ./Build.PL
|
||||
perl:
|
||||
- "5.12"
|
||||
- "5.14"
|
||||
- "5.18"
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- stable
|
||||
|
||||
96
Build.PL
96
Build.PL
@@ -7,42 +7,47 @@ use Config;
|
||||
use File::Spec;
|
||||
|
||||
my %prereqs = qw(
|
||||
Encode::Locale 0
|
||||
Devel::CheckLib 0
|
||||
ExtUtils::MakeMaker 6.80
|
||||
ExtUtils::ParseXS 3.22
|
||||
ExtUtils::XSpp 0
|
||||
ExtUtils::Typemaps 0
|
||||
ExtUtils::Typemaps::Basic 0
|
||||
File::Basename 0
|
||||
File::Spec 0
|
||||
Getopt::Long 0
|
||||
Math::PlanePath 53
|
||||
Module::Build::WithXSpp 0.14
|
||||
Moo 1.003001
|
||||
POSIX 0
|
||||
Scalar::Util 0
|
||||
Test::Harness 0
|
||||
Test::More 0
|
||||
Thread::Semaphore 0
|
||||
IO::Scalar 0
|
||||
threads 1.96
|
||||
Time::HiRes 0
|
||||
);
|
||||
my %recommends = qw(
|
||||
Class::XSAccessor 0
|
||||
XML::SAX::ExpatXS 0
|
||||
Test::Harness 0
|
||||
);
|
||||
|
||||
my $sudo = grep { $_ eq '--sudo' } @ARGV;
|
||||
my $gui = grep { $_ eq '--gui' } @ARGV;
|
||||
my $xs_only = grep { $_ eq '--xs' } @ARGV;
|
||||
my $nolocal = grep { $_ eq '--nolocal' } @ARGV;
|
||||
if ($gui) {
|
||||
%prereqs = qw(
|
||||
Class::Accessor 0
|
||||
Wx 0.9918
|
||||
Socket 2.016
|
||||
);
|
||||
%recommends = qw(
|
||||
Growl::GNTP 0.15
|
||||
Wx::GLCanvas 0
|
||||
OpenGL 0
|
||||
);
|
||||
} elsif ($xs_only) {
|
||||
%prereqs = %recommends = ();
|
||||
if ($^O eq 'MSWin32') {
|
||||
$recommends{"Win32::TieRegistry"} = 0;
|
||||
# we need an up-to-date Win32::API because older aren't thread-safe (GH #2517)
|
||||
$prereqs{'Win32::API'} = 0.79;
|
||||
}
|
||||
}
|
||||
|
||||
my @missing_prereqs = ();
|
||||
@@ -95,27 +100,29 @@ EOF
|
||||
my @cpanm_args = ();
|
||||
push @cpanm_args, "--sudo" if $sudo;
|
||||
|
||||
# install local::lib without --local-lib otherwise it's not usable afterwards
|
||||
if (!eval "use local::lib qw(local-lib); 1") {
|
||||
my $res = system $cpanm, @cpanm_args, 'local::lib';
|
||||
warn "Warning: local::lib is required. You might need to run the `cpanm --sudo local::lib` command in order to install it.\n"
|
||||
if $res != 0;
|
||||
}
|
||||
|
||||
push @cpanm_args, ('--local-lib', 'local-lib') if ! $nolocal;
|
||||
|
||||
# make sure our cpanm is updated (old ones don't support the ~ syntax)
|
||||
system $cpanm, @cpanm_args, 'App::cpanminus';
|
||||
|
||||
# install the Windows-compatible Math::Libm
|
||||
if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") {
|
||||
system $cpanm, @cpanm_args, 'https://github.com/alexrj/Math-Libm/tarball/master';
|
||||
}
|
||||
|
||||
my %modules = (%prereqs, %recommends);
|
||||
foreach my $module (sort keys %modules) {
|
||||
my $version = $modules{$module};
|
||||
my @cmd = ($cpanm, @cpanm_args, "$module~$version");
|
||||
if ($module eq 'XML::SAX::ExpatXS' && $^O eq 'MSWin32') {
|
||||
my $mingw = 'C:\dev\CitrusPerl\mingw64';
|
||||
$mingw = 'C:\dev\CitrusPerl\mingw32' if !-d $mingw;
|
||||
if (!-d $mingw) {
|
||||
print "Could not find the MinGW directory at $mingw; skipping XML::SAX::ExpatXS (only needed for faster parsing of AMF files)\n";
|
||||
} else {
|
||||
push @cmd, sprintf('--configure-args="EXPATLIBPATH=%s\lib EXPATINCPATH=%s\include"', $mingw, $mingw);
|
||||
}
|
||||
}
|
||||
my @cmd = ($cpanm, @cpanm_args);
|
||||
|
||||
# temporary workaround for upstream bug in test
|
||||
push @cmd, '--notest'
|
||||
if $module =~ /^(?:OpenGL|Test::Harness)$/;
|
||||
|
||||
push @cmd, "$module~$version";
|
||||
|
||||
my $res = system @cmd;
|
||||
if ($res != 0) {
|
||||
if (exists $prereqs{$module}) {
|
||||
@@ -125,36 +132,19 @@ EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gui) {
|
||||
# clean xs directory before reinstalling, to make sure Build is called
|
||||
# with current perl binary
|
||||
if (-e './xs/Build') {
|
||||
if ($^O eq 'MSWin32') {
|
||||
system '.\xs\Build', 'distclean';
|
||||
} else {
|
||||
system './xs/Build', 'distclean';
|
||||
}
|
||||
}
|
||||
my $res = system $cpanm, @cpanm_args, '--reinstall', '--verbose', './xs';
|
||||
if ($res != 0) {
|
||||
die "The XS/C++ code failed to compile, aborting\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@missing_prereqs) {
|
||||
printf "The following prerequisites failed to install: %s\n", join(', ', @missing_prereqs);
|
||||
exit 1;
|
||||
} elsif (!$gui) {
|
||||
eval "use App::Prove; 1" or die "Failed to load App::Prove";
|
||||
my $res = App::Prove->new->run ? 0 : 1;
|
||||
if ($res == 0) {
|
||||
print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n";
|
||||
} else {
|
||||
print "Some tests failed. Please report the failure to the author!\n";
|
||||
}
|
||||
exit $res;
|
||||
print "\n";
|
||||
if ($gui) {
|
||||
print "Perl dependencies for the Slic3r GUI were installed.\n";
|
||||
} else {
|
||||
print "Perl dependencies for Slic3r were installed.\n";
|
||||
print "If you also want to use the GUI you can now run `perl Build.PL --gui` to install the required modules.\n";
|
||||
}
|
||||
|
||||
print "\n";
|
||||
print "In the next step, you need to build the Slic3r C++ library.\n";
|
||||
print "1) Create a build directory and change to it\n";
|
||||
print "2) run cmake .. -DCMAKE_BUILD_TYPE=Release\n";
|
||||
print "3) run make\n";
|
||||
print "4) to execute the automatic tests, run ctest --verbose\n";
|
||||
__END__
|
||||
|
||||
91
CMakeLists.txt
Normal file
91
CMakeLists.txt
Normal file
@@ -0,0 +1,91 @@
|
||||
# Boost 1.63 requires CMake 3.7 or newer
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
project(Slic3r)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(STATUS "No build type selected, default to Release")
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE)
|
||||
endif()
|
||||
|
||||
if(DEFINED ENV{SLIC3R_STATIC})
|
||||
set(SLIC3R_STATIC_INITIAL $ENV{SLIC3R_STATIC})
|
||||
else()
|
||||
if (MSVC OR MINGW OR APPLE)
|
||||
set(SLIC3R_STATIC_INITIAL 1)
|
||||
else()
|
||||
set(SLIC3R_STATIC_INITIAL 0)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(SLIC3R_STATIC "Compile Slic3r with static libraries (Boost, TBB, glew)" ${SLIC3R_STATIC_INITIAL})
|
||||
option(SLIC3R_GUI "Compile Slic3r with GUI components (OpenGL, wxWidgets)" 1)
|
||||
option(SLIC3R_PRUSACONTROL "Compile Slic3r with the PrusaControl prject file format (requires wxWidgets base library)" 1)
|
||||
option(SLIC3R_PROFILE "Compile Slic3r with an invasive Shiny profiler" 0)
|
||||
option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
|
||||
|
||||
if (MSVC AND SLIC3R_MSVC_COMPILE_PARALLEL)
|
||||
add_compile_options(/MP)
|
||||
endif ()
|
||||
|
||||
# Find the Perl interpreter, add local-lib to PATH and PERL5LIB environment variables,
|
||||
# so the locally installed modules (mainly the Alien::wxPerl) will be reached.
|
||||
if (WIN32)
|
||||
set(ENV_PATH_SEPARATOR ";")
|
||||
else()
|
||||
set(ENV_PATH_SEPARATOR ":")
|
||||
endif()
|
||||
set(ENV{PATH} "${PROJECT_SOURCE_DIR}/local-lib/bin${ENV_PATH_SEPARATOR}$ENV{PATH}")
|
||||
set(ENV{PERL5LIB} "${PROJECT_SOURCE_DIR}/local-lib/lib/perl${ENV_PATH_SEPARATOR}$ENV{PERL5LIB}")
|
||||
message("PATH: $ENV{PATH}")
|
||||
message("PERL5LIB: $ENV{PERL5LIB}")
|
||||
find_package(Perl REQUIRED)
|
||||
|
||||
# CMAKE_PREFIX_PATH is used to point CMake to the remaining dependencies (Boost, TBB, ...)
|
||||
# We pick it from environment if it is not defined in another way
|
||||
if(NOT DEFINED CMAKE_PREFIX_PATH)
|
||||
if(DEFINED ENV{CMAKE_PREFIX_PATH})
|
||||
set(CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory.
|
||||
# We pick it from environment if it is not defined in another way
|
||||
if(WIN32)
|
||||
if(NOT DEFINED WIN10SDK_PATH)
|
||||
if(DEFINED ENV{WIN10SDK_PATH})
|
||||
set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}")
|
||||
endif()
|
||||
endif()
|
||||
if(DEFINED WIN10SDK_PATH AND NOT EXISTS "${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h")
|
||||
message("WIN10SDK_PATH is invalid: ${WIN10SDK_PATH}")
|
||||
message("${WIN10SDK_PATH}/include/winrt/windows.graphics.printing3d.h was not found")
|
||||
message("STL fixing by the Netfabb service will not be compiled")
|
||||
unset(WIN10SDK_PATH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_subdirectory(xs)
|
||||
|
||||
get_filename_component(PERL_BIN_PATH "${PERL_EXECUTABLE}" DIRECTORY)
|
||||
if (MSVC)
|
||||
# By default the startup project in MSVC is the 'ALL_BUILD' cmake-created project,
|
||||
# but we want 'slic3r' as the startup one because debugging run command is associated with it.
|
||||
# (Unfortunatelly it cannot be associated with ALL_BUILD using CMake.)
|
||||
# Note: For some reason this needs to be set in the top-level CMakeLists.txt
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT XS)
|
||||
set(PERL_PROVE "${PERL_BIN_PATH}/prove.bat")
|
||||
else ()
|
||||
set(PERL_PROVE "${PERL_BIN_PATH}/prove")
|
||||
endif ()
|
||||
|
||||
enable_testing ()
|
||||
add_test (NAME xs COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} -I ${PROJECT_SOURCE_DIR}/local-lib/lib/perl5 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/xs)
|
||||
add_test (NAME integration COMMAND "${PERL_EXECUTABLE}" ${PERL_PROVE} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
install(PROGRAMS slic3r.pl DESTINATION bin RENAME slic3r-prusa3d)
|
||||
|
||||
file(GLOB MyVar var/*.png)
|
||||
install(FILES ${MyVar} DESTINATION share/slic3r-prusa3d)
|
||||
install(FILES lib/Slic3r.pm DESTINATION lib/slic3r-prusa3d)
|
||||
install(DIRECTORY lib/Slic3r DESTINATION lib/slic3r-prusa3d)
|
||||
661
LICENSE
Normal file
661
LICENSE
Normal file
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
81
README.md
81
README.md
@@ -2,13 +2,16 @@ _Q: Oh cool, a new RepRap slicer?_
|
||||
|
||||
A: Yes.
|
||||
|
||||
Slic3r [](https://travis-ci.org/alexrj/Slic3r)
|
||||
Slic3r
|
||||
======
|
||||
Prebuilt Windows, OSX and Linux binaries are available through the [git releases page](https://github.com/prusa3d/Slic3r/releases).
|
||||
|
||||
<img width=256 src=https://cloud.githubusercontent.com/assets/31754/22719818/09998c92-ed6d-11e6-9fa0-09de638f3a36.png />
|
||||
|
||||
Slic3r takes 3D models (STL, OBJ, AMF) and converts them into G-code instructions for
|
||||
3D printers. It's compatible with any modern printer based on the RepRap toolchain,
|
||||
including all those based on the Marlin, Sprinter and Repetier firmware. It also works
|
||||
with Mach3 and LinuxCNC controllers.
|
||||
with Mach3, LinuxCNC and Machinekit controllers.
|
||||
|
||||
See the [project homepage](http://slic3r.org/) at slic3r.org and the
|
||||
[manual](http://manual.slic3r.org/) for more information.
|
||||
@@ -17,7 +20,7 @@ See the [project homepage](http://slic3r.org/) at slic3r.org and the
|
||||
|
||||
The core geometric algorithms and data structures are written in C++,
|
||||
and Perl is used for high-level flow abstraction, GUI and testing.
|
||||
If you're wondering why Perl, see http://xkcd.com/224/
|
||||
If you're wondering why Perl, see https://xkcd.com/224/
|
||||
|
||||
The C++ API is public and its use in other projects is encouraged.
|
||||
The goal is to make Slic3r fully modular so that any part of its logic
|
||||
@@ -30,7 +33,7 @@ Key features are:
|
||||
* **multi-platform** (Linux/Mac/Win) and packaged as standalone-app with no dependencies required
|
||||
* complete **command-line interface** to use it with no GUI
|
||||
* multi-material **(multiple extruders)** object printing
|
||||
* multiple G-code flavors supported (RepRap, Makerbot, Mach3 etc.)
|
||||
* multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)
|
||||
* ability to plate **multiple objects having distinct print settings**
|
||||
* **multithread** processing
|
||||
* **STL auto-repair** (tolerance for broken models)
|
||||
@@ -52,25 +55,26 @@ Other major features are:
|
||||
|
||||
### How to install?
|
||||
|
||||
You can just download a precompiled package from [slic3r.org](http://slic3r.org/);
|
||||
You can download a precompiled package from [slic3r.org](http://slic3r.org/);
|
||||
it will run without the need for any dependency.
|
||||
|
||||
If you want to compile the source yourself just do the following (checkout
|
||||
[slic3r.org](http://slic3r.org/download) for more details):
|
||||
|
||||
```
|
||||
$ git clone https://github.com/alexrj/Slic3r.git
|
||||
$ cd Slic3r
|
||||
$ sudo perl Build.PL
|
||||
$ sudo perl Build.PL --gui
|
||||
$ ./slic3r.pl
|
||||
```
|
||||
If you want to compile the source yourself follow the instructions on one of these wiki pages:
|
||||
* [Linux](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-GNU-Linux)
|
||||
* [Windows](https://github.com/prusa3d/Slic3r/wiki/How-to-compile-Slic3r-Prusa-Edition-on-MS-Windows)
|
||||
* [Mac OSX](https://github.com/alexrj/Slic3r/wiki/Running-Slic3r-from-git-on-OS-X)
|
||||
|
||||
### Can I help?
|
||||
|
||||
Sure! Drop me a line at aar@cpan.org. You can also
|
||||
find me in #reprap and in #slic3r on FreeNode with the nickname _Sound_.
|
||||
Before sending patches and pull requests contact me to discuss your proposed
|
||||
Sure! You can do the following to find things that are available to help with:
|
||||
* [Pull Request Milestone](https://github.com/alexrj/Slic3r/milestone/31)
|
||||
* Please comment in the related github issue that you are working on it so that other people know.
|
||||
* Items in the [TODO](https://github.com/alexrj/Slic3r/wiki/TODO) wiki page.
|
||||
* Please comment in the related github issue that you are working on it so that other people know.
|
||||
* Drop me a line at aar@cpan.org.
|
||||
* You can also find me (rarely) in #reprap and in #slic3r on [FreeNode](https://webchat.freenode.net) with the nickname _Sound_. Another contributor, _LoH_, is also in both channels.
|
||||
* Add an [issue](https://github.com/alexrj/Slic3r/issues) to the github tracker if it isn't already present.
|
||||
|
||||
Before sending patches and pull requests contact me (preferably through opening a github issue or commenting on an existing, related, issue) to discuss your proposed
|
||||
changes: this way we'll ensure nobody wastes their time and no conflicts arise
|
||||
in development.
|
||||
|
||||
@@ -109,8 +113,11 @@ The author of the Silk icon set is Mark James.
|
||||
-j, --threads <num> Number of threads to use (1+, default: 2)
|
||||
|
||||
GUI options:
|
||||
--gui Forces the GUI launch instead of command line slicing (if you
|
||||
supply a model file, it will be loaded into the plater)
|
||||
--no-plater Disable the plater tab
|
||||
--gui-mode Overrides the configured mode (simple/expert)
|
||||
--no-gui Forces the command line slicing instead of gui.
|
||||
This takes precedence over --gui if both are present.
|
||||
--autosave <file> Automatically export current configuration to the specified file
|
||||
|
||||
Output options:
|
||||
@@ -130,17 +137,12 @@ The author of the Silk icon set is Mark James.
|
||||
(default: 100,100)
|
||||
--z-offset Additional height in mm to add to vertical coordinates
|
||||
(+/-, default: 0)
|
||||
--gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion,
|
||||
--gcode-flavor The type of G-code to generate (reprap/teacup/repetier/makerware/sailfish/mach3/machinekit/smoothie/no-extrusion,
|
||||
default: reprap)
|
||||
--use-relative-e-distances Enable this to get relative E values (default: no)
|
||||
--use-firmware-retraction Enable firmware-controlled retraction using G10/G11 (default: no)
|
||||
--gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported
|
||||
by all firmwares)
|
||||
--g0 Use G0 commands for retraction (experimental, not supported by all
|
||||
firmwares)
|
||||
--use-volumetric-e Express E in cubic millimeters and prepend M200 (default: no)
|
||||
--gcode-comments Make G-code verbose by adding comments (default: no)
|
||||
--vibration-limit Limit the frequency of moves on X and Y axes (Hz, set zero to disable;
|
||||
default: 0)
|
||||
|
||||
Filament options:
|
||||
--filament-diameter Diameter in mm of your raw filament (default: 3)
|
||||
@@ -194,7 +196,7 @@ The author of the Silk icon set is Mark James.
|
||||
to disable; default: 0)
|
||||
--default-acceleration
|
||||
Acceleration will be reset to this value after the specific settings above
|
||||
have been applied. (mm/s^2, set zero to disable; default: 130)
|
||||
have been applied. (mm/s^2, set zero to disable; default: 0)
|
||||
|
||||
Accuracy options:
|
||||
--layer-height Layer height in mm (default: 0.3)
|
||||
@@ -218,7 +220,8 @@ The author of the Silk icon set is Mark James.
|
||||
--end-gcode Load final G-code from the supplied file. This will overwrite
|
||||
the default commands (turn off temperature [M104 S0],
|
||||
home X axis [G28 X], disable motors [M84]).
|
||||
--layer-gcode Load layer-change G-code from the supplied file (default: nothing).
|
||||
--before-layer-gcode Load before-layer-change G-code from the supplied file (default: nothing).
|
||||
--layer-gcode Load after-layer-change G-code from the supplied file (default: nothing).
|
||||
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
|
||||
--seam-position Position of loop starting points (random/nearest/aligned, default: aligned).
|
||||
--external-perimeters-first Reverse perimeter order. (default: no)
|
||||
@@ -252,6 +255,9 @@ The author of the Silk icon set is Mark James.
|
||||
Spacing between pattern lines (mm, default: 2.5)
|
||||
--support-material-angle
|
||||
Support material angle in degrees (range: 0-90, default: 0)
|
||||
--support-material-contact-distance
|
||||
Vertical distance between object and support material
|
||||
(0+, default: 0.2)
|
||||
--support-material-interface-layers
|
||||
Number of perpendicular layers between support material and object (0+, default: 3)
|
||||
--support-material-interface-spacing
|
||||
@@ -272,14 +278,16 @@ The author of the Silk icon set is Mark James.
|
||||
--retract-before-travel
|
||||
Only retract before travel moves of this length in mm (default: 2)
|
||||
--retract-lift Lift Z by the given distance in mm when retracting (default: 0)
|
||||
--retract-lift-above Only lift Z when above the specified height (default: 0)
|
||||
--retract-lift-below Only lift Z when below the specified height (default: 0)
|
||||
--retract-layer-change
|
||||
Enforce a retraction before each Z move (default: yes)
|
||||
Enforce a retraction before each Z move (default: no)
|
||||
--wipe Wipe the nozzle while doing a retraction (default: no)
|
||||
|
||||
Retraction options for multi-extruder setups:
|
||||
--retract-length-toolchange
|
||||
Length of retraction in mm when disabling tool (default: 1)
|
||||
--retract-restart-extra-toolchnage
|
||||
Length of retraction in mm when disabling tool (default: 10)
|
||||
--retract-restart-extra-toolchange
|
||||
Additional amount of filament in mm to push after
|
||||
switching tool (default: 0)
|
||||
|
||||
@@ -313,6 +321,9 @@ The author of the Silk icon set is Mark James.
|
||||
--duplicate Number of items with auto-arrange (1+, default: 1)
|
||||
--duplicate-grid Number of items with grid arrangement (default: 1,1)
|
||||
--duplicate-distance Distance in mm between copies (default: 6)
|
||||
--dont-arrange Don't arrange the objects on the build plate. The model coordinates
|
||||
define the absolute positions on the build plate.
|
||||
The option --print-center will be ignored.
|
||||
--xy-size-compensation
|
||||
Grow/shrink objects by the configured absolute distance (mm, default: 0)
|
||||
|
||||
@@ -345,16 +356,18 @@ The author of the Silk icon set is Mark James.
|
||||
Set a different extrusion width for top infill
|
||||
--support-material-extrusion-width
|
||||
Set a different extrusion width for support material
|
||||
--infill-overlap Overlap between infill and perimeters (default: 15%)
|
||||
--bridge-flow-ratio Multiplier for extrusion when bridging (> 0, default: 1)
|
||||
|
||||
Multiple extruder options:
|
||||
--extruder-offset Offset of each extruder, if firmware doesn't handle the displacement
|
||||
(can be specified multiple times, default: 0x0)
|
||||
--perimeter-extruder
|
||||
Extruder to use for perimeters (1+, default: 1)
|
||||
Extruder to use for perimeters and brim (1+, default: 1)
|
||||
--infill-extruder Extruder to use for infill (1+, default: 1)
|
||||
--solid-infill-extruder Extruder to use for solid infill (1+, default: 1)
|
||||
--support-material-extruder
|
||||
Extruder to use for support material (1+, default: 1)
|
||||
Extruder to use for support material, raft and skirt (1+, default: 1)
|
||||
--support-material-interface-extruder
|
||||
Extruder to use for support material interface (1+, default: 1)
|
||||
--ooze-prevention Drop temperature and park extruders outside a full skirt for automatic wiping
|
||||
@@ -372,4 +385,4 @@ If you want to change a preset file, just do
|
||||
|
||||
If you want to slice a file overriding an option contained in your preset file:
|
||||
|
||||
slic3r.pl --load config.ini --layer-height 0.25 file.stl
|
||||
slic3r.pl --load config.ini --layer-height 0.25 file.stl
|
||||
106
cmake/modules/FindAlienWx.cmake
Normal file
106
cmake/modules/FindAlienWx.cmake
Normal file
@@ -0,0 +1,106 @@
|
||||
# Find the wxWidgets module based on the information provided by the Perl Alien::wxWidgets module.
|
||||
|
||||
# Check for the Perl & PerlLib modules
|
||||
include(LibFindMacros)
|
||||
libfind_package(AlienWx Perl)
|
||||
libfind_package(AlienWx PerlLibs)
|
||||
|
||||
if (AlienWx_DEBUG)
|
||||
message(STATUS " AlienWx_FIND_COMPONENTS=${AlienWx_FIND_COMPONENTS}")
|
||||
endif()
|
||||
|
||||
# Execute an Alien::Wx module to find the relevant information regarding
|
||||
# the wxWidgets used by the Perl interpreter.
|
||||
# Perl specific stuff
|
||||
set(AlienWx_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/AlienWx_TEMP_INCLUDE.txt)
|
||||
execute_process(
|
||||
COMMAND ${PERL_EXECUTABLE} -e "
|
||||
# Import Perl modules.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Text::ParseWords;
|
||||
|
||||
BEGIN {
|
||||
# CMake sets the environment variables CC and CXX to the detected C compiler.
|
||||
# There is an issue with the Perl ExtUtils::CBuilder, which does not handle whitespaces
|
||||
# in the paths correctly on Windows, so we rather drop the CMake auto-detected paths.
|
||||
delete \$ENV{CC};
|
||||
delete \$ENV{CXX};
|
||||
}
|
||||
|
||||
use Alien::wxWidgets;
|
||||
use ExtUtils::CppGuess;
|
||||
|
||||
# Test for a Visual Studio compiler
|
||||
my \$cpp_guess = ExtUtils::CppGuess->new;
|
||||
my \$mswin = \$^O eq 'MSWin32';
|
||||
my \$msvc = \$cpp_guess->is_msvc;
|
||||
|
||||
# List of wxWidgets components to be used.
|
||||
my @components = split /;/, '${AlienWx_FIND_COMPONENTS}';
|
||||
|
||||
# Query the available data from Alien::wxWidgets.
|
||||
my \$version = Alien::wxWidgets->version;
|
||||
my \$config = Alien::wxWidgets->config;
|
||||
my \$compiler = Alien::wxWidgets->compiler;
|
||||
my \$linker = Alien::wxWidgets->linker;
|
||||
my \$include_path = ' ' . Alien::wxWidgets->include_path;
|
||||
my \$defines = ' ' . Alien::wxWidgets->defines;
|
||||
my \$cflags = Alien::wxWidgets->c_flags;
|
||||
my \$linkflags = Alien::wxWidgets->link_flags;
|
||||
my \$libraries = ' ' . Alien::wxWidgets->libraries(@components);
|
||||
#my @libraries = Alien::wxWidgets->link_libraries(@components);
|
||||
#my @implib = Alien::wxWidgets->import_libraries(@components);
|
||||
#my @shrlib = Alien::wxWidgets->shared_libraries(@components);
|
||||
#my @keys = Alien::wxWidgets->library_keys; # 'gl', 'adv', ...
|
||||
#my \$library_path = Alien::wxWidgets->shared_library_path;
|
||||
#my \$key = Alien::wxWidgets->key;
|
||||
#my \$prefix = Alien::wxWidgets->prefix;
|
||||
|
||||
my \$filename = '${AlienWx_TEMP_INCLUDE}';
|
||||
open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\";
|
||||
|
||||
# Convert a space separated lists to CMake semicolon separated lists,
|
||||
# escape the backslashes,
|
||||
# export the resulting list to a temp file.
|
||||
sub cmake_set_var {
|
||||
my (\$varname, \$content) = @_;
|
||||
# Remove line separators.
|
||||
\$content =~ s/\\r|\\n//g;
|
||||
# Escape the path separators.
|
||||
\$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g;
|
||||
my @words = shellwords(\$content);
|
||||
print \$fh \"set(AlienWx_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\";
|
||||
}
|
||||
cmake_set_var('VERSION', \$version);
|
||||
\$include_path =~ s/ -I/ /g;
|
||||
cmake_set_var('INCLUDE_DIRS', \$include_path);
|
||||
\$libraries =~ s/ -L/ -LIBPATH:/g if \$msvc;
|
||||
cmake_set_var('LIBRARIES', \$libraries);
|
||||
#cmake_set_var('LIBRARY_DIRS', );
|
||||
#\$defines =~ s/ -D/ /g;
|
||||
cmake_set_var('DEFINITIONS', \$defines);
|
||||
#cmake_set_var('DEFINITIONS_DEBUG', );
|
||||
cmake_set_var('CXX_FLAGS', \$cflags);
|
||||
close \$fh;
|
||||
")
|
||||
include(${AlienWx_TEMP_INCLUDE})
|
||||
file(REMOVE ${AlienWx_TEMP_INCLUDE})
|
||||
unset(AlienWx_TEMP_INCLUDE)
|
||||
|
||||
if (AlienWx_DEBUG)
|
||||
message(STATUS " AlienWx_VERSION = ${AlienWx_VERSION}")
|
||||
message(STATUS " AlienWx_INCLUDE_DIRS = ${AlienWx_INCLUDE_DIRS}")
|
||||
message(STATUS " AlienWx_LIBRARIES = ${AlienWx_LIBRARIES}")
|
||||
message(STATUS " AlienWx_LIBRARY_DIRS = ${AlienWx_LIBRARY_DIRS}")
|
||||
message(STATUS " AlienWx_DEFINITIONS = ${AlienWx_DEFINITIONS}")
|
||||
message(STATUS " AlienWx_DEFINITIONS_DEBUG = ${AlienWx_DEFINITIONS_DEBUG}")
|
||||
message(STATUS " AlienWx_CXX_FLAGS = ${AlienWx_CXX_FLAGS}")
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(AlienWx
|
||||
REQUIRED_VARS AlienWx_INCLUDE_DIRS AlienWx_LIBRARIES
|
||||
# HANDLE_COMPONENTS
|
||||
VERSION_VAR AlienWx_VERSION)
|
||||
59
cmake/modules/FindCURL.cmake
Normal file
59
cmake/modules/FindCURL.cmake
Normal file
@@ -0,0 +1,59 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#.rst:
|
||||
# FindCURL
|
||||
# --------
|
||||
#
|
||||
# Find curl
|
||||
#
|
||||
# Find the native CURL headers and libraries.
|
||||
#
|
||||
# ::
|
||||
#
|
||||
# CURL_INCLUDE_DIRS - where to find curl/curl.h, etc.
|
||||
# CURL_LIBRARIES - List of libraries when using curl.
|
||||
# CURL_FOUND - True if curl found.
|
||||
# CURL_VERSION_STRING - the version of curl found (since CMake 2.8.8)
|
||||
|
||||
# Look for the header file.
|
||||
find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
|
||||
mark_as_advanced(CURL_INCLUDE_DIR)
|
||||
|
||||
# Look for the library (sorted from most current/relevant entry to least).
|
||||
find_library(CURL_LIBRARY NAMES
|
||||
curl
|
||||
# Windows MSVC Makefile:
|
||||
libcurl_a
|
||||
# Windows MSVC prebuilts:
|
||||
curllib
|
||||
libcurl_imp
|
||||
curllib_static
|
||||
# Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
|
||||
libcurl
|
||||
)
|
||||
mark_as_advanced(CURL_LIBRARY)
|
||||
|
||||
if(CURL_INCLUDE_DIR)
|
||||
foreach(_curl_version_header curlver.h curl.h)
|
||||
if(EXISTS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}")
|
||||
file(STRINGS "${CURL_INCLUDE_DIR}/curl/${_curl_version_header}" curl_version_str REGEX "^#define[\t ]+LIBCURL_VERSION[\t ]+\".*\"")
|
||||
|
||||
string(REGEX REPLACE "^#define[\t ]+LIBCURL_VERSION[\t ]+\"([^\"]*)\".*" "\\1" CURL_VERSION_STRING "${curl_version_str}")
|
||||
unset(curl_version_str)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
find_package_handle_standard_args(CURL
|
||||
REQUIRED_VARS CURL_LIBRARY CURL_INCLUDE_DIR
|
||||
VERSION_VAR CURL_VERSION_STRING)
|
||||
|
||||
if(CURL_FOUND)
|
||||
set(CURL_LIBRARIES ${CURL_LIBRARY})
|
||||
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
|
||||
|
||||
message(STATUS " Curl libraries: = ${CURL_LIBRARIES}")
|
||||
message(STATUS " Curl include dirs: = ${CURL_INCLUDE_DIRS}")
|
||||
endif()
|
||||
86
cmake/modules/FindEigen3.cmake
Normal file
86
cmake/modules/FindEigen3.cmake
Normal file
@@ -0,0 +1,86 @@
|
||||
# - Try to find Eigen3 lib
|
||||
#
|
||||
# This module supports requiring a minimum version, e.g. you can do
|
||||
# find_package(Eigen3 3.1.2)
|
||||
# to require version 3.1.2 or newer of Eigen3.
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# EIGEN3_FOUND - system has eigen lib with correct version
|
||||
# EIGEN3_INCLUDE_DIR - the eigen include directory
|
||||
# EIGEN3_VERSION - eigen version
|
||||
|
||||
# Copyright (c) 2006, 2007 Montel Laurent, <montel@kde.org>
|
||||
# Copyright (c) 2008, 2009 Gael Guennebaud, <g.gael@free.fr>
|
||||
# Copyright (c) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
|
||||
# Redistribution and use is allowed according to the terms of the 2-clause BSD license.
|
||||
|
||||
if(NOT Eigen3_FIND_VERSION)
|
||||
if(NOT Eigen3_FIND_VERSION_MAJOR)
|
||||
set(Eigen3_FIND_VERSION_MAJOR 2)
|
||||
endif(NOT Eigen3_FIND_VERSION_MAJOR)
|
||||
if(NOT Eigen3_FIND_VERSION_MINOR)
|
||||
set(Eigen3_FIND_VERSION_MINOR 91)
|
||||
endif(NOT Eigen3_FIND_VERSION_MINOR)
|
||||
if(NOT Eigen3_FIND_VERSION_PATCH)
|
||||
set(Eigen3_FIND_VERSION_PATCH 0)
|
||||
endif(NOT Eigen3_FIND_VERSION_PATCH)
|
||||
|
||||
set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}")
|
||||
endif(NOT Eigen3_FIND_VERSION)
|
||||
|
||||
macro(_eigen3_check_version)
|
||||
file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header)
|
||||
|
||||
string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}")
|
||||
set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}")
|
||||
set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}")
|
||||
string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}")
|
||||
set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}")
|
||||
|
||||
set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION})
|
||||
if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
|
||||
set(EIGEN3_VERSION_OK FALSE)
|
||||
else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
|
||||
set(EIGEN3_VERSION_OK TRUE)
|
||||
endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION})
|
||||
|
||||
if(NOT EIGEN3_VERSION_OK)
|
||||
|
||||
message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, "
|
||||
"but at least version ${Eigen3_FIND_VERSION} is required")
|
||||
endif(NOT EIGEN3_VERSION_OK)
|
||||
endmacro(_eigen3_check_version)
|
||||
|
||||
if (EIGEN3_INCLUDE_DIR)
|
||||
|
||||
# in cache already
|
||||
_eigen3_check_version()
|
||||
set(EIGEN3_FOUND ${EIGEN3_VERSION_OK})
|
||||
|
||||
else (EIGEN3_INCLUDE_DIR)
|
||||
|
||||
# specific additional paths for some OS
|
||||
if (WIN32)
|
||||
set(EIGEN_ADDITIONAL_SEARCH_PATHS ${EIGEN_ADDITIONAL_SEARCH_PATHS} "C:/Program Files/Eigen/include" "C:/Program Files (x86)/Eigen/include")
|
||||
endif(WIN32)
|
||||
|
||||
find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library
|
||||
PATHS
|
||||
${CMAKE_INSTALL_PREFIX}/include
|
||||
${EIGEN_ADDITIONAL_SEARCH_PATHS}
|
||||
${KDE4_INCLUDE_DIR}
|
||||
PATH_SUFFIXES eigen3 eigen
|
||||
)
|
||||
|
||||
if(EIGEN3_INCLUDE_DIR)
|
||||
_eigen3_check_version()
|
||||
endif(EIGEN3_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK)
|
||||
|
||||
mark_as_advanced(EIGEN3_INCLUDE_DIR)
|
||||
|
||||
endif(EIGEN3_INCLUDE_DIR)
|
||||
88
cmake/modules/FindPerlEmbed.cmake
Normal file
88
cmake/modules/FindPerlEmbed.cmake
Normal file
@@ -0,0 +1,88 @@
|
||||
# Find the dependencies for linking with the Perl runtime library.
|
||||
|
||||
# Check for the Perl & PerlLib modules
|
||||
include(LibFindMacros)
|
||||
libfind_package(PerlEmbed Perl)
|
||||
libfind_package(PerlEmbed PerlLibs)
|
||||
|
||||
# Execute an Alien::Wx module to find the relevant information regarding
|
||||
# the wxWidgets used by the Perl interpreter.
|
||||
# Perl specific stuff
|
||||
set(PerlEmbed_TEMP_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/PerlEmbed_TEMP_INCLUDE.txt)
|
||||
execute_process(
|
||||
COMMAND ${PERL_EXECUTABLE} -MExtUtils::Embed -e "
|
||||
# Import Perl modules.
|
||||
use strict;
|
||||
use warnings;
|
||||
use Config;
|
||||
use Text::ParseWords;
|
||||
use ExtUtils::CppGuess;
|
||||
|
||||
# Test for a Visual Studio compiler
|
||||
my \$cpp_guess = ExtUtils::CppGuess->new;
|
||||
my \$mswin = \$^O eq 'MSWin32';
|
||||
my \$msvc = \$cpp_guess->is_msvc;
|
||||
|
||||
# Query the available data from Alien::wxWidgets.
|
||||
my \$ccflags;
|
||||
my \$ldflags;
|
||||
{ local *STDOUT; open STDOUT, '>', \\\$ccflags; ccflags; }
|
||||
{ local *STDOUT; open STDOUT, '>', \\\$ldflags; ldopts; }
|
||||
\$ccflags = ' ' . \$ccflags;
|
||||
\$ldflags = ' ' . \$ldflags;
|
||||
|
||||
my \$filename = '${PerlEmbed_TEMP_INCLUDE}';
|
||||
open(my $fh, '>', \$filename) or die \"Could not open file '\$filename' \$!\";
|
||||
|
||||
# Convert a space separated lists to CMake semicolon separated lists,
|
||||
# escape the backslashes,
|
||||
# export the resulting list to a temp file.
|
||||
sub cmake_set_var {
|
||||
my (\$varname, \$content) = @_;
|
||||
# Remove line separators.
|
||||
\$content =~ s/\\r|\\n//g;
|
||||
# Escape the path separators.
|
||||
\$content =~ s/\\\\/\\\\\\\\\\\\\\\\/g;
|
||||
my @words = shellwords(\$content);
|
||||
print \$fh \"set(PerlEmbed_\$varname \\\"\" . join(';', @words) . \"\\\")\\n\";
|
||||
}
|
||||
cmake_set_var('ARCHNAME', \$Config{archname});
|
||||
cmake_set_var('CCFLAGS', \$ccflags);
|
||||
\$ldflags =~ s/ -L/ -LIBPATH:/g if \$msvc;
|
||||
cmake_set_var('LD', \$Config{ld});
|
||||
cmake_set_var('LDFLAGS', \$ldflags);
|
||||
cmake_set_var('CCCDLFLAGS', \$Config{cccdlflags});
|
||||
cmake_set_var('LDDLFLAGS', \$Config{lddlflags});
|
||||
cmake_set_var('DLEXT', \$Config{dlext});
|
||||
close \$fh;
|
||||
")
|
||||
include(${PerlEmbed_TEMP_INCLUDE})
|
||||
file(REMOVE ${PerlEmbed_TEMP_INCLUDE})
|
||||
unset(PerlEmbed_TEMP_INCLUDE)
|
||||
|
||||
if (PerlEmbed_DEBUG)
|
||||
# First show the configuration extracted by FindPerl & FindPerlLibs:
|
||||
message(STATUS " PERL_INCLUDE_PATH = ${PERL_INCLUDE_PATH}")
|
||||
message(STATUS " PERL_LIBRARY = ${PERL_LIBRARY}")
|
||||
message(STATUS " PERL_EXECUTABLE = ${PERL_EXECUTABLE}")
|
||||
message(STATUS " PERL_SITESEARCH = ${PERL_SITESEARCH}")
|
||||
message(STATUS " PERL_SITELIB = ${PERL_SITELIB}")
|
||||
message(STATUS " PERL_VENDORARCH = ${PERL_VENDORARCH}")
|
||||
message(STATUS " PERL_VENDORLIB = ${PERL_VENDORLIB}")
|
||||
message(STATUS " PERL_ARCHLIB = ${PERL_ARCHLIB}")
|
||||
message(STATUS " PERL_PRIVLIB = ${PERL_PRIVLIB}")
|
||||
message(STATUS " PERL_EXTRA_C_FLAGS = ${PERL_EXTRA_C_FLAGS}")
|
||||
# Second show the configuration extracted by this module (FindPerlEmbed):
|
||||
message(STATUS " PerlEmbed_ARCHNAME = ${PerlEmbed_ARCHNAME}")
|
||||
message(STATUS " PerlEmbed_CCFLAGS = ${PerlEmbed_CCFLAGS}")
|
||||
message(STATUS " PerlEmbed_CCCDLFLAGS = ${PerlEmbed_CCCDLFLAGS}")
|
||||
message(STATUS " LD = ${PerlEmbed_LD}")
|
||||
message(STATUS " PerlEmbed_LDFLAGS = ${PerlEmbed_LDFLAGS}")
|
||||
message(STATUS " PerlEmbed_LDDLFLAGS = ${PerlEmbed_LDDLFLAGS}")
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_package_handle_standard_args(PerlEmbed
|
||||
REQUIRED_VARS PerlEmbed_CCFLAGS PerlEmbed_LDFLAGS
|
||||
VERSION_VAR PERL_VERSION)
|
||||
322
cmake/modules/FindTBB.cmake
Normal file
322
cmake/modules/FindTBB.cmake
Normal file
@@ -0,0 +1,322 @@
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2015 Justus Calvin
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
#
|
||||
# FindTBB
|
||||
# -------
|
||||
#
|
||||
# Find TBB include directories and libraries.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# find_package(TBB [major[.minor]] [EXACT]
|
||||
# [QUIET] [REQUIRED]
|
||||
# [[COMPONENTS] [components...]]
|
||||
# [OPTIONAL_COMPONENTS components...])
|
||||
#
|
||||
# where the allowed components are tbbmalloc and tbb_preview. Users may modify
|
||||
# the behavior of this module with the following variables:
|
||||
#
|
||||
# * TBB_ROOT_DIR - The base directory the of TBB installation.
|
||||
# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files.
|
||||
# * TBB_LIBRARY - The directory that contains the TBB library files.
|
||||
# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
|
||||
# These libraries, if specified, override the
|
||||
# corresponding library search results, where <library>
|
||||
# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug,
|
||||
# tbb_preview, or tbb_preview_debug.
|
||||
# * TBB_USE_DEBUG_BUILD - The debug version of tbb libraries, if present, will
|
||||
# be used instead of the release version.
|
||||
# * TBB_STATIC - Static linking of libraries with a _static suffix.
|
||||
# For example, on Windows a tbb_static.lib will be searched for
|
||||
# instead of tbb.lib.
|
||||
#
|
||||
# Users may modify the behavior of this module with the following environment
|
||||
# variables:
|
||||
#
|
||||
# * TBB_INSTALL_DIR
|
||||
# * TBBROOT
|
||||
# * LIBRARY_PATH
|
||||
#
|
||||
# This module will set the following variables:
|
||||
#
|
||||
# * TBB_FOUND - Set to false, or undefined, if we haven’t found, or
|
||||
# don’t want to use TBB.
|
||||
# * TBB_<component>_FOUND - If False, optional <component> part of TBB sytem is
|
||||
# not available.
|
||||
# * TBB_VERSION - The full version string
|
||||
# * TBB_VERSION_MAJOR - The major version
|
||||
# * TBB_VERSION_MINOR - The minor version
|
||||
# * TBB_INTERFACE_VERSION - The interface version number defined in
|
||||
# tbb/tbb_stddef.h.
|
||||
# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
|
||||
# <library>, where <library> may be tbb, tbb_debug,
|
||||
# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
# tbb_preview_debug.
|
||||
# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
|
||||
# <library>, where <library> may be tbb, tbb_debug,
|
||||
# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
# tbb_preview_debug.
|
||||
#
|
||||
# The following varibles should be used to build and link with TBB:
|
||||
#
|
||||
# * TBB_INCLUDE_DIRS - The include directory for TBB.
|
||||
# * TBB_LIBRARIES - The libraries to link against to use TBB.
|
||||
# * TBB_LIBRARIES_RELEASE - The release libraries to link against to use TBB.
|
||||
# * TBB_LIBRARIES_DEBUG - The debug libraries to link against to use TBB.
|
||||
# * TBB_DEFINITIONS - Definitions to use when compiling code that uses
|
||||
# TBB.
|
||||
# * TBB_DEFINITIONS_RELEASE - Definitions to use when compiling release code that
|
||||
# uses TBB.
|
||||
# * TBB_DEFINITIONS_DEBUG - Definitions to use when compiling debug code that
|
||||
# uses TBB.
|
||||
#
|
||||
# This module will also create the "tbb" target that may be used when building
|
||||
# executables and libraries.
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
if(NOT TBB_FOUND)
|
||||
|
||||
##################################
|
||||
# Check the build type
|
||||
##################################
|
||||
|
||||
if(NOT DEFINED TBB_USE_DEBUG_BUILD)
|
||||
if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
|
||||
set(TBB_BUILD_TYPE DEBUG)
|
||||
else()
|
||||
set(TBB_BUILD_TYPE RELEASE)
|
||||
endif()
|
||||
elseif(TBB_USE_DEBUG_BUILD)
|
||||
set(TBB_BUILD_TYPE DEBUG)
|
||||
else()
|
||||
set(TBB_BUILD_TYPE RELEASE)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
# Set the TBB search directories
|
||||
##################################
|
||||
|
||||
# Define search paths based on user input and environment variables
|
||||
set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
|
||||
|
||||
# Define the search directories based on the current platform
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
|
||||
"C:/Program Files (x86)/Intel/TBB")
|
||||
|
||||
# Set the target architecture
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(TBB_ARCHITECTURE "intel64")
|
||||
else()
|
||||
set(TBB_ARCHITECTURE "ia32")
|
||||
endif()
|
||||
|
||||
# Set the TBB search library path search suffix based on the version of VC
|
||||
if(WINDOWS_STORE)
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
|
||||
elseif(MSVC14)
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc14")
|
||||
elseif(MSVC12)
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc12")
|
||||
elseif(MSVC11)
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11")
|
||||
elseif(MSVC10)
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
|
||||
endif()
|
||||
|
||||
# Add the library path search suffix for the VC independent version of TBB
|
||||
list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
|
||||
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
# OS X
|
||||
set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
|
||||
# TODO: Check to see which C++ library is being used by the compiler.
|
||||
if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
|
||||
# The default C++ library on OS X 10.9 and later is libc++
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
|
||||
else()
|
||||
set(TBB_LIB_PATH_SUFFIX "lib")
|
||||
endif()
|
||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
# Linux
|
||||
set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
|
||||
# TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
|
||||
# <arch>/gcc4.1. For now, assume that the compiler is more recent than
|
||||
# gcc 4.4.x or later.
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
|
||||
set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
##################################
|
||||
# Find the TBB include dir
|
||||
##################################
|
||||
|
||||
find_path(TBB_INCLUDE_DIRS tbb/tbb.h
|
||||
HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
|
||||
PATHS ${TBB_DEFAULT_SEARCH_DIR}
|
||||
PATH_SUFFIXES include)
|
||||
|
||||
##################################
|
||||
# Set version strings
|
||||
##################################
|
||||
|
||||
if(TBB_INCLUDE_DIRS)
|
||||
file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
|
||||
string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
|
||||
TBB_VERSION_MAJOR "${_tbb_version_file}")
|
||||
string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
|
||||
TBB_VERSION_MINOR "${_tbb_version_file}")
|
||||
string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
|
||||
TBB_INTERFACE_VERSION "${_tbb_version_file}")
|
||||
set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
|
||||
endif()
|
||||
|
||||
##################################
|
||||
# Find TBB components
|
||||
##################################
|
||||
|
||||
if(TBB_VERSION VERSION_LESS 4.3)
|
||||
set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
|
||||
else()
|
||||
set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
|
||||
endif()
|
||||
|
||||
if(TBB_STATIC)
|
||||
set(TBB_STATIC_SUFFIX "_static")
|
||||
endif()
|
||||
|
||||
# Find each component
|
||||
foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
|
||||
if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
|
||||
|
||||
# Search for the libraries
|
||||
find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
|
||||
HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
|
||||
find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
|
||||
HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
|
||||
if(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
|
||||
endif()
|
||||
if(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
|
||||
endif()
|
||||
if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
|
||||
set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
|
||||
endif()
|
||||
|
||||
if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
|
||||
set(TBB_${_comp}_FOUND TRUE)
|
||||
else()
|
||||
set(TBB_${_comp}_FOUND FALSE)
|
||||
endif()
|
||||
|
||||
# Mark internal variables as advanced
|
||||
mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
mark_as_advanced(TBB_${_comp}_LIBRARY)
|
||||
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
unset(TBB_STATIC_SUFFIX)
|
||||
|
||||
##################################
|
||||
# Set compile flags and libraries
|
||||
##################################
|
||||
|
||||
set(TBB_DEFINITIONS_RELEASE "")
|
||||
set(TBB_DEFINITIONS_DEBUG "-DTBB_USE_DEBUG=1")
|
||||
|
||||
if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
|
||||
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_${TBB_BUILD_TYPE}}")
|
||||
set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
|
||||
elseif(TBB_LIBRARIES_RELEASE)
|
||||
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_RELEASE}")
|
||||
set(TBB_LIBRARIES "${TBB_LIBRARIES_RELEASE}")
|
||||
elseif(TBB_LIBRARIES_DEBUG)
|
||||
set(TBB_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}")
|
||||
set(TBB_LIBRARIES "${TBB_LIBRARIES_DEBUG}")
|
||||
endif()
|
||||
|
||||
find_package_handle_standard_args(TBB
|
||||
REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
|
||||
HANDLE_COMPONENTS
|
||||
VERSION_VAR TBB_VERSION)
|
||||
|
||||
##################################
|
||||
# Create targets
|
||||
##################################
|
||||
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
|
||||
add_library(tbb SHARED IMPORTED)
|
||||
set_target_properties(tbb PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
|
||||
IMPORTED_LOCATION ${TBB_LIBRARIES})
|
||||
if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
|
||||
set_target_properties(tbb PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:TBB_USE_DEBUG=1>"
|
||||
IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
|
||||
IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_DEBUG}
|
||||
IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
|
||||
IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
|
||||
)
|
||||
elseif(TBB_LIBRARIES_RELEASE)
|
||||
set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ${TBB_LIBRARIES_RELEASE})
|
||||
else()
|
||||
set_target_properties(tbb PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS_DEBUG}"
|
||||
IMPORTED_LOCATION ${TBB_LIBRARIES_DEBUG}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
|
||||
|
||||
unset(TBB_ARCHITECTURE)
|
||||
unset(TBB_BUILD_TYPE)
|
||||
unset(TBB_LIB_PATH_SUFFIX)
|
||||
unset(TBB_DEFAULT_SEARCH_DIR)
|
||||
|
||||
if(TBB_DEBUG)
|
||||
message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
|
||||
message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
|
||||
message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
|
||||
message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
|
||||
message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
|
||||
message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
|
||||
message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
265
cmake/modules/LibFindMacros.cmake
Normal file
265
cmake/modules/LibFindMacros.cmake
Normal file
@@ -0,0 +1,265 @@
|
||||
# Version 2.2
|
||||
# Public Domain, originally written by Lasse Kärkkäinen <tronic>
|
||||
# Maintained at https://github.com/Tronic/cmake-modules
|
||||
# Please send your improvements as pull requests on Github.
|
||||
|
||||
# Find another package and make it a dependency of the current package.
|
||||
# This also automatically forwards the "REQUIRED" argument.
|
||||
# Usage: libfind_package(<prefix> <another package> [extra args to find_package])
|
||||
macro (libfind_package PREFIX PKG)
|
||||
set(${PREFIX}_args ${PKG} ${ARGN})
|
||||
if (${PREFIX}_FIND_REQUIRED)
|
||||
set(${PREFIX}_args ${${PREFIX}_args} REQUIRED)
|
||||
endif()
|
||||
find_package(${${PREFIX}_args})
|
||||
set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG})
|
||||
unset(${PREFIX}_args)
|
||||
endmacro()
|
||||
|
||||
# A simple wrapper to make pkg-config searches a bit easier.
|
||||
# Works the same as CMake's internal pkg_check_modules but is always quiet.
|
||||
macro (libfind_pkg_check_modules)
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(${ARGN} QUIET)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
# Avoid useless copy&pasta by doing what most simple libraries do anyway:
|
||||
# pkg-config, find headers, find library.
|
||||
# Usage: libfind_pkg_detect(<prefix> <pkg-config args> FIND_PATH <name> [other args] FIND_LIBRARY <name> [other args])
|
||||
# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2)
|
||||
function (libfind_pkg_detect PREFIX)
|
||||
# Parse arguments
|
||||
set(argname pkgargs)
|
||||
foreach (i ${ARGN})
|
||||
if ("${i}" STREQUAL "FIND_PATH")
|
||||
set(argname pathargs)
|
||||
elseif ("${i}" STREQUAL "FIND_LIBRARY")
|
||||
set(argname libraryargs)
|
||||
else()
|
||||
set(${argname} ${${argname}} ${i})
|
||||
endif()
|
||||
endforeach()
|
||||
if (NOT pkgargs)
|
||||
message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.")
|
||||
endif()
|
||||
# Find library
|
||||
libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs})
|
||||
if (pathargs)
|
||||
find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS})
|
||||
endif()
|
||||
if (libraryargs)
|
||||
find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Extracts a version #define from a version.h file, output stored to <PREFIX>_VERSION.
|
||||
# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR)
|
||||
# Fourth argument "QUIET" may be used for silently testing different define names.
|
||||
# This function does nothing if the version variable is already defined.
|
||||
function (libfind_version_header PREFIX VERSION_H DEFINE_NAME)
|
||||
# Skip processing if we already have a version or if the include dir was not found
|
||||
if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR)
|
||||
return()
|
||||
endif()
|
||||
set(quiet ${${PREFIX}_FIND_QUIETLY})
|
||||
# Process optional arguments
|
||||
foreach(arg ${ARGN})
|
||||
if (arg STREQUAL "QUIET")
|
||||
set(quiet TRUE)
|
||||
else()
|
||||
message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.")
|
||||
endif()
|
||||
endforeach()
|
||||
# Read the header and parse for version number
|
||||
set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
|
||||
if (NOT EXISTS ${filename})
|
||||
if (NOT quiet)
|
||||
message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
file(READ "${filename}" header)
|
||||
string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}")
|
||||
# No regex match?
|
||||
if (match STREQUAL header)
|
||||
if (NOT quiet)
|
||||
message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"<version>\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
# Export the version string
|
||||
set(${PREFIX}_VERSION "${match}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Do the final processing once the paths have been detected.
|
||||
# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
|
||||
# all the variables, each of which contain one include directory.
|
||||
# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
|
||||
# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
|
||||
# Also handles errors in case library detection was required, etc.
|
||||
function (libfind_process PREFIX)
|
||||
# Skip processing if already processed during this configuration run
|
||||
if (${PREFIX}_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(found TRUE) # Start with the assumption that the package was found
|
||||
|
||||
# Did we find any files? Did we miss includes? These are for formatting better error messages.
|
||||
set(some_files FALSE)
|
||||
set(missing_headers FALSE)
|
||||
|
||||
# Shorthands for some variables that we need often
|
||||
set(quiet ${${PREFIX}_FIND_QUIETLY})
|
||||
set(required ${${PREFIX}_FIND_REQUIRED})
|
||||
set(exactver ${${PREFIX}_FIND_VERSION_EXACT})
|
||||
set(findver "${${PREFIX}_FIND_VERSION}")
|
||||
set(version "${${PREFIX}_VERSION}")
|
||||
|
||||
# Lists of config option names (all, includes, libs)
|
||||
unset(configopts)
|
||||
set(includeopts ${${PREFIX}_PROCESS_INCLUDES})
|
||||
set(libraryopts ${${PREFIX}_PROCESS_LIBS})
|
||||
|
||||
# Process deps to add to
|
||||
foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES})
|
||||
if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS)
|
||||
# The package seems to export option lists that we can use, woohoo!
|
||||
list(APPEND includeopts ${${i}_INCLUDE_OPTS})
|
||||
list(APPEND libraryopts ${${i}_LIBRARY_OPTS})
|
||||
else()
|
||||
# If plural forms don't exist or they equal singular forms
|
||||
if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR
|
||||
({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES))
|
||||
# Singular forms can be used
|
||||
if (DEFINED ${i}_INCLUDE_DIR)
|
||||
list(APPEND includeopts ${i}_INCLUDE_DIR)
|
||||
endif()
|
||||
if (DEFINED ${i}_LIBRARY)
|
||||
list(APPEND libraryopts ${i}_LIBRARY)
|
||||
endif()
|
||||
else()
|
||||
# Oh no, we don't know the option names
|
||||
message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if (includeopts)
|
||||
list(REMOVE_DUPLICATES includeopts)
|
||||
endif()
|
||||
|
||||
if (libraryopts)
|
||||
list(REMOVE_DUPLICATES libraryopts)
|
||||
endif()
|
||||
|
||||
string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}")
|
||||
if (NOT tmp STREQUAL "${includeopts} ${libraryopts}")
|
||||
message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).")
|
||||
endif()
|
||||
|
||||
# Include/library names separated by spaces (notice: not CMake lists)
|
||||
unset(includes)
|
||||
unset(libs)
|
||||
|
||||
# Process all includes and set found false if any are missing
|
||||
foreach (i ${includeopts})
|
||||
list(APPEND configopts ${i})
|
||||
if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND")
|
||||
list(APPEND includes "${${i}}")
|
||||
else()
|
||||
set(found FALSE)
|
||||
set(missing_headers TRUE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Process all libraries and set found false if any are missing
|
||||
foreach (i ${libraryopts})
|
||||
list(APPEND configopts ${i})
|
||||
if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND")
|
||||
list(APPEND libs "${${i}}")
|
||||
else()
|
||||
set (found FALSE)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Version checks
|
||||
if (found AND findver)
|
||||
if (NOT version)
|
||||
message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.")
|
||||
elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver))
|
||||
set(found FALSE)
|
||||
set(version_unsuitable TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# If all-OK, hide all config options, export variables, print status and exit
|
||||
if (found)
|
||||
foreach (i ${configopts})
|
||||
mark_as_advanced(${i})
|
||||
endforeach()
|
||||
if (NOT quiet)
|
||||
message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
|
||||
if (LIBFIND_DEBUG)
|
||||
message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}")
|
||||
message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}")
|
||||
message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}")
|
||||
message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}")
|
||||
message(STATUS " ${PREFIX}_LIBRARIES=${libs}")
|
||||
endif()
|
||||
set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE)
|
||||
set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE)
|
||||
set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE)
|
||||
set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE)
|
||||
set (${PREFIX}_FOUND TRUE PARENT_SCOPE)
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Format messages for debug info and the type of error
|
||||
set(vars "Relevant CMake configuration variables:\n")
|
||||
foreach (i ${configopts})
|
||||
mark_as_advanced(CLEAR ${i})
|
||||
set(val ${${i}})
|
||||
if ("${val}" STREQUAL "${i}-NOTFOUND")
|
||||
set (val "<not found>")
|
||||
elseif (val AND NOT EXISTS ${val})
|
||||
set (val "${val} (does not exist)")
|
||||
else()
|
||||
set(some_files TRUE)
|
||||
endif()
|
||||
set(vars "${vars} ${i}=${val}\n")
|
||||
endforeach()
|
||||
set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n")
|
||||
if (version_unsuitable)
|
||||
set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but")
|
||||
if (exactver)
|
||||
set(msg "${msg} only version ${findver} is acceptable.")
|
||||
else()
|
||||
set(msg "${msg} version ${findver} is the minimum requirement.")
|
||||
endif()
|
||||
else()
|
||||
if (missing_headers)
|
||||
set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?")
|
||||
elseif (some_files)
|
||||
set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?")
|
||||
if(findver)
|
||||
set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).")
|
||||
endif()
|
||||
else()
|
||||
set(msg "We were unable to find package ${PREFIX}.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Fatal error out if REQUIRED
|
||||
if (required)
|
||||
set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.")
|
||||
message(FATAL_ERROR "${msg}\n${vars}")
|
||||
endif()
|
||||
# Otherwise just print a nasty warning
|
||||
if (NOT quiet)
|
||||
message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}")
|
||||
endif()
|
||||
endfunction()
|
||||
22
cmake/msvc/xs.wperl.props.in
Normal file
22
cmake/msvc/xs.wperl.props.in
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file is autogenerated by CMake
|
||||
Note: In the .in template file, the $ {}-style variables are interpreted by CMake while the $()-style variables belong to MSVC
|
||||
-->
|
||||
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets">
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<ExecutablePath>$(VC_ExecutablePath_x64);$(WindowsSDK_ExecutablePath);$(VS_ExecutablePath);$(MSBuild_ExecutablePath);$(FxCopDir);$(PATH);${PROPS_PERL_BIN_PATH}\;</ExecutablePath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup />
|
||||
<ItemGroup />
|
||||
|
||||
<PropertyGroup>
|
||||
<LocalDebuggerCommand>${PROPS_PERL_EXECUTABLE}</LocalDebuggerCommand>
|
||||
<LocalDebuggerCommandArguments>slic3r.pl</LocalDebuggerCommandArguments>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
<LocalDebuggerWorkingDirectory>${PROPS_CMAKE_SOURCE_DIR}</LocalDebuggerWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
2
doc/How to build - UNIX.md
Normal file
2
doc/How to build - UNIX.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Building Slic3r PE on Linux/UNIX
|
||||
|
||||
93
doc/How to build - Windows.md
Normal file
93
doc/How to build - Windows.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Building Slic3r PE on Microsoft Windows
|
||||
|
||||
The currently supported way of building Slic3r PE on Windows is with CMake and MS Visual Studio 2013
|
||||
using our Perl binary distribution (compiled from official Perl sources).
|
||||
You can use the free [Visual Studio 2013 Community Edition](https://www.visualstudio.com/vs/older-downloads/).
|
||||
CMake installer can be downloaded from [the official website](https://cmake.org/download/).
|
||||
|
||||
Other setups (such as mingw + Strawberry Perl) _may_ work, but we cannot guarantee this will work
|
||||
and cannot provide guidance.
|
||||
|
||||
|
||||
### Geting the dependencies
|
||||
|
||||
First, download and upnack our Perl + wxWidgets binary distribution:
|
||||
|
||||
- 32 bit, release mode: [wperl32-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl32-5.24.0-2018-03-02.7z)
|
||||
- 64 bit, release mode: [wperl64-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64-5.24.0-2018-03-02.7z)
|
||||
- 64 bit, release mode + debug symbols: [wperl64d-5.24.0-2018-03-02.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=wperl64d-5.24.0-2018-03-02.7z)
|
||||
|
||||
It is recommended to unpack this package into `C:\`.
|
||||
|
||||
Apart from wxWidgets and Perl, you will also need additional dependencies:
|
||||
|
||||
- Boost
|
||||
- Intel TBB
|
||||
- libcurl
|
||||
|
||||
We have prepared a binary package of the listed libraries:
|
||||
|
||||
- 32 bit: [slic3r-destdir-32.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=2%2Fslic3r-destdir-32.7z)
|
||||
- 64 bit: [slic3r-destdir-64.7z](https://bintray.com/vojtechkral/Slic3r-PE/download_file?file_path=2%2Fslic3r-destdir-64.7z)
|
||||
|
||||
It is recommended you unpack this package into `C:\local\` as the environment
|
||||
setup script expects it there.
|
||||
|
||||
Alternatively you can also compile the additional dependencies yourself.
|
||||
There is a [powershell script](./deps-build/windows/slic3r-makedeps.ps1) which automates this process.
|
||||
|
||||
### Building Slic3r PE
|
||||
|
||||
Once the dependencies are set up in their respective locations,
|
||||
go to the `wperl*` directory extracted earlier and launch the `cmdline.lnk` file
|
||||
which opens a command line prompt with appropriate environment variables set up.
|
||||
|
||||
In this command line, `cd` into the directory with Slic3r sources
|
||||
and use these commands to build the Slic3r from the command line:
|
||||
|
||||
perl Build.PL
|
||||
perl Build.PL --gui
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||
nmake
|
||||
cd ..
|
||||
perl slic3r.pl
|
||||
|
||||
The above commands use `nmake` Makefiles.
|
||||
You may also build Slic3r PE with other build tools:
|
||||
|
||||
|
||||
### Building with Visual Studio
|
||||
|
||||
To build and debug Slic3r PE with Visual Studio (64 bits), replace the `cmake` command with:
|
||||
|
||||
cmake .. -G "Visual Studio 12 Win64" -DCMAKE_CONFIGURATION_TYPES=RelWithDebInfo
|
||||
|
||||
For the 32-bit variant, use:
|
||||
|
||||
cmake .. -G "Visual Studio 12" -DCMAKE_CONFIGURATION_TYPES=RelWithDebInfo
|
||||
|
||||
After `cmake` has finished, go to the build directory and open the `Slic3r.sln` solution file.
|
||||
This should open Visual Studio and load the Slic3r solution containing all the projects.
|
||||
Make sure you use Visual Studio 2013 to open the solution.
|
||||
|
||||
You can then use the usual Visual Studio controls to build Slic3r (Hit `F5` to build and run with debugger).
|
||||
If you want to run or debug Slic3r from within Visual Studio, make sure the `XS` project is activated.
|
||||
It should be set as the Startup project by CMake by default, but you might want to check anyway.
|
||||
There are multiple projects in the Slic3r solution, but only the `XS` project is configured with the right
|
||||
commands to run and debug Slic3r.
|
||||
|
||||
The above cmake commands generate Visual Studio project files with the `RelWithDebInfo` configuration only.
|
||||
If you also want to use the `Release` configuration, you can generate Visual Studio projects with:
|
||||
|
||||
-DCMAKE_CONFIGURATION_TYPES=Release;RelWithDebInfo
|
||||
|
||||
(The `Debug` configuration is not supported as of now.)
|
||||
|
||||
### Building with ninja
|
||||
|
||||
To use [Ninja](https://ninja-build.org/), replace the `cmake` and `nmake` commands with:
|
||||
|
||||
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
316
doc/How_to_build_Slic3r.txt
Normal file
316
doc/How_to_build_Slic3r.txt
Normal file
@@ -0,0 +1,316 @@
|
||||
How to build Slic3r on Mac OS X 10.9 Maveric
|
||||
---------------------------------------------
|
||||
Vojtech Bubnik, 2017-12-12
|
||||
|
||||
|
||||
1) Install Mac OS X 10.7 Lion 64 bit with X Code
|
||||
------------------------------------------------
|
||||
|
||||
One has to build the OSX Slic3r on a real Mac, either directly on the system, or on a virtualized OSX. On Mac, two commercial solutions are available to legally virtualize MacOS on MacOS:
|
||||
http://www.parallels.com/eu/products/desktop/
|
||||
http://www.vmware.com/products/workstation/
|
||||
|
||||
Installation of a X Code on an OS X 10.7 Lion needs a bit of work. The latest X Code supported by the Lion on a Virtual Box is 4.21. The trouble is, the certificates of the X Code 4.21 installation package expired. One way to work around the certificate is to flatten the installation package by unpacking and repacking it:
|
||||
pkgutil --expand Foobar.pkg foobar
|
||||
pkgutil --flatten foobar barfoo.pkg
|
||||
|
||||
The flattened package is available here:
|
||||
\\rs.prusa\Development\Slic3r-Prusa\installxcode_421_lion_fixed.pkg
|
||||
|
||||
This installer does not install the X Code directly. Instead, it installs another installer with a set of 47 pkg files. These files have their certificates expired as well. You will find the packages on your MacOS here:
|
||||
/Applications/Install Xcode.app/Contents/Resources/Packages/
|
||||
|
||||
It is best to flatten them in a loop:
|
||||
cd /Applications/Install\ Xcode.app/Contents/Resources/Packages/
|
||||
for f in *.pkg; do
|
||||
pkgutil --expand $f /tmp/$f
|
||||
rm -f $f
|
||||
pkgutil --flatten /tmp/$f $f
|
||||
done
|
||||
|
||||
After that, you may finish the installation of Xcode by running
|
||||
/Applications/Install\ Xcode.app
|
||||
|
||||
|
||||
1b) Installing the Xcode on a newer system
|
||||
-------------------------------------------
|
||||
You will need to register as an Apple developer on
|
||||
https://developer.apple.com/
|
||||
log in and download and install Xcode
|
||||
https://developer.apple.com/downloads/
|
||||
You will likely need to download and install Xcode Command Line Tools, though the Xcode 4.1 came with the command line tools installed.
|
||||
|
||||
|
||||
2) Prepare the development environment
|
||||
--------------------------------------
|
||||
|
||||
Install the brew package manager:
|
||||
http://brew.sh/
|
||||
The brew package manager requires the git command line tool. Normally the git tool is installed as part of the Xcode command line tools.
|
||||
Copy and execute a command line from the top of http://brew.sh/ . It is possible, that the invocation of git fails because of some parameters the old git does not recognize. If so, invoke the git call manually.
|
||||
|
||||
Compile the boost library using brew. Following line compiles a 64bit boost with both static and shared libraries.
|
||||
brew install boost --universal
|
||||
|
||||
Install dylibbundler tool. The dylibbundler tool serves to collect dependent dynamic libraries and fix their linkage. Execute
|
||||
brew install dylibbundler
|
||||
|
||||
Install cmake
|
||||
brew install cmake
|
||||
|
||||
3) Install perl
|
||||
---------------
|
||||
|
||||
We don't want to distribute perl pre-installed on the Mac OS box. First, the system perl installation is not correct on some Mac OS versions, second it is not rellocatable. To compile a 64bit rellocatable perl, we use the perlbrew distribution. The perlbrew distribution installs into a user home directory and it allows switching between multiple versions of perl.
|
||||
http://perlbrew.pl/
|
||||
|
||||
First install perlbrew
|
||||
curl -L http://install.perlbrew.pl | bash
|
||||
Then compile the newest perl with the rellocatable @INC path and with multithreading enabled, execute following line:
|
||||
perlbrew install --threads -Duserelocatableinc --switch perl-5.26.1
|
||||
The --switch parameter switches the active perl to the currently compiled one.
|
||||
Available perl versions could be listed by calling
|
||||
perlbrew available
|
||||
Switch to the newly compiled perl
|
||||
perl5/perlbrew/bin/perlbrew switch perl-5.26.1
|
||||
Install cpanm
|
||||
perlbrew install-cpanm
|
||||
|
||||
Initialize CPAN, install PAR and PAR::Packer modules
|
||||
execute cpan command, from the cpan prompt, run
|
||||
install App::cpanminus
|
||||
install ExtUtils::CppGuess
|
||||
install ExtUtils::Typemaps
|
||||
install ExtUtils::Typemaps::Basic
|
||||
install PAR
|
||||
install PAR::Packer
|
||||
install Module::Build
|
||||
install Module::Pluggable
|
||||
install Module::Runtime
|
||||
install Moo
|
||||
install Test::Pod
|
||||
install Test::Pod::Coverage
|
||||
quit
|
||||
|
||||
4) Download and install Slic3r
|
||||
------------------------------
|
||||
|
||||
git clone git://github.com/alexrj/Slic3r
|
||||
cd Slic3r
|
||||
perl Build.PL
|
||||
|
||||
Now Slic3r shall be compiled. You may try to execute
|
||||
perl slic3r.pl
|
||||
to get a help screen, or
|
||||
perl slic3r.pl some_model.stl
|
||||
to have the model sliced.
|
||||
|
||||
5) Download and compile the GUI libraries needed to execute Slic3r in GUI mode
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
Building the Perl Alien-Wx containing a wxWidgets library:
|
||||
We use wxWidgets-3.0.3.
|
||||
patch wxWidgets-3.0.3//src/osx/cocoa/textctrl.mm , see https://github.com/prusa3d/Slic3r/issues/600
|
||||
perl -I. Build.PL --wxWidgets-extraflags="--with-osx_cocoa --with-macosx-version-min=10.9 --with-macosx-sdk=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk --with-libjpeg=builtin --with-libpng=builtin --with-regex=builtin --with-libtiff=builtin --with-zlib=builtin --with-expat=builtin --with-opengl"
|
||||
perl -I. Build
|
||||
perl -I. Build test
|
||||
perl -I. Build
|
||||
|
||||
Building the Perl Wx package:
|
||||
cpan install Wx
|
||||
|
||||
Building the Perl OpenGL package:
|
||||
OpenGL needs to be patched to support MSAA, see the Windows patch.
|
||||
|
||||
|
||||
For the current Slic3r 1.2.30 code base, set the environment variable SLIC3R_STATIC to link a static version of the boost library:
|
||||
export SLIC3R_STATIC=1
|
||||
|
||||
then execute
|
||||
perl Build.PL --gui
|
||||
and keep your fingers crossed. The Build.PL script downloads and compiles the WxWidgets 3.0 through a Alien::Wx PERL package. The WxWidget shared libraries will land into
|
||||
~/perl5/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/
|
||||
|
||||
On Maverics, we experienced following issue compiling WxPerl:
|
||||
http://wiki.bolay.net/doku.php?id=acdsn:acdsn-a:mac
|
||||
|
||||
Now you could run the GUI version of slic3r by calling
|
||||
perl slic3r.pl --gui
|
||||
If some dependency is missing, the MacOS system will let you know.
|
||||
|
||||
6) Packing the Slic3r
|
||||
---------------------
|
||||
|
||||
Perl is an operating system on its own. Many modules are shared among multiple applications and it is difficult to extract a stand-alone application from a perl installation manually. Fortunately, tools are available, which automate the process to some extent. One of the tools is the PAR::Packer. The PAR::Packer tool (pp executable) is able to create a standalone executable for a perl script. The standalone executable contains a PAR archive (a zip file) bundled with a perl interpreter. When executed, the bundled executable will decompress most of the PAR archive into a temp folder. Because of that, we will use the PAR::Packer to resolve and collect the dependencies, but we will create an installer manually.
|
||||
|
||||
The PAR::Packer could analyze the dependencies by a statical analysis, or at a runtime. The statical analysis does not resolve the dynamically loaded modules. On the other side, the statical analysis is pessimistic, therefore it often collects unneeded packages. The dynamic analysis may miss some package, if not all branches of a code are executed. We will try to solely depend on the dynamic analysis to keep the installation size minimal. We may need to develop a protocol or an automatic UI tool to exercise as much as possible from the Slic3r GUI to pack the GUI version reliably. Once a reliable list of dependencies is collected, we may not need the PAR::Packer anymore.
|
||||
|
||||
To create a PAR archive of a command line slic3r, execute
|
||||
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
|
||||
and let the slic3r slice a cube.stl to load the dynamic modules.
|
||||
|
||||
To create a PAR archive of a GUI slic3r, execute
|
||||
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
|
||||
and exercise the slic3r gui to load all modules.
|
||||
|
||||
Rename the slic3r.par file to slic3r.zip and decompress. Most of the code needed to execute Slic3r is there, only the perl executable is missing for the command line slic3r, and the WxWidgets shared libraries and the liblzma shared library are missing for the GUI version.
|
||||
|
||||
7) Collecting the dependent shared libraries, making the link paths relative
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
We have linked Slic3r against a static boost library, therefore the command line slic3r is not dependent on any non-system shared library. The situation is different for the GUI slic3r, which links dynamically against WxWidgets and liblzma.
|
||||
|
||||
The trick developed by Apple to allow rellocable shared libraries is to addres a shared library relatively to the path of the executable by encoding a special token @executable_path at the start of the path. Unfortunately the libraries requried by Slic3r are compiled with absolute paths.
|
||||
|
||||
Once the slic3r.par archive is unpacked, one may list the native shared libraries by
|
||||
find ./ -name '*.bundle'
|
||||
and one may list the dependencies by running
|
||||
otool -L somefile.bundle
|
||||
Most of the dependencies point to system directores and these dependences are always fulfilled. Dependencies pointing to the WxWidget libraries need to be fixed. These have a form
|
||||
~/perlbrew/perls/perl-5.22.1/lib/site_perl/5.22.1/darwin-thread-multi-2level/Alien/wxWidgets/osx_cocoa_3_0_2_uni/lib/libwx_*.dylib
|
||||
and we need to replace them with
|
||||
@executable_path/../Frameworks/libwx_*.dylib
|
||||
Another dependency, which needs our attention is
|
||||
/usr/local/Cellar/xz/5.2.2/lib/liblzma.5.dylib
|
||||
|
||||
Fortunately, a tool dylibbundler was developed to address this problem.
|
||||
First install dylibbundler by calling
|
||||
brew dylibbundler
|
||||
|
||||
For some installations, the dylibbundler tool sufficiently fixes all dependencies. Unfortunately, the WxWidgets installation is inconsistent in the versioning, therefore a certain clean-up is required. Namely, the WxWidgets libraries are compiled with the full build number in their file name. For example, the base library is built as libwx_baseu-3.0.0.2.0.dylib and a symlink is created libwx_baseu-3.0.dylib pointing to the full name. Then some of the Wx libraries link against the full name and some against the symlink, leading the dylibbundler to pack both. We solved the problem by whipping up a following script:
|
||||
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\fix_dependencies.sh
|
||||
|
||||
call
|
||||
slic3r_dependencies.sh --fix
|
||||
to collect the shared libraries into Content/Frameworks and to fix their linkage.
|
||||
|
||||
call
|
||||
slic3r_dependencies.sh --show
|
||||
to list dependent libraries in a sorted order. All the non-system dependencies shall start with @executable_path after the fix.
|
||||
|
||||
|
||||
|
||||
8) Packing Slic3r into a dmg image using a bunch of scripts
|
||||
-----------------------------------------------------------
|
||||
|
||||
Instead of relying on the PAR::Packer to collect the dependencies, we have used PAR::Packer to extract the dependencies, we manually cleaned them up and created an installer script.
|
||||
\\rs.prusa\Development\Slic3r-Prusa\How_to_build_on_MacOSX_Lion\Slic3r-Build-MacOS
|
||||
First compile Slic3r, then call build_dmg.sh with a path to the Slic3r source tree as a parameter.
|
||||
The script will collect all dependencies into Slic3r.app and it will create Slic3r.dmg.
|
||||
If SLIC3R_GUI variable is defined, a GUI variant of Slic3r will be packed.
|
||||
|
||||
|
||||
|
||||
|
||||
How to build on Windows
|
||||
-----------------------
|
||||
|
||||
The prefered perl distribution on MS Windows is the Strawberry Perl 5.22.1.3 (32bit)
|
||||
http://strawberryperl.com/
|
||||
|
||||
Let it install into c:\strawberry
|
||||
You may make a copy of the distribution. We recommend to make following copies:
|
||||
For a release command line only build: c:\strawberry-minimal
|
||||
For a release GUI build: c:\strawberry-minimal-gui
|
||||
For a development build with debugging information: c:\strawberry-debug
|
||||
and to make one of them active by making a directory junction:
|
||||
mklink /d c:\Strawberry c:\Strawberry-debug
|
||||
|
||||
Building boost:
|
||||
Slic3r seems to have a trouble with the latest boost 1.60.0 on Windows. Please use 1.59.
|
||||
Decompress it to
|
||||
c:\dev\
|
||||
otherwise it will not be found by the Build.PL on Windows. You may consider to hack xs\Build.PL with your prefered boost path.
|
||||
run
|
||||
bootstrap.bat mingw
|
||||
b2 toolset=gcc
|
||||
|
||||
Install git command line
|
||||
https://git-scm.com/
|
||||
|
||||
Download Slic3r source code
|
||||
git clone git://github.com/alexrj/Slic3r.git
|
||||
|
||||
Run compilation
|
||||
cd Slic3r
|
||||
perl Build.PL
|
||||
perl Build.PL --gui
|
||||
|
||||
With a bit of luck, you will end up with a working Slic3r including GUI.
|
||||
|
||||
|
||||
|
||||
|
||||
Packing on Windows
|
||||
------------------
|
||||
|
||||
Life is easy on Windows. PAR::Packer will help again to collect the dependencies. The basic procedure is the same as for MacOS:
|
||||
|
||||
To create a PAR archive of a command line slic3r, execute
|
||||
pp -e -p -x slic3r.pl --xargs cube.stl -o slic3r.par
|
||||
and let the slic3r slice a cube.stl to load the dynamic modules.
|
||||
|
||||
To create a PAR archive of a GUI slic3r, execute
|
||||
pp -e -p -x slic3r.pl --xargs --gui -o slic3r.par
|
||||
and exercise the slic3r gui to load all modules.
|
||||
|
||||
The standalone installation is then created from the PAR archive by renaming it into a zip and adding following binaries from c:\strawberry to the root of the extracted zip:
|
||||
perl5.22.1.exe
|
||||
perl522.dll
|
||||
libgcc_s_sjlj-1.dll
|
||||
libstdc++-6.dll
|
||||
libwinpthread-1.dll
|
||||
|
||||
The GUI build requires following DLLs in addition:
|
||||
libglut-0_.dll
|
||||
wxbase30u_gcc_custom.dll
|
||||
wxmsw30u_adv_gcc_custom.dll
|
||||
wxmsw30u_core_gcc_custom.dll
|
||||
wxmsw30u_gl_gcc_custom.dll
|
||||
wxmsw30u_html_gcc_custom.dll
|
||||
|
||||
and the var directory with the icons needs to be copied to the destination directory.
|
||||
|
||||
To run the slic3r, move the script\slic3r.pl one level up and create a following tiny windows batch in the root of the unpacked zip:
|
||||
@perl5.22.1.exe slic3r.pl %*
|
||||
A windows shortcut may be created for the GUI version. Instead of the perl.exe, it is better to use the wperl.exe to start the GUI Slic3r, because it does not open a text console.
|
||||
|
||||
The strawberry perl is already rellocatable, which means that the perl interpreter will find the perl modules in the lib directory,
|
||||
and Windows look up the missing DLLs in the directory of the executable, therefore no further rellocation effort is necessary.
|
||||
|
||||
|
||||
Packing on Windows, a single EXE solution
|
||||
-----------------------------------------
|
||||
|
||||
One may try to create a PAR executable for command line slic3r:
|
||||
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M Slic3r::XS -M Unicode::Normalize -o slic3r.exe slic3r.pl
|
||||
|
||||
One may as well create a PAR executable for Windows GUI:
|
||||
pp -M Encode::Locale -M Moo -M Thread::Semaphore -M OpenGL -M Slic3r::XS -M Unicode::Normalize -M Wx -M Class::Accessor -M Wx::DND -M Wx::Grid -M Wx::Print -M Wx::Html -M Wx::GLCanvas -M Math::Trig -M threads -M threads::shared -M Thread::Queue -l C:\strawberry\perl\site\lib\auto\Wx\Wx.xs.dll -o -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxbase30u_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_core_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_gl_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_adv_gcc_custom.dll -l C:\strawberry\perl\site\lib\Alien\wxWidgets\msw_3_0_2_uni_gcc_3_4\lib\wxmsw30u_html_gcc_custom.dll -o slic3r.exe slic3r.pl
|
||||
|
||||
Remember, that these executables will unpack into a temporary directory. The directory may be declared by setting an environment variable PAR_GLOBAL_TEMP. Otherwise the temporaries are unpacked into
|
||||
C:\Users\xxx\AppData\Local\Temp\par-xxxxxx
|
||||
|
||||
|
||||
Debugging the perl, debugging on Windows
|
||||
----------------------------------------
|
||||
|
||||
It is possible to debug perl using the integrated debugger. The EPIC plugin for Eclipse works very well with an older eclipse-SDK-3.6.2. There is a catch though: The Perl debugger does not work correctly with multiple threads running under the Perl interpreter. If that happens, the EPIC plugin gets confused and the debugger stops working. The same happens with the Komodo IDE.
|
||||
|
||||
Debugging the C++ code works fine using the latest Eclipse for C++ and the gdb of MinGW. The gdb packed with the Strawberry distribution does not contain the Python support, so pretty printing of the stl containers only works if another gdb build is used. The one of the QT installation works well.
|
||||
|
||||
It is yet a bit more complicated. The Strawberry MINGW is compiled for a different C++ exception passing model (SJLJ) than the other MINGWs, so one cannot simply combine MINGWs on Windows. For an unknown reason the nice debugger of the QT Creator hangs when debugging the C++ compiled by the Strawberry MINGW. Mabe it is because of the different exception passing models.
|
||||
|
||||
And to disable optimization of the C/C++ code, one has to manually modify Config_heavy.pl in the Perl central installation. The SLIC3R_DEBUG environment variable did not override all the -O2 and -O3 flags that the perl build adds the gcc execution line.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Building boost.
|
||||
|
||||
One may save compilation time by compiling just what Slic3r needs.
|
||||
./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex
|
||||
The -fPIC flag is required on Linux to make the static libraries rellocatable,
|
||||
so they could be embedded into a shared library.
|
||||
It is important to disable boost.locale.icu=off when compiling the static boost library.
|
||||
./bjam -a link=static variant=release threading=multi boost.locale.icu=off --with-locale cxxflags=-fPIC cflags=-fPIC
|
||||
To install on Linux to /usr/local/..., run the line above with the additional install keyword and with sudo.
|
||||
76
doc/Localization_guide.md
Normal file
76
doc/Localization_guide.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Localization and translation guide
|
||||
|
||||
The purpose of this guide is to describe how to contribute to the Slic3rPE translations. We use GNUgettext for extracting string resources from the project and PoEdit for editing translations.
|
||||
|
||||
Those are possible to download here:
|
||||
- https://sourceforge.net/directory/os:windows/?q=gnu+gettext GNUgettext package contains a set of tools to extract strings from the source code and to create the translation Catalog.
|
||||
- https://poedit.net PoEdit provides good interface for the translators.
|
||||
|
||||
After GNUgettext is installed it is recommended to add the path to gettext/bin to PATH variable.
|
||||
|
||||
Full manual for GNUgettext you can see here: http://www.gnu.org/software/gettext/manual/gettext.html
|
||||
|
||||
|
||||
### Scenario 1. How do I add a translation or fix the existing translation
|
||||
1. Get PO-file from corresponding folder here:
|
||||
https://github.com/prusa3d/Slic3r/tree/master/resources/localization
|
||||
2. Open this file in PoEdit as "Edit a translation"
|
||||
3. Apply your corrections to translation
|
||||
4. Push changed Slic3rPE.po and Slic3rPE.mo (will create automatically after saving of Slic3r.po in PoEdit) back to to the enter folder.
|
||||
|
||||
### Scenario 2. How do I add a new language support
|
||||
1. Get file Slic3rPE.pot here :
|
||||
https://github.com/prusa3d/Slic3r/tree/master/resources/localization
|
||||
2. Open it in PoEdit for "Create new translation"
|
||||
3. Select Translation Language (for example French).
|
||||
4. As a result you will have fr.po - the file contaning translation to French.
|
||||
Notice. When the transtation is complete you need to:
|
||||
- Rename the file to Slic3rPE.po
|
||||
- Click "Save file" button. Slic3rPE.mo will be created immediatly
|
||||
- Both Slic3rPE.po and Slic3rPE.mo have to be saved here:
|
||||
https://github.com/prusa3d/Slic3r/tree/master/resources/localization/fr
|
||||
( name of folder "fr" means "French" - the translation language).
|
||||
|
||||
### Scenario 3. How do I add a new text resource when implementing a feature to Slic3rPE
|
||||
Each string resource in Slic3rPE available for translation needs to be explicitly marked using L() macro like this:
|
||||
```C++
|
||||
auto msg = L("This message to be localized")
|
||||
```
|
||||
To get translated text use one of needed macro/function (`_(s)`, `_CHB(s)` or `L_str(s)` ).
|
||||
If you add new file resourse, add it to list of files contaned macro `L()`
|
||||
|
||||
### Scenario 4. How do I use GNUgettext to localize my own application taking Slic3rPE as an example
|
||||
|
||||
1. For conviniance create list of files with this macro `L(s)`. We have
|
||||
https://github.com/prusa3d/Slic3r/tree/master/resources/localization/list.txt.
|
||||
|
||||
2. Create template file(*.POT) with GNUgettext command:
|
||||
```
|
||||
xgettext --keyword=L --from-code=UTF-8 --debug -o Slic3rPE.pot -f list.txt
|
||||
```
|
||||
|
||||
Use flag `--from-code=UTF-8` to specify that the source strings are in UTF-8 encoding
|
||||
Use flag `--debug` to correctly extract formated strings(used %d, %s etc.)
|
||||
|
||||
3. Create PO- and MO-files for your project as described above.
|
||||
|
||||
4. To merge old PO-file with strings from creaded new POT-file use command:
|
||||
```
|
||||
msgmerge -N -o new.po old.po new.pot
|
||||
```
|
||||
Use option `-N` to not using fuzzy matching when an exact match is not found.
|
||||
|
||||
5. To concatenate old PO-file with strings from new PO-file use command:
|
||||
```
|
||||
msgcat -o new.po old.po
|
||||
```
|
||||
|
||||
6. Create an English translation catalog with command:
|
||||
```
|
||||
msgen -o new.po old.po
|
||||
```
|
||||
Notice, in this Catalog it will be totally same strings for initial text and translated.
|
||||
|
||||
When you have Catalog to translation open POT or PO file in PoEdit and start to translation.
|
||||
It's very important to keep attention to every gaps and punctuation. Especially with
|
||||
formated strings. (used %d, %s etc.)
|
||||
135
doc/deps-build/unix-static/Makefile
Normal file
135
doc/deps-build/unix-static/Makefile
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
#
|
||||
# This makefile downloads, configures and builds Slic3r PE dependencies for Unix.
|
||||
# (That is, all dependencies except perl + wxWidgets.)
|
||||
# The libraries are installed in DESTDIR, which you can customize like so:
|
||||
#
|
||||
# DESTDIR=foo/bar make
|
||||
#
|
||||
# The default DESTDIR is ~/slic3r-destdir
|
||||
# If the DESTDIR doesn't exits, the makefile tries to create it
|
||||
#
|
||||
# To pass the DESTDIR path along to cmake, set the use CMAKE_PREFIX_PATH variable
|
||||
# and set it to $DESTDIR/usr/local
|
||||
#
|
||||
# You can also customize the NPROC variable in the same way to configure the number
|
||||
# of cores the build process uses. By default this is set to what the `nproc` command says.
|
||||
#
|
||||
|
||||
|
||||
DESTDIR ?= $(HOME)/slic3r-destdir
|
||||
NPROC ?= $(shell nproc)
|
||||
|
||||
|
||||
BOOST = boost_1_66_0
|
||||
TBB_SHA = a0dc9bf76d0120f917b641ed095360448cabc85b
|
||||
TBB = tbb-$(TBB_SHA)
|
||||
OPENSSL = openssl-OpenSSL_1_1_0g
|
||||
CURL = curl-7.58.0
|
||||
|
||||
.PHONY: all destdir boost libcurl libopenssl libtbb
|
||||
|
||||
all: destdir boost libtbb libcurl
|
||||
@echo
|
||||
@echo "All done!"
|
||||
@echo
|
||||
|
||||
destdir:
|
||||
mkdir -p $(DESTDIR)
|
||||
|
||||
|
||||
|
||||
boost: $(BOOST).tar.gz
|
||||
tar -zxvf $(BOOST).tar.gz
|
||||
cd $(BOOST) && ./bootstrap.sh --with-libraries=system,filesystem,thread,log,locale,regex --prefix=$(DESTDIR)/usr/local
|
||||
cd $(BOOST) && ./b2 \
|
||||
-j $(NPROC) \
|
||||
link=static \
|
||||
variant=release \
|
||||
threading=multi \
|
||||
boost.locale.icu=off \
|
||||
cxxflags=-fPIC cflags=-fPIC \
|
||||
install
|
||||
|
||||
$(BOOST).tar.gz:
|
||||
curl -L -o $@ https://dl.bintray.com/boostorg/release/1.66.0/source/$@
|
||||
|
||||
|
||||
|
||||
libtbb: $(TBB).tar.gz
|
||||
tar -zxvf $(TBB).tar.gz
|
||||
mkdir -p $(TBB)/mybuild
|
||||
cd $(TBB)/mybuild && cmake .. -DTBB_BUILD_SHARED=OFF -DTBB_BUILD_TESTS=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
$(MAKE) -C $(TBB)/mybuild -j$(NPROC)
|
||||
$(MAKE) -C $(TBB)/mybuild install DESTDIR=$(DESTDIR)
|
||||
|
||||
$(TBB).tar.gz:
|
||||
curl -L -o $@ https://github.com/wjakob/tbb/archive/$(TBB_SHA).tar.gz
|
||||
|
||||
|
||||
# Note: libcurl build system seems to be a bit wonky about finding openssl (cf. #2378).
|
||||
# It seems that currently the only working option is to set a prefix in the openssl build
|
||||
# and use the `--with-ssl=...` option in libcurl.
|
||||
# Additionally, pkg-config needs to be installed and openssl libs need to NOT be installed on the build system.
|
||||
|
||||
libopenssl: $(OPENSSL).tar.gz
|
||||
tar -zxvf $(OPENSSL).tar.gz
|
||||
cd $(OPENSSL) && ./config --prefix=$(DESTDIR)/usr/local no-shared no-ssl3-method no-dynamic-engine '-Wa,--noexecstack'
|
||||
$(MAKE) -C $(OPENSSL) depend
|
||||
$(MAKE) -C $(OPENSSL) -j$(NPROC)
|
||||
$(MAKE) -C $(OPENSSL) install_sw
|
||||
|
||||
$(OPENSSL).tar.gz:
|
||||
curl -L -o $@ 'https://github.com/openssl/openssl/archive/OpenSSL_1_1_0g.tar.gz'
|
||||
|
||||
|
||||
|
||||
libcurl: libopenssl $(CURL).tar.gz
|
||||
tar -zxvf $(CURL).tar.gz
|
||||
cd $(CURL) && ./configure \
|
||||
--enable-static \
|
||||
--disable-shared \
|
||||
--with-ssl=$(DESTDIR)/usr/local \
|
||||
--with-pic \
|
||||
--enable-ipv6 \
|
||||
--enable-versioned-symbols \
|
||||
--enable-threaded-resolver \
|
||||
--with-random=/dev/urandom \
|
||||
--with-ca-bundle=/etc/ssl/certs/ca-certificates.crt \
|
||||
--disable-ldap \
|
||||
--disable-ldaps \
|
||||
--disable-manual \
|
||||
--disable-rtsp \
|
||||
--disable-dict \
|
||||
--disable-telnet \
|
||||
--disable-pop3 \
|
||||
--disable-imap \
|
||||
--disable-smb \
|
||||
--disable-smtp \
|
||||
--disable-gopher \
|
||||
--disable-crypto-auth \
|
||||
--without-gssapi \
|
||||
--without-libpsl \
|
||||
--without-libidn2 \
|
||||
--without-gnutls \
|
||||
--without-polarssl \
|
||||
--without-mbedtls \
|
||||
--without-cyassl \
|
||||
--without-nss \
|
||||
--without-axtls \
|
||||
--without-brotli \
|
||||
--without-libmetalink \
|
||||
--without-libssh \
|
||||
--without-libssh2 \
|
||||
--without-librtmp \
|
||||
--without-nghttp2 \
|
||||
--without-zsh-functions-dir
|
||||
$(MAKE) -C $(CURL) -j$(NPROC)
|
||||
$(MAKE) -C $(CURL) install DESTDIR=$(DESTDIR)
|
||||
|
||||
$(CURL).tar.gz:
|
||||
curl -L -o $@ https://curl.haxx.se/download/$@
|
||||
|
||||
|
||||
clean:
|
||||
rm -rf $(BOOST) $(BOOST).tar.gz $(TBB) $(TBB).tar.gz $(OPENSSL) $(OPENSSL).tar.gz $(CURL) $(CURL).tar.gz
|
||||
141
doc/deps-build/windows/slic3r-makedeps.ps1
Normal file
141
doc/deps-build/windows/slic3r-makedeps.ps1
Normal file
@@ -0,0 +1,141 @@
|
||||
#!powershell
|
||||
#
|
||||
# This script downloads, configures and builds Slic3r PE dependencies for Unix.
|
||||
# (That is, all dependencies except perl + wxWidgets.)
|
||||
#
|
||||
# To use this script, launch the Visual Studio command line,
|
||||
# `cd` into the directory containing this script and use this command:
|
||||
#
|
||||
# powershell .\slic3r-makedeps.ps1
|
||||
#
|
||||
# The dependencies will be downloaded and unpacked into the current dir.
|
||||
# This script WILL NOT try to guess the build architecture (64 vs 32 bits),
|
||||
# it will by default build the 64-bit variant. To build the 32-bit variant, use:
|
||||
#
|
||||
# powershell .\slic3r-makedeps.ps1 -b32
|
||||
#
|
||||
# Built libraries are installed into $destdir,
|
||||
# which by default is C:\local\slic3r-destdir-$bits
|
||||
# You can customize the $destdir using:
|
||||
#
|
||||
# powershell .\slic3r-makedeps.ps1 -destdir C:\foo\bar
|
||||
#
|
||||
# To pass the $destdir path along to cmake, set the use CMAKE_PREFIX_PATH variable
|
||||
# and set it to $destdir\usr\local
|
||||
#
|
||||
# Script requirements: PowerShell 3.0, .NET 4.5
|
||||
#
|
||||
|
||||
|
||||
param(
|
||||
[switch]$b32 = $false,
|
||||
[string]$destdir = ""
|
||||
)
|
||||
|
||||
if ($destdir -eq "") {
|
||||
$destdir = "C:\local\slic3r-destdir-" + ('32', '64')[!$b32]
|
||||
}
|
||||
|
||||
$BOOST = 'boost_1_63_0'
|
||||
$CURL = 'curl-7.58.0'
|
||||
$TBB_SHA = 'a0dc9bf76d0120f917b641ed095360448cabc85b'
|
||||
$TBB = "tbb-$TBB_SHA"
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
|
||||
# Set up various settings and utilities:
|
||||
[Environment]::CurrentDirectory = Get-Location
|
||||
$NPROC = (Get-WmiObject -class Win32_processor).NumberOfLogicalProcessors
|
||||
Add-Type -A System.IO.Compression.FileSystem
|
||||
# This fxies SSL/TLS errors, credit goes to Ansible; see their `win_get_url.ps1` file
|
||||
$security_protcols = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::SystemDefault
|
||||
if ([Net.SecurityProtocolType].GetMember('Tls11').Count -gt 0) {
|
||||
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls11
|
||||
}
|
||||
if ([Net.SecurityProtocolType].GetMember('Tls12').Count -gt 0) {
|
||||
$security_protcols = $security_protcols -bor [Net.SecurityProtocolType]::Tls12
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = $security_protcols
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
|
||||
|
||||
# Ensure DESTDIR exists:
|
||||
mkdir $destdir -ea 0
|
||||
mkdir "$destdir\usr\local" -ea 0
|
||||
|
||||
|
||||
# Download sources:
|
||||
echo 'Downloading sources ...'
|
||||
if (!(Test-Path "$BOOST.zip")) { $webclient.DownloadFile("https://dl.bintray.com/boostorg/release/1.63.0/source/$BOOST.zip", "$BOOST.zip") }
|
||||
if (!(Test-Path "$TBB.zip")) { $webclient.DownloadFile("https://github.com/wjakob/tbb/archive/$TBB_SHA.zip", "$TBB.zip") }
|
||||
if (!(Test-Path "$CURL.zip")) { $webclient.DownloadFile("https://curl.haxx.se/download/$CURL.zip", ".\$CURL.zip") }
|
||||
|
||||
|
||||
# Unpack sources:
|
||||
echo 'Unpacking ...'
|
||||
if (!(Test-Path $BOOST)) { [IO.Compression.ZipFile]::ExtractToDirectory("$BOOST.zip", '.') }
|
||||
if (!(Test-Path $TBB)) { [IO.Compression.ZipFile]::ExtractToDirectory("$TBB.zip", '.') }
|
||||
if (!(Test-Path $CURL)) { [IO.Compression.ZipFile]::ExtractToDirectory("$CURL.zip", '.') }
|
||||
|
||||
|
||||
# Build libraries:
|
||||
echo 'Building ...'
|
||||
|
||||
# Build boost
|
||||
pushd "$BOOST"
|
||||
.\bootstrap
|
||||
$adr_mode = ('32', '64')[!$b32]
|
||||
.\b2 `
|
||||
-j "$NPROC" `
|
||||
--with-system `
|
||||
--with-filesystem `
|
||||
--with-thread `
|
||||
--with-log `
|
||||
--with-locale `
|
||||
--with-regex `
|
||||
"--prefix=$destdir/usr/local" `
|
||||
"address-model=$adr_mode" `
|
||||
toolset=msvc-12.0 `
|
||||
link=static `
|
||||
variant=release `
|
||||
threading=multi `
|
||||
boost.locale.icu=off `
|
||||
install
|
||||
popd
|
||||
|
||||
# Build TBB
|
||||
pushd "$TBB"
|
||||
mkdir 'mybuild' -ea 0
|
||||
cd 'mybuild'
|
||||
$generator = ('Visual Studio 12', 'Visual Studio 12 Win64')[!$b32]
|
||||
cmake .. `
|
||||
-G "$generator" `
|
||||
-DCMAKE_CONFIGURATION_TYPES=Release `
|
||||
-DTBB_BUILD_SHARED=OFF `
|
||||
-DTBB_BUILD_TESTS=OFF "-DCMAKE_INSTALL_PREFIX:PATH=$destdir\usr\local"
|
||||
msbuild /P:Configuration=Release INSTALL.vcxproj
|
||||
popd
|
||||
|
||||
# Build libcurl:
|
||||
pushd "$CURL\winbuild"
|
||||
$machine = ("x86", "x64")[!$b32]
|
||||
nmake /f Makefile.vc mode=static VC=12 GEN_PDB=yes DEBUG=no "MACHINE=$machine"
|
||||
Copy-Item -R -Force ..\builds\libcurl-*-winssl\include\* "$destdir\usr\local\include\"
|
||||
Copy-Item -R -Force ..\builds\libcurl-*-winssl\lib\* "$destdir\usr\local\lib\"
|
||||
popd
|
||||
|
||||
|
||||
echo ""
|
||||
echo "All done!"
|
||||
echo ""
|
||||
|
||||
|
||||
}
|
||||
catch [Exception]
|
||||
{
|
||||
# This prints errors in a verbose manner
|
||||
echo $_.Exception|format-list -force
|
||||
}
|
||||
52
doc/updating/Updatig.md
Normal file
52
doc/updating/Updatig.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Slic3r PE 1.40 configuration update
|
||||
|
||||
Slic3r PE 1.40.0 comes with a major re-work of the way configuration presets work.
|
||||
There are three new features:
|
||||
|
||||
+ A two-tier system of presets being divided into _System_ and _User_ groups
|
||||
+ Configuration snapshots
|
||||
+ Configuration updating from the internet
|
||||
|
||||
## System and User presets
|
||||
|
||||
- _System preset_: These are the presets that come with Slic3r PE installation. They come from a vendor configuration bundle (not individual files like before). They are **read-only** – a user cannot modify them, but may instead create a derived User preset based on a System preset
|
||||
- _User preset_: These are regular presets stored in files just like before. Additionally, they may be derived (inherited) from one of the System presets
|
||||
|
||||
A derived User preset keeps track of wich settings are inherited from the parent System preset and which are modified by the user. When a system preset is updated (either via installation of a new Slic3r or automatically from the internet), in a User preset the settings that are modified by the user will stay that way, while the ones that are inherited reflect the updated System preset.
|
||||
|
||||
This system ensures that we don't overwrite user's settings when there is an update to the built in presets.
|
||||
|
||||
Slic3r GUI now displays accurately which settings are inherited and which are modified.
|
||||
A setting derived from a System preset is represented by green label and a locked lock icon:
|
||||
|
||||

|
||||
|
||||
A settings modified in a User preset has an open lock icon:
|
||||
|
||||

|
||||
|
||||
Clickign the open lock icon restored the system setting.
|
||||
|
||||
Additionaly, any setting that is modified but not yet saved onto disk is represented by orange label and a back-arrow:
|
||||
|
||||

|
||||
|
||||
Clicking the back-arrow restores the value that was previously saved in this Preset.
|
||||
|
||||
## Configuration snapshots
|
||||
|
||||
Configuration snapshots can now be taken via the _Configuration_ menu.
|
||||
A snapshot contains complete configuration from the point when the snapshot was taken.
|
||||
Users may move back and forth between snapshots at will using a dialog:
|
||||
|
||||

|
||||
|
||||
|
||||
# Updating from the internet
|
||||
|
||||
Slic3r PE 1.40.0 checks for updates of the built-in System presets and downloads them.
|
||||
The first-time configuration assistant will ask you if you want to enable this feature - it is **not** mandatory.
|
||||
|
||||
Updates are checked for and downloaded in the background. If there's is an update, Slic3r will prompt about it
|
||||
next time it is launched, never during normal program operation. An update may be either accepted or refused.
|
||||
Before any update is applied a configuration snapshot (as described above) is taken.
|
||||
BIN
doc/updating/setting_mod.png
Normal file
BIN
doc/updating/setting_mod.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
BIN
doc/updating/setting_sys.png
Normal file
BIN
doc/updating/setting_sys.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
doc/updating/setting_user.png
Normal file
BIN
doc/updating/setting_user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
BIN
doc/updating/snapshots_dialog.png
Normal file
BIN
doc/updating/snapshots_dialog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
249
lib/Slic3r.pm
249
lib/Slic3r.pm
@@ -1,3 +1,6 @@
|
||||
# This package loads all the non-GUI Slic3r perl packages.
|
||||
# In addition, it implements utility functions for file handling and threading.
|
||||
|
||||
package Slic3r;
|
||||
|
||||
# Copyright holder: Alessandro Ranellucci
|
||||
@@ -5,62 +8,54 @@ package Slic3r;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Config;
|
||||
require v5.10;
|
||||
|
||||
our $VERSION = "1.2.0";
|
||||
our $VERSION = VERSION();
|
||||
our $BUILD = BUILD();
|
||||
our $FORK_NAME = FORK_NAME();
|
||||
|
||||
our $debug = 0;
|
||||
sub debugf {
|
||||
printf @_ if $debug;
|
||||
}
|
||||
|
||||
our $loglevel = 0;
|
||||
|
||||
# load threads before Moo as required by it
|
||||
our $have_threads;
|
||||
BEGIN {
|
||||
# Test, whether the perl was compiled with ithreads support and ithreads actually work.
|
||||
use Config;
|
||||
$have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
|
||||
|
||||
### temporarily disable threads if using the broken Moo version
|
||||
use Moo;
|
||||
$have_threads = 0 if $Moo::VERSION == 1.003000;
|
||||
my $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1";
|
||||
die "Slic3r Prusa Edition requires working Perl threads.\n" if ! $have_threads;
|
||||
die "threads.pm >= 1.96 is required, please update\n" if $threads::VERSION < 1.96;
|
||||
die "Perl threading is broken with this Moo version: " . $Moo::VERSION . "\n" if $Moo::VERSION == 1.003000;
|
||||
$debug = 1 if (defined($ENV{'SLIC3R_DEBUGOUT'}) && $ENV{'SLIC3R_DEBUGOUT'} == 1);
|
||||
print "Debugging output enabled\n" if $debug;
|
||||
}
|
||||
|
||||
warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n"
|
||||
if $^V >= v5.16;
|
||||
warn "Running Slic3r under Perl 5.16 is neither supported nor recommended\n"
|
||||
if $^V == v5.16;
|
||||
|
||||
use FindBin;
|
||||
our $var = "$FindBin::Bin/var";
|
||||
|
||||
use Encode;
|
||||
use Encode::Locale;
|
||||
# Let the XS module know where the GUI resources reside.
|
||||
set_resources_dir(decode_path($FindBin::Bin) . (($^O eq 'darwin') ? '/../Resources' : '/resources'));
|
||||
set_var_dir(resources_dir() . "/icons");
|
||||
set_local_dir(resources_dir() . "/localization/");
|
||||
|
||||
use Moo 1.003001;
|
||||
|
||||
use Slic3r::XS; # import all symbols (constants etc.) before they get parsed
|
||||
use Slic3r::Config;
|
||||
use Slic3r::ExPolygon;
|
||||
use Slic3r::Extruder;
|
||||
use Slic3r::ExtrusionLoop;
|
||||
use Slic3r::ExtrusionPath;
|
||||
use Slic3r::ExtrusionPath::Collection;
|
||||
use Slic3r::Fill;
|
||||
use Slic3r::Flow;
|
||||
use Slic3r::Format::AMF;
|
||||
use Slic3r::Format::OBJ;
|
||||
use Slic3r::Format::STL;
|
||||
use Slic3r::GCode;
|
||||
use Slic3r::GCode::ArcFitting;
|
||||
use Slic3r::GCode::CoolingBuffer;
|
||||
use Slic3r::GCode::Layer;
|
||||
use Slic3r::GCode::MotionPlanner;
|
||||
use Slic3r::GCode::PlaceholderParser;
|
||||
use Slic3r::GCode::Reader;
|
||||
use Slic3r::GCode::SpiralVase;
|
||||
use Slic3r::GCode::VibrationLimit;
|
||||
use Slic3r::Geometry qw(PI);
|
||||
use Slic3r::Geometry::Clipper;
|
||||
use Slic3r::Layer;
|
||||
use Slic3r::Layer::BridgeDetector;
|
||||
use Slic3r::Layer::Region;
|
||||
use Slic3r::Line;
|
||||
use Slic3r::Model;
|
||||
use Slic3r::Point;
|
||||
@@ -69,38 +64,40 @@ use Slic3r::Polyline;
|
||||
use Slic3r::Print;
|
||||
use Slic3r::Print::Object;
|
||||
use Slic3r::Print::Simple;
|
||||
use Slic3r::Print::SupportMaterial;
|
||||
use Slic3r::Surface;
|
||||
our $build = eval "use Slic3r::Build; 1";
|
||||
use Thread::Semaphore;
|
||||
|
||||
# Scaling between the float and integer coordinates.
|
||||
# Floats are in mm.
|
||||
use constant SCALING_FACTOR => 0.000001;
|
||||
use constant RESOLUTION => 0.0125;
|
||||
use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR;
|
||||
use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI;
|
||||
use constant LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER => 0.15;
|
||||
use constant INFILL_OVERLAP_OVER_SPACING => 0.45;
|
||||
use constant EXTERNAL_INFILL_MARGIN => 3;
|
||||
use constant INSET_OVERLAP_TOLERANCE => 0.2;
|
||||
|
||||
# keep track of threads we created
|
||||
my @threads : shared = ();
|
||||
my $sema = Thread::Semaphore->new;
|
||||
# Keep track of threads we created. Perl worker threads shall not create further threads.
|
||||
my @threads = ();
|
||||
my $pause_sema = Thread::Semaphore->new;
|
||||
my $paused = 0;
|
||||
|
||||
# Set the logging level at the Slic3r XS module.
|
||||
$Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0;
|
||||
set_logging_level($Slic3r::loglevel);
|
||||
|
||||
# Let the palceholder parser evaluate one expression to initialize its local static macro_processor
|
||||
# class instance in a thread safe manner.
|
||||
Slic3r::GCode::PlaceholderParser->new->evaluate_boolean_expression('1==1');
|
||||
|
||||
sub spawn_thread {
|
||||
my ($cb) = @_;
|
||||
|
||||
@_ = ();
|
||||
my $thread = threads->create(sub {
|
||||
Slic3r::debugf "Starting thread %d...\n", threads->tid;
|
||||
local $SIG{'KILL'} = sub {
|
||||
Slic3r::debugf "Exiting thread...\n";
|
||||
Slic3r::debugf "Exiting thread %d...\n", threads->tid;
|
||||
Slic3r::thread_cleanup();
|
||||
threads->exit();
|
||||
};
|
||||
local $SIG{'STOP'} = sub {
|
||||
$sema->down;
|
||||
$sema->up;
|
||||
$pause_sema->down;
|
||||
$pause_sema->up;
|
||||
};
|
||||
$cb->();
|
||||
});
|
||||
@@ -108,45 +105,6 @@ sub spawn_thread {
|
||||
return $thread;
|
||||
}
|
||||
|
||||
sub parallelize {
|
||||
my %params = @_;
|
||||
|
||||
if (!$params{disable} && $Slic3r::have_threads && $params{threads} > 1) {
|
||||
my @items = (ref $params{items} eq 'CODE') ? $params{items}->() : @{$params{items}};
|
||||
my $q = Thread::Queue->new;
|
||||
$q->enqueue(@items, (map undef, 1..$params{threads}));
|
||||
|
||||
my $thread_cb = sub {
|
||||
# execute thread callback
|
||||
$params{thread_cb}->($q);
|
||||
|
||||
# cleanup before terminating thread
|
||||
Slic3r::thread_cleanup();
|
||||
|
||||
# This explicit exit avoids an untrappable
|
||||
# "Attempt to free unreferenced scalar" error
|
||||
# triggered on Ubuntu 12.04 32-bit when we're running
|
||||
# from the Wx plater and
|
||||
# we're reusing the same plater object more than once.
|
||||
# The downside to using this exit is that we can't return
|
||||
# any value to the main thread but we're not doing that
|
||||
# anymore anyway.
|
||||
# collect_cb is completely useless now
|
||||
# and should be removed from the codebase.
|
||||
threads->exit;
|
||||
};
|
||||
$params{collect_cb} ||= sub {};
|
||||
|
||||
@_ = ();
|
||||
my @my_threads = map spawn_thread($thread_cb), 1..$params{threads};
|
||||
foreach my $th (@my_threads) {
|
||||
$params{collect_cb}->($th->join);
|
||||
}
|
||||
} else {
|
||||
$params{no_threads_cb}->();
|
||||
}
|
||||
}
|
||||
|
||||
# call this at the very end of each thread (except the main one)
|
||||
# so that it does not try to free existing objects.
|
||||
# at that stage, existing objects are only those that we
|
||||
@@ -159,27 +117,34 @@ sub parallelize {
|
||||
# object in a thread, make sure the main thread still holds a
|
||||
# reference so that it won't be destroyed in thread.
|
||||
sub thread_cleanup {
|
||||
return if !$Slic3r::have_threads;
|
||||
|
||||
# prevent destruction of shared objects
|
||||
no warnings 'redefine';
|
||||
*Slic3r::BridgeDetector::DESTROY = sub {};
|
||||
*Slic3r::Config::DESTROY = sub {};
|
||||
*Slic3r::Config::Full::DESTROY = sub {};
|
||||
*Slic3r::Config::GCode::DESTROY = sub {};
|
||||
*Slic3r::Config::Print::DESTROY = sub {};
|
||||
*Slic3r::Config::PrintObject::DESTROY = sub {};
|
||||
*Slic3r::Config::PrintRegion::DESTROY = sub {};
|
||||
*Slic3r::Config::Static::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::DESTROY = sub {};
|
||||
*Slic3r::ExPolygon::Collection::DESTROY = sub {};
|
||||
*Slic3r::Extruder::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionLoop::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionMultiPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionPath::Collection::DESTROY = sub {};
|
||||
*Slic3r::ExtrusionSimulator::DESTROY = sub {};
|
||||
*Slic3r::Flow::DESTROY = sub {};
|
||||
*Slic3r::GCode::DESTROY = sub {};
|
||||
*Slic3r::GCode::PlaceholderParser::DESTROY = sub {};
|
||||
*Slic3r::GCode::PreviewData::DESTROY = sub {};
|
||||
*Slic3r::GCode::Sender::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBox::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf::DESTROY = sub {};
|
||||
*Slic3r::Geometry::BoundingBoxf3::DESTROY = sub {};
|
||||
*Slic3r::Layer::PerimeterGenerator::DESTROY = sub {};
|
||||
*Slic3r::Line::DESTROY = sub {};
|
||||
*Slic3r::Linef3::DESTROY = sub {};
|
||||
*Slic3r::Model::DESTROY = sub {};
|
||||
*Slic3r::Model::Object::DESTROY = sub {};
|
||||
*Slic3r::Point::DESTROY = sub {};
|
||||
@@ -189,57 +154,135 @@ sub thread_cleanup {
|
||||
*Slic3r::Polyline::DESTROY = sub {};
|
||||
*Slic3r::Polyline::Collection::DESTROY = sub {};
|
||||
*Slic3r::Print::DESTROY = sub {};
|
||||
*Slic3r::Print::Object::DESTROY = sub {};
|
||||
*Slic3r::Print::Region::DESTROY = sub {};
|
||||
*Slic3r::Surface::DESTROY = sub {};
|
||||
*Slic3r::Surface::Collection::DESTROY = sub {};
|
||||
*Slic3r::Print::SupportMaterial2::DESTROY = sub {};
|
||||
*Slic3r::TriangleMesh::DESTROY = sub {};
|
||||
*Slic3r::GUI::AppConfig::DESTROY = sub {};
|
||||
*Slic3r::GUI::GCodePreviewData::DESTROY = sub {};
|
||||
*Slic3r::GUI::PresetBundle::DESTROY = sub {};
|
||||
*Slic3r::GUI::Tab::DESTROY = sub {};
|
||||
*Slic3r::GUI::PresetHints::DESTROY = sub {};
|
||||
*Slic3r::GUI::TabIface::DESTROY = sub {};
|
||||
*Slic3r::OctoPrint::DESTROY = sub {};
|
||||
*Slic3r::PresetUpdater::DESTROY = sub {};
|
||||
return undef; # this prevents a "Scalars leaked" warning
|
||||
}
|
||||
|
||||
sub get_running_threads {
|
||||
sub _get_running_threads {
|
||||
return grep defined($_), map threads->object($_), @threads;
|
||||
}
|
||||
|
||||
sub kill_all_threads {
|
||||
# detach any running thread created in the current one
|
||||
my @killed = ();
|
||||
foreach my $thread (get_running_threads()) {
|
||||
# Send SIGKILL to all the running threads to let them die.
|
||||
foreach my $thread (_get_running_threads) {
|
||||
Slic3r::debugf "Thread %d killing %d...\n", threads->tid, $thread->tid;
|
||||
$thread->kill('KILL');
|
||||
push @killed, $thread;
|
||||
}
|
||||
|
||||
# unlock semaphore before we block on wait
|
||||
# otherwise we'd get a deadlock if threads were paused
|
||||
resume_threads();
|
||||
$_->join for @killed; # block until threads are killed
|
||||
resume_all_threads();
|
||||
# in any thread we wait for our children
|
||||
foreach my $thread (_get_running_threads) {
|
||||
Slic3r::debugf " Thread %d waiting for %d...\n", threads->tid, $thread->tid;
|
||||
$thread->join; # block until threads are killed
|
||||
Slic3r::debugf " Thread %d finished waiting for %d...\n", threads->tid, $thread->tid;
|
||||
}
|
||||
@threads = ();
|
||||
}
|
||||
|
||||
sub pause_threads {
|
||||
sub pause_all_threads {
|
||||
return if $paused;
|
||||
$paused = 1;
|
||||
$sema->down;
|
||||
$_->kill('STOP') for get_running_threads();
|
||||
$pause_sema->down;
|
||||
$_->kill('STOP') for _get_running_threads;
|
||||
}
|
||||
|
||||
sub resume_threads {
|
||||
sub resume_all_threads {
|
||||
return unless $paused;
|
||||
$paused = 0;
|
||||
$sema->up;
|
||||
}
|
||||
|
||||
sub encode_path {
|
||||
my ($filename) = @_;
|
||||
return encode('locale_fs', $filename);
|
||||
$pause_sema->up;
|
||||
}
|
||||
|
||||
# Open a file by converting $filename to local file system locales.
|
||||
sub open {
|
||||
my ($fh, $mode, $filename) = @_;
|
||||
return CORE::open $$fh, $mode, encode_path($filename);
|
||||
}
|
||||
|
||||
sub tags {
|
||||
my ($format) = @_;
|
||||
$format //= '';
|
||||
my %tags;
|
||||
# End of line
|
||||
$tags{eol} = ($format eq 'html') ? '<br>' : "\n";
|
||||
# Heading
|
||||
$tags{h2start} = ($format eq 'html') ? '<b>' : '';
|
||||
$tags{h2end} = ($format eq 'html') ? '</b>' : '';
|
||||
# Bold font
|
||||
$tags{bstart} = ($format eq 'html') ? '<b>' : '';
|
||||
$tags{bend} = ($format eq 'html') ? '</b>' : '';
|
||||
# Verbatim
|
||||
$tags{vstart} = ($format eq 'html') ? '<pre>' : '';
|
||||
$tags{vend} = ($format eq 'html') ? '</pre>' : '';
|
||||
return %tags;
|
||||
}
|
||||
|
||||
sub slic3r_info
|
||||
{
|
||||
my (%params) = @_;
|
||||
my %tag = Slic3r::tags($params{format});
|
||||
my $out = '';
|
||||
$out .= "$tag{bstart}$Slic3r::FORK_NAME$tag{bend}$tag{eol}";
|
||||
$out .= "$tag{bstart}Version: $tag{bend}$Slic3r::VERSION$tag{eol}";
|
||||
$out .= "$tag{bstart}Build: $tag{bend}$Slic3r::BUILD$tag{eol}";
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub copyright_info
|
||||
{
|
||||
my (%params) = @_;
|
||||
my %tag = Slic3r::tags($params{format});
|
||||
my $out =
|
||||
'Copyright © 2016 Vojtech Bubnik, Prusa Research. <br />' .
|
||||
'Copyright © 2011-2016 Alessandro Ranellucci. <br />' .
|
||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
||||
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||
'<br /><br /><br />' .
|
||||
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' .
|
||||
'Manual by Gary Hodgson. Inspired by the RepRap community. <br />' .
|
||||
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ';
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub system_info
|
||||
{
|
||||
my (%params) = @_;
|
||||
my %tag = Slic3r::tags($params{format});
|
||||
|
||||
my $out = '';
|
||||
$out .= "$tag{bstart}Operating System: $tag{bend}$Config{osname}$tag{eol}";
|
||||
$out .= "$tag{bstart}System Architecture: $tag{bend}$Config{archname}$tag{eol}";
|
||||
if ($^O eq 'MSWin32') {
|
||||
$out .= "$tag{bstart}Windows Version: $tag{bend}" . `ver` . $tag{eol};
|
||||
} else {
|
||||
# Hopefully some kind of unix / linux.
|
||||
$out .= "$tag{bstart}System Version: $tag{bend}" . `uname -a` . $tag{eol};
|
||||
}
|
||||
$out .= $tag{vstart} . Config::myconfig . $tag{vend};
|
||||
$out .= " $tag{bstart}\@INC:$tag{bend}$tag{eol}$tag{vstart}";
|
||||
foreach my $i (@INC) {
|
||||
$out .= " $i\n";
|
||||
}
|
||||
$out .= "$tag{vend}";
|
||||
return $out;
|
||||
}
|
||||
|
||||
# this package declaration prevents an ugly fatal warning to be emitted when
|
||||
# spawning a new thread
|
||||
package GLUquadricObjPtr;
|
||||
package Wx::Printout;
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Extends C++ class Slic3r::DynamicPrintConfig
|
||||
# This perl class does not keep any perl class variables,
|
||||
# all the storage is handled by the underlying C++ code.
|
||||
package Slic3r::Config;
|
||||
use strict;
|
||||
use warnings;
|
||||
@@ -5,45 +8,31 @@ use utf8;
|
||||
|
||||
use List::Util qw(first max);
|
||||
|
||||
# cemetery of old config settings
|
||||
our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration
|
||||
adjust_overhang_flow standby_temperature scale rotate duplicate duplicate_grid
|
||||
rotate scale duplicate_grid start_perimeters_at_concave_points start_perimeters_at_non_overhang
|
||||
randomize_start seal_position bed_size print_center);
|
||||
|
||||
# C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
|
||||
# The C++ counterpart is a constant singleton.
|
||||
our $Options = print_config_def();
|
||||
|
||||
# overwrite the hard-coded readonly value (this information is not available in XS)
|
||||
$Options->{threads}{readonly} = !$Slic3r::have_threads;
|
||||
|
||||
# generate accessors
|
||||
# Generate accessors.
|
||||
{
|
||||
no strict 'refs';
|
||||
for my $opt_key (keys %$Options) {
|
||||
*{$opt_key} = sub { $_[0]->get($opt_key) };
|
||||
*{$opt_key} = sub {
|
||||
#print "Slic3r::Config::accessor $opt_key\n";
|
||||
$_[0]->get($opt_key)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub new_from_defaults {
|
||||
my $class = shift;
|
||||
my (@opt_keys) = @_;
|
||||
|
||||
my $self = $class->new;
|
||||
my $defaults = Slic3r::Config::Full->new;
|
||||
if (@opt_keys) {
|
||||
$self->set($_, $defaults->get($_)) for @opt_keys;
|
||||
} else {
|
||||
$self->apply_static($defaults);
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
# From command line parameters, used by slic3r.pl
|
||||
sub new_from_cli {
|
||||
my $class = shift;
|
||||
my %args = @_;
|
||||
|
||||
# Delete hash keys with undefined value.
|
||||
delete $args{$_} for grep !defined $args{$_}, keys %args;
|
||||
|
||||
# Replace the start_gcode, end_gcode ... hash values
|
||||
# with the content of the files they reference.
|
||||
for (qw(start end layer toolchange)) {
|
||||
my $opt_key = "${_}_gcode";
|
||||
if ($args{$opt_key}) {
|
||||
@@ -56,7 +45,7 @@ sub new_from_cli {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my $self = $class->new;
|
||||
foreach my $opt_key (keys %args) {
|
||||
my $opt_def = $Options->{$opt_key};
|
||||
@@ -75,373 +64,13 @@ sub new_from_cli {
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub merge {
|
||||
my $class = shift;
|
||||
my $config = $class->new;
|
||||
$config->apply($_) for @_;
|
||||
return $config;
|
||||
}
|
||||
|
||||
sub load {
|
||||
my $class = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
my $ini = __PACKAGE__->read_ini($file);
|
||||
return $class->load_ini_hash($ini->{_});
|
||||
}
|
||||
|
||||
sub load_ini_hash {
|
||||
my $class = shift;
|
||||
my ($ini_hash) = @_;
|
||||
|
||||
my $config = $class->new;
|
||||
foreach my $opt_key (keys %$ini_hash) {
|
||||
($opt_key, my $value) = _handle_legacy($opt_key, $ini_hash->{$opt_key});
|
||||
next if !defined $opt_key;
|
||||
$config->set_deserialize($opt_key, $value);
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
sub clone {
|
||||
my $self = shift;
|
||||
|
||||
my $new = (ref $self)->new;
|
||||
$new->apply($self);
|
||||
return $new;
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my $self = shift;
|
||||
my ($opt_key) = @_;
|
||||
|
||||
return $Options->{$opt_key}{ratio_over}
|
||||
? $self->get_abs_value($opt_key)
|
||||
: $self->get($opt_key);
|
||||
}
|
||||
|
||||
sub _handle_legacy {
|
||||
my ($opt_key, $value) = @_;
|
||||
|
||||
# handle legacy options
|
||||
if ($opt_key =~ /^(extrusion_width|bottom_layer_speed|first_layer_height)_ratio$/) {
|
||||
$opt_key = $1;
|
||||
$opt_key =~ s/^bottom_layer_speed$/first_layer_speed/;
|
||||
$value = $value =~ /^\d+(?:\.\d+)?$/ && $value != 0 ? ($value*100) . "%" : 0;
|
||||
}
|
||||
if ($opt_key eq 'threads' && !$Slic3r::have_threads) {
|
||||
$value = 1;
|
||||
}
|
||||
if ($opt_key eq 'gcode_flavor' && $value eq 'makerbot') {
|
||||
$value = 'makerware';
|
||||
}
|
||||
if ($opt_key eq 'fill_density' && defined($value) && $value !~ /%/ && $value <= 1) {
|
||||
# fill_density was turned into a percent value
|
||||
$value *= 100;
|
||||
$value = "$value"; # force update of the PV value, workaround for bug https://rt.cpan.org/Ticket/Display.html?id=94110
|
||||
}
|
||||
if ($opt_key eq 'randomize_start' && $value) {
|
||||
$opt_key = 'seam_position';
|
||||
$value = 'random';
|
||||
}
|
||||
if ($opt_key eq 'bed_size' && $value) {
|
||||
$opt_key = 'bed_shape';
|
||||
my ($x, $y) = split /,/, $value;
|
||||
$value = "0x0,${x}x0,${x}x${y},0x${y}";
|
||||
}
|
||||
return () if first { $_ eq $opt_key } @Ignore;
|
||||
|
||||
# For historical reasons, the world's full of configs having these very low values;
|
||||
# to avoid unexpected behavior we need to ignore them. Banning these two hard-coded
|
||||
# values is a dirty hack and will need to be removed sometime in the future, but it
|
||||
# will avoid lots of complaints for now.
|
||||
if ($opt_key eq 'perimeter_acceleration' && $value == '25') {
|
||||
$value = 0;
|
||||
}
|
||||
if ($opt_key eq 'infill_acceleration' && $value == '50') {
|
||||
$value = 0;
|
||||
}
|
||||
|
||||
if (!exists $Options->{$opt_key}) {
|
||||
my @keys = grep { $Options->{$_}{aliases} && grep $_ eq $opt_key, @{$Options->{$_}{aliases}} } keys %$Options;
|
||||
if (!@keys) {
|
||||
warn "Unknown option $opt_key\n";
|
||||
return ();
|
||||
}
|
||||
$opt_key = $keys[0];
|
||||
}
|
||||
|
||||
return ($opt_key, $value);
|
||||
}
|
||||
|
||||
sub set_ifndef {
|
||||
my $self = shift;
|
||||
my ($opt_key, $value, $deserialize) = @_;
|
||||
|
||||
if (!$self->has($opt_key)) {
|
||||
if ($deserialize) {
|
||||
$self->set_deserialize($opt_key, $value);
|
||||
} else {
|
||||
$self->set($opt_key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub as_ini {
|
||||
my ($self) = @_;
|
||||
|
||||
my $ini = { _ => {} };
|
||||
foreach my $opt_key (sort @{$self->get_keys}) {
|
||||
next if $Options->{$opt_key}{shortcut};
|
||||
$ini->{_}{$opt_key} = $self->serialize($opt_key);
|
||||
}
|
||||
return $ini;
|
||||
}
|
||||
|
||||
sub save {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
__PACKAGE__->write_ini($file, $self->as_ini);
|
||||
}
|
||||
|
||||
sub setenv {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $opt_key (@{$self->get_keys}) {
|
||||
$ENV{"SLIC3R_" . uc $opt_key} = $self->serialize($opt_key);
|
||||
}
|
||||
}
|
||||
|
||||
sub equals {
|
||||
my ($self, $other) = @_;
|
||||
return @{ $self->diff($other) } == 0;
|
||||
}
|
||||
|
||||
# this will *ignore* options not present in both configs
|
||||
sub diff {
|
||||
my ($self, $other) = @_;
|
||||
|
||||
my @diff = ();
|
||||
foreach my $opt_key (sort @{$self->get_keys}) {
|
||||
push @diff, $opt_key
|
||||
if $other->has($opt_key) && $other->serialize($opt_key) ne $self->serialize($opt_key);
|
||||
}
|
||||
return [@diff];
|
||||
}
|
||||
|
||||
# this method is idempotent by design and only applies to ::DynamicConfig or ::Full
|
||||
# objects because it performs cross checks
|
||||
sub validate {
|
||||
my $self = shift;
|
||||
|
||||
# -j, --threads
|
||||
die "Invalid value for --threads\n"
|
||||
if $self->threads < 1;
|
||||
|
||||
# --layer-height
|
||||
die "Invalid value for --layer-height\n"
|
||||
if $self->layer_height <= 0;
|
||||
die "--layer-height must be a multiple of print resolution\n"
|
||||
if $self->layer_height / &Slic3r::SCALING_FACTOR % 1 != 0;
|
||||
|
||||
# --first-layer-height
|
||||
die "Invalid value for --first-layer-height\n"
|
||||
if $self->first_layer_height !~ /^(?:\d*(?:\.\d+)?)%?$/;
|
||||
|
||||
# --filament-diameter
|
||||
die "Invalid value for --filament-diameter\n"
|
||||
if grep $_ < 1, @{$self->filament_diameter};
|
||||
|
||||
# --nozzle-diameter
|
||||
die "Invalid value for --nozzle-diameter\n"
|
||||
if grep $_ < 0, @{$self->nozzle_diameter};
|
||||
|
||||
# --perimeters
|
||||
die "Invalid value for --perimeters\n"
|
||||
if $self->perimeters < 0;
|
||||
|
||||
# --solid-layers
|
||||
die "Invalid value for --solid-layers\n" if defined $self->solid_layers && $self->solid_layers < 0;
|
||||
die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0;
|
||||
die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0;
|
||||
|
||||
# --gcode-flavor
|
||||
die "Invalid value for --gcode-flavor\n"
|
||||
if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}};
|
||||
|
||||
die "--use-firmware-retraction is only supported by Marlin firmware\n"
|
||||
if $self->use_firmware_retraction && $self->gcode_flavor ne 'reprap';
|
||||
|
||||
die "--use-firmware-retraction is not compatible with --wipe\n"
|
||||
if $self->use_firmware_retraction && first {$_} @{$self->wipe};
|
||||
|
||||
# --fill-pattern
|
||||
die "Invalid value for --fill-pattern\n"
|
||||
if !first { $_ eq $self->fill_pattern } @{$Options->{fill_pattern}{values}};
|
||||
|
||||
# --solid-fill-pattern
|
||||
die "Invalid value for --solid-fill-pattern\n"
|
||||
if !first { $_ eq $self->solid_fill_pattern } @{$Options->{solid_fill_pattern}{values}};
|
||||
|
||||
# --fill-density
|
||||
die "The selected fill pattern is not supposed to work at 100% density\n"
|
||||
if $self->fill_density == 100
|
||||
&& !first { $_ eq $self->fill_pattern } @{$Options->{solid_fill_pattern}{values}};
|
||||
|
||||
# --infill-every-layers
|
||||
die "Invalid value for --infill-every-layers\n"
|
||||
if $self->infill_every_layers !~ /^\d+$/ || $self->infill_every_layers < 1;
|
||||
|
||||
# --skirt-height
|
||||
die "Invalid value for --skirt-height\n"
|
||||
if $self->skirt_height < -1; # -1 means as tall as the object
|
||||
|
||||
# --bridge-flow-ratio
|
||||
die "Invalid value for --bridge-flow-ratio\n"
|
||||
if $self->bridge_flow_ratio <= 0;
|
||||
|
||||
# extruder clearance
|
||||
die "Invalid value for --extruder-clearance-radius\n"
|
||||
if $self->extruder_clearance_radius <= 0;
|
||||
die "Invalid value for --extruder-clearance-height\n"
|
||||
if $self->extruder_clearance_height <= 0;
|
||||
|
||||
# --extrusion-multiplier
|
||||
die "Invalid value for --extrusion-multiplier\n"
|
||||
if defined first { $_ <= 0 } @{$self->extrusion_multiplier};
|
||||
|
||||
# --default-acceleration
|
||||
die "Invalid zero value for --default-acceleration when using other acceleration settings\n"
|
||||
if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration)
|
||||
&& !$self->default_acceleration;
|
||||
|
||||
# --spiral-vase
|
||||
if ($self->spiral_vase) {
|
||||
# Note that we might want to have more than one perimeter on the bottom
|
||||
# solid layers.
|
||||
die "Can't make more than one perimeter when spiral vase mode is enabled\n"
|
||||
if $self->perimeters > 1;
|
||||
|
||||
die "Can't make less than one perimeter when spiral vase mode is enabled\n"
|
||||
if $self->perimeters < 1;
|
||||
|
||||
die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n"
|
||||
if $self->fill_density > 0;
|
||||
|
||||
die "Spiral vase mode is not compatible with top solid layers\n"
|
||||
if $self->top_solid_layers > 0;
|
||||
|
||||
die "Spiral vase mode is not compatible with support material\n"
|
||||
if $self->support_material || $self->support_material_enforce_layers > 0;
|
||||
}
|
||||
|
||||
# extrusion widths
|
||||
{
|
||||
my $max_nozzle_diameter = max(@{ $self->nozzle_diameter });
|
||||
die "Invalid extrusion width (too large)\n"
|
||||
if defined first { $_ > 10 * $max_nozzle_diameter }
|
||||
map $self->get_abs_value_over("${_}_extrusion_width", $self->layer_height),
|
||||
qw(perimeter infill solid_infill top_infill support_material first_layer);
|
||||
}
|
||||
|
||||
# general validation, quick and dirty
|
||||
foreach my $opt_key (@{$self->get_keys}) {
|
||||
my $opt = $Options->{$opt_key};
|
||||
next unless defined $self->$opt_key;
|
||||
next unless defined $opt->{cli} && $opt->{cli} =~ /=(.+)$/;
|
||||
my $type = $1;
|
||||
my @values = ();
|
||||
if ($type =~ s/\@$//) {
|
||||
die "Invalid value for $opt_key\n" if ref($self->$opt_key) ne 'ARRAY';
|
||||
@values = @{ $self->$opt_key };
|
||||
} else {
|
||||
@values = ($self->$opt_key);
|
||||
}
|
||||
foreach my $value (@values) {
|
||||
if ($type eq 'i' || $type eq 'f' || $opt->{type} eq 'percent') {
|
||||
$value =~ s/%$// if $opt->{type} eq 'percent';
|
||||
die "Invalid value for $opt_key\n"
|
||||
if ($type eq 'i' && $value !~ /^-?\d+$/)
|
||||
|| (($type eq 'f' || $opt->{type} eq 'percent') && $value !~ /^-?(?:\d+|\d*\.\d+)$/)
|
||||
|| (defined $opt->{min} && $value < $opt->{min})
|
||||
|| (defined $opt->{max} && $value > $opt->{max});
|
||||
} elsif ($type eq 's' && $opt->{type} eq 'select') {
|
||||
die "Invalid value for $opt_key\n"
|
||||
unless first { $_ eq $value } @{ $opt->{values} };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# min object distance is max(duplicate_distance, clearance_radius)
|
||||
sub min_object_distance {
|
||||
my $self = shift;
|
||||
|
||||
return ($self->complete_objects && $self->extruder_clearance_radius > $self->duplicate_distance)
|
||||
? $self->extruder_clearance_radius
|
||||
: $self->duplicate_distance;
|
||||
}
|
||||
|
||||
# CLASS METHODS:
|
||||
|
||||
sub write_ini {
|
||||
my $class = shift;
|
||||
my ($file, $ini) = @_;
|
||||
|
||||
Slic3r::open(\my $fh, '>', $file);
|
||||
binmode $fh, ':utf8';
|
||||
my $localtime = localtime;
|
||||
printf $fh "# generated by Slic3r $Slic3r::VERSION on %s\n", "$localtime";
|
||||
# make sure the _ category is the first one written
|
||||
foreach my $category (sort { ($a eq '_') ? -1 : ($a cmp $b) } keys %$ini) {
|
||||
printf $fh "\n[%s]\n", $category if $category ne '_';
|
||||
foreach my $key (sort keys %{$ini->{$category}}) {
|
||||
printf $fh "%s = %s\n", $key, $ini->{$category}{$key};
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
|
||||
sub read_ini {
|
||||
my $class = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
local $/ = "\n";
|
||||
Slic3r::open(\my $fh, '<', $file);
|
||||
binmode $fh, ':utf8';
|
||||
|
||||
my $ini = { _ => {} };
|
||||
my $category = '_';
|
||||
while (<$fh>) {
|
||||
s/\R+$//;
|
||||
next if /^\s+/;
|
||||
next if /^$/;
|
||||
next if /^\s*#/;
|
||||
if (/^\[(.+?)\]$/) {
|
||||
$category = $1;
|
||||
next;
|
||||
}
|
||||
/^(\w+) = (.*)/ or die "Unreadable configuration file (invalid data at line $.)\n";
|
||||
$ini->{$category}{$1} = $2;
|
||||
}
|
||||
close $fh;
|
||||
|
||||
return $ini;
|
||||
}
|
||||
|
||||
package Slic3r::Config::Print;
|
||||
package Slic3r::Config::Static;
|
||||
use parent 'Slic3r::Config';
|
||||
|
||||
package Slic3r::Config::PrintObject;
|
||||
use parent 'Slic3r::Config';
|
||||
|
||||
package Slic3r::Config::PrintRegion;
|
||||
use parent 'Slic3r::Config';
|
||||
|
||||
package Slic3r::Config::Full;
|
||||
use parent 'Slic3r::Config';
|
||||
sub Slic3r::Config::GCode::new { Slic3r::Config::Static::new_GCodeConfig }
|
||||
sub Slic3r::Config::Print::new { Slic3r::Config::Static::new_PrintConfig }
|
||||
sub Slic3r::Config::PrintObject::new { Slic3r::Config::Static::new_PrintObjectConfig }
|
||||
sub Slic3r::Config::PrintRegion::new { Slic3r::Config::Static::new_PrintRegionConfig }
|
||||
sub Slic3r::Config::Full::new { Slic3r::Config::Static::new_FullPrintConfig }
|
||||
|
||||
1;
|
||||
|
||||
@@ -5,7 +5,6 @@ use warnings;
|
||||
# an ExPolygon is a polygon with holes
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(X Y A B point_in_polygon epsilon scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex diff_pl);
|
||||
|
||||
sub wkt {
|
||||
@@ -43,7 +42,6 @@ sub bounding_box {
|
||||
}
|
||||
|
||||
package Slic3r::ExPolygon::Collection;
|
||||
use Slic3r::Geometry qw(X1 Y1);
|
||||
|
||||
sub size {
|
||||
my $self = shift;
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package Slic3r::Extruder;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(EXTRUDER_ROLE_PERIMETER EXTRUDER_ROLE_INFILL EXTRUDER_ROLE_SUPPORT_MATERIAL
|
||||
EXTRUDER_ROLE_SUPPORT_MATERIAL_INTERFACE);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
use Slic3r::Geometry qw(PI scale);
|
||||
|
||||
# has 'e_per_mm3' => (is => 'lazy');
|
||||
# has 'retract_speed_mm_min' => (is => 'lazy');
|
||||
|
||||
use constant EXTRUDER_ROLE_PERIMETER => 1;
|
||||
use constant EXTRUDER_ROLE_INFILL => 2;
|
||||
use constant EXTRUDER_ROLE_SUPPORT_MATERIAL => 3;
|
||||
use constant EXTRUDER_ROLE_SUPPORT_MATERIAL_INTERFACE => 4;
|
||||
|
||||
|
||||
sub e_per_mm3 {
|
||||
my $self = shift;
|
||||
return $self->extrusion_multiplier * (4 / (($self->filament_diameter ** 2) * PI));
|
||||
}
|
||||
|
||||
sub retract_speed_mm_min {
|
||||
my $self = shift;
|
||||
return $self->retract_speed * 60;
|
||||
}
|
||||
|
||||
sub scaled_wipe_distance {
|
||||
my ($self, $travel_speed) = @_;
|
||||
|
||||
# how far do we move in XY at travel_speed for the time needed to consume
|
||||
# retract_length at retract_speed?
|
||||
# reduce feedrate a bit; travel speed is often too high to move on existing material
|
||||
# too fast = ripping of existing material; too slow = short wipe path, thus more blob
|
||||
return scale($self->retract_length / $self->retract_speed * $travel_speed * 0.8);
|
||||
}
|
||||
|
||||
sub extruded_volume {
|
||||
my ($self, $E) = @_;
|
||||
return $E * ($self->filament_diameter**2) * PI/4;
|
||||
}
|
||||
|
||||
sub e_per_mm {
|
||||
my ($self, $mm3_per_mm) = @_;
|
||||
return $mm3_per_mm * $self->e_per_mm3;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -4,8 +4,8 @@ use warnings;
|
||||
|
||||
use parent qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT EXTRL_ROLE_EXTERNAL_PERIMETER
|
||||
EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER);
|
||||
our @EXPORT_OK = qw(EXTRL_ROLE_DEFAULT
|
||||
EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER EXTRL_ROLE_SKIRT);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ use parent qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
|
||||
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_GAPFILL EXTR_ROLE_BRIDGE
|
||||
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE);
|
||||
EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_SUPPORTMATERIAL_INTERFACE
|
||||
EXTR_ROLE_NONE);
|
||||
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package Slic3r::ExtrusionPath::Collection;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
1;
|
||||
@@ -1,262 +0,0 @@
|
||||
package Slic3r::Fill;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Fill::3DHoneycomb;
|
||||
use Slic3r::Fill::ArchimedeanChords;
|
||||
use Slic3r::Fill::Base;
|
||||
use Slic3r::Fill::Concentric;
|
||||
use Slic3r::Fill::Flowsnake;
|
||||
use Slic3r::Fill::HilbertCurve;
|
||||
use Slic3r::Fill::Honeycomb;
|
||||
use Slic3r::Fill::Line;
|
||||
use Slic3r::Fill::OctagramSpiral;
|
||||
use Slic3r::Fill::PlanePath;
|
||||
use Slic3r::Fill::Rectilinear;
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(X Y PI scale chained_path deg2rad);
|
||||
use Slic3r::Geometry::Clipper qw(union union_ex diff diff_ex intersection_ex offset offset2);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
||||
has 'bounding_box' => (is => 'ro', required => 0);
|
||||
has 'fillers' => (is => 'rw', default => sub { {} });
|
||||
|
||||
our %FillTypes = (
|
||||
archimedeanchords => 'Slic3r::Fill::ArchimedeanChords',
|
||||
rectilinear => 'Slic3r::Fill::Rectilinear',
|
||||
flowsnake => 'Slic3r::Fill::Flowsnake',
|
||||
octagramspiral => 'Slic3r::Fill::OctagramSpiral',
|
||||
hilbertcurve => 'Slic3r::Fill::HilbertCurve',
|
||||
line => 'Slic3r::Fill::Line',
|
||||
concentric => 'Slic3r::Fill::Concentric',
|
||||
honeycomb => 'Slic3r::Fill::Honeycomb',
|
||||
'3dhoneycomb' => 'Slic3r::Fill::3DHoneycomb',
|
||||
);
|
||||
|
||||
sub filler {
|
||||
my $self = shift;
|
||||
my ($filler) = @_;
|
||||
|
||||
if (!ref $self) {
|
||||
return $FillTypes{$filler}->new;
|
||||
}
|
||||
|
||||
$self->fillers->{$filler} ||= $FillTypes{$filler}->new(
|
||||
bounding_box => $self->bounding_box,
|
||||
);
|
||||
return $self->fillers->{$filler};
|
||||
}
|
||||
|
||||
sub make_fill {
|
||||
my $self = shift;
|
||||
my ($layerm) = @_;
|
||||
|
||||
Slic3r::debugf "Filling layer %d:\n", $layerm->id;
|
||||
|
||||
my $fill_density = $layerm->config->fill_density;
|
||||
my $infill_flow = $layerm->flow(FLOW_ROLE_INFILL);
|
||||
my $solid_infill_flow = $layerm->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
|
||||
my @surfaces = ();
|
||||
|
||||
# merge adjacent surfaces
|
||||
# in case of bridge surfaces, the ones with defined angle will be attached to the ones
|
||||
# without any angle (shouldn't this logic be moved to process_external_surfaces()?)
|
||||
{
|
||||
my @surfaces_with_bridge_angle = grep { $_->bridge_angle >= 0 } @{$layerm->fill_surfaces};
|
||||
|
||||
# group surfaces by distinct properties
|
||||
my @groups = @{$layerm->fill_surfaces->group};
|
||||
|
||||
# merge compatible groups (we can generate continuous infill for them)
|
||||
{
|
||||
# cache flow widths and patterns used for all solid groups
|
||||
# (we'll use them for comparing compatible groups)
|
||||
my @is_solid = my @fw = my @pattern = ();
|
||||
for (my $i = 0; $i <= $#groups; $i++) {
|
||||
# we can only merge solid non-bridge surfaces, so discard
|
||||
# non-solid surfaces
|
||||
if ($groups[$i][0]->is_solid && (!$groups[$i][0]->is_bridge || $layerm->id == 0)) {
|
||||
$is_solid[$i] = 1;
|
||||
$fw[$i] = ($groups[$i][0]->surface_type == S_TYPE_TOP)
|
||||
? $layerm->flow(FLOW_ROLE_TOP_SOLID_INFILL)->width
|
||||
: $solid_infill_flow->width;
|
||||
$pattern[$i] = $groups[$i][0]->is_external
|
||||
? $layerm->config->solid_fill_pattern
|
||||
: 'rectilinear';
|
||||
} else {
|
||||
$is_solid[$i] = 0;
|
||||
$fw[$i] = 0;
|
||||
$pattern[$i] = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
# loop through solid groups
|
||||
for (my $i = 0; $i <= $#groups; $i++) {
|
||||
next if !$is_solid[$i];
|
||||
|
||||
# find compatible groups and append them to this one
|
||||
for (my $j = $i+1; $j <= $#groups; $j++) {
|
||||
next if !$is_solid[$j];
|
||||
|
||||
if ($fw[$i] == $fw[$j] && $pattern[$i] eq $pattern[$j]) {
|
||||
# groups are compatible, merge them
|
||||
push @{$groups[$i]}, @{$groups[$j]};
|
||||
splice @groups, $j, 1;
|
||||
splice @is_solid, $j, 1;
|
||||
splice @fw, $j, 1;
|
||||
splice @pattern, $j, 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# give priority to bridges
|
||||
@groups = sort { ($a->[0]->bridge_angle >= 0) ? -1 : 0 } @groups;
|
||||
|
||||
foreach my $group (@groups) {
|
||||
my $union_p = union([ map $_->p, @$group ], 1);
|
||||
|
||||
# subtract surfaces having a defined bridge_angle from any other
|
||||
if (@surfaces_with_bridge_angle && $group->[0]->bridge_angle < 0) {
|
||||
$union_p = diff(
|
||||
$union_p,
|
||||
[ map $_->p, @surfaces_with_bridge_angle ],
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
# subtract any other surface already processed
|
||||
my $union = diff_ex(
|
||||
$union_p,
|
||||
[ map $_->p, @surfaces ],
|
||||
1,
|
||||
);
|
||||
|
||||
push @surfaces, map $group->[0]->clone(expolygon => $_), @$union;
|
||||
}
|
||||
}
|
||||
|
||||
# we need to detect any narrow surfaces that might collapse
|
||||
# when adding spacing below
|
||||
# such narrow surfaces are often generated in sloping walls
|
||||
# by bridge_over_infill() and combine_infill() as a result of the
|
||||
# subtraction of the combinable area from the layer infill area,
|
||||
# which leaves small areas near the perimeters
|
||||
# we are going to grow such regions by overlapping them with the void (if any)
|
||||
# TODO: detect and investigate whether there could be narrow regions without
|
||||
# any void neighbors
|
||||
my $distance_between_surfaces = $infill_flow->scaled_spacing * &Slic3r::INFILL_OVERLAP_OVER_SPACING;
|
||||
{
|
||||
my $collapsed = diff(
|
||||
[ map @{$_->expolygon}, @surfaces ],
|
||||
offset2([ map @{$_->expolygon}, @surfaces ], -$distance_between_surfaces/2, +$distance_between_surfaces/2),
|
||||
1,
|
||||
);
|
||||
push @surfaces, map Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNALSOLID,
|
||||
), @{intersection_ex(
|
||||
offset($collapsed, $distance_between_surfaces),
|
||||
[
|
||||
(map @{$_->expolygon}, grep $_->surface_type == S_TYPE_INTERNALVOID, @surfaces),
|
||||
(@$collapsed),
|
||||
],
|
||||
1,
|
||||
)};
|
||||
}
|
||||
|
||||
# add spacing between surfaces
|
||||
@surfaces = map @{$_->offset(-$distance_between_surfaces / 2)}, @surfaces;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg",
|
||||
expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ],
|
||||
red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ],
|
||||
);
|
||||
}
|
||||
|
||||
my @fills = ();
|
||||
my @fills_ordering_points = ();
|
||||
SURFACE: foreach my $surface (@surfaces) {
|
||||
next if $surface->surface_type == S_TYPE_INTERNALVOID;
|
||||
my $filler = $layerm->config->fill_pattern;
|
||||
my $density = $fill_density;
|
||||
my $role = ($surface->surface_type == S_TYPE_TOP) ? FLOW_ROLE_TOP_SOLID_INFILL
|
||||
: $surface->is_solid ? FLOW_ROLE_SOLID_INFILL
|
||||
: FLOW_ROLE_INFILL;
|
||||
my $is_bridge = $layerm->id > 0 && $surface->is_bridge;
|
||||
my $is_solid = $surface->is_solid;
|
||||
|
||||
# force 100% density and rectilinear fill for external surfaces
|
||||
if ($surface->surface_type != S_TYPE_INTERNAL) {
|
||||
$density = 100;
|
||||
$filler = $layerm->config->solid_fill_pattern;
|
||||
if ($is_bridge) {
|
||||
$filler = 'rectilinear';
|
||||
} elsif ($surface->surface_type == S_TYPE_INTERNALSOLID) {
|
||||
$filler = 'rectilinear';
|
||||
}
|
||||
} else {
|
||||
next SURFACE unless $density > 0;
|
||||
}
|
||||
|
||||
my $h = $surface->thickness == -1 ? $layerm->height : $surface->thickness;
|
||||
my $flow = $layerm->region->flow(
|
||||
$role,
|
||||
$h,
|
||||
$is_bridge,
|
||||
$layerm->id == 0,
|
||||
-1,
|
||||
$layerm->object,
|
||||
);
|
||||
|
||||
my $f = $self->filler($filler);
|
||||
$f->layer_id($layerm->id);
|
||||
$f->z($layerm->print_z);
|
||||
$f->angle(deg2rad($layerm->config->fill_angle));
|
||||
my ($params, @polylines) = $f->fill_surface(
|
||||
$surface,
|
||||
density => $density/100,
|
||||
flow => $flow,
|
||||
layer_height => $h,
|
||||
);
|
||||
next unless @polylines;
|
||||
|
||||
my $mm3_per_mm = $flow->mm3_per_mm;
|
||||
|
||||
# save into layer
|
||||
push @fills, my $collection = Slic3r::ExtrusionPath::Collection->new;
|
||||
$collection->no_sort($params->{no_sort});
|
||||
|
||||
$collection->append(
|
||||
map Slic3r::ExtrusionPath->new(
|
||||
polyline => $_,
|
||||
role => ($is_bridge
|
||||
? EXTR_ROLE_BRIDGE
|
||||
: $is_solid
|
||||
? (($surface->surface_type == S_TYPE_TOP) ? EXTR_ROLE_TOPSOLIDFILL : EXTR_ROLE_SOLIDFILL)
|
||||
: EXTR_ROLE_FILL),
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => ($is_bridge ? $flow->width : $h),
|
||||
), @polylines,
|
||||
);
|
||||
push @fills_ordering_points, $polylines[0]->first_point;
|
||||
}
|
||||
|
||||
# add thin fill regions
|
||||
foreach my $thin_fill (@{$layerm->thin_fills}) {
|
||||
push @fills, Slic3r::ExtrusionPath::Collection->new($thin_fill);
|
||||
push @fills_ordering_points, $thin_fill->first_point;
|
||||
}
|
||||
|
||||
# organize infill paths using a nearest-neighbor search
|
||||
@fills = @fills[ @{chained_path(\@fills_ordering_points)} ];
|
||||
|
||||
return @fills;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,233 +0,0 @@
|
||||
package Slic3r::Fill::3DHoneycomb;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Base';
|
||||
|
||||
use POSIX qw(ceil fmod);
|
||||
use Slic3r::Geometry qw(scale scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
|
||||
sub fill_surface {
|
||||
my ($self, $surface, %params) = @_;
|
||||
|
||||
# use bridge flow since most of this pattern hangs in air
|
||||
my $flow = Slic3r::Flow->new(
|
||||
width => $params{flow}->width,
|
||||
height => $params{flow}->height,
|
||||
nozzle_diameter => $params{flow}->nozzle_diameter,
|
||||
bridge => 1,
|
||||
);
|
||||
|
||||
my $expolygon = $surface->expolygon;
|
||||
my $bb = $expolygon->bounding_box;
|
||||
my $size = $bb->size;
|
||||
|
||||
my $distance = $flow->scaled_spacing / $params{density};
|
||||
|
||||
# align bounding box to a multiple of our honeycomb grid
|
||||
{
|
||||
my $min = $bb->min_point;
|
||||
$min->translate(
|
||||
-($bb->x_min % $distance),
|
||||
-($bb->y_min % $distance),
|
||||
);
|
||||
$bb->merge_point($min);
|
||||
}
|
||||
|
||||
# generate pattern
|
||||
my @polylines = map Slic3r::Polyline->new(@$_),
|
||||
makeGrid(
|
||||
scale($self->z),
|
||||
$distance,
|
||||
ceil($size->x / $distance),
|
||||
ceil($size->y / $distance), #//
|
||||
(($self->layer_id / $surface->thickness_layers) % 2) + 1,
|
||||
);
|
||||
|
||||
# move pattern in place
|
||||
$_->translate($bb->x_min, $bb->y_min) for @polylines;
|
||||
|
||||
# clip pattern to boundaries
|
||||
@polylines = @{intersection_pl(\@polylines, \@$expolygon)};
|
||||
|
||||
# connect lines
|
||||
unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections
|
||||
my ($expolygon_off) = @{$expolygon->offset_ex(scaled_epsilon)};
|
||||
my $collection = Slic3r::Polyline::Collection->new(@polylines);
|
||||
@polylines = ();
|
||||
foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) {
|
||||
# try to append this polyline to previous one if any
|
||||
if (@polylines) {
|
||||
my $line = Slic3r::Line->new($polylines[-1]->last_point, $polyline->first_point);
|
||||
if ($line->length <= 1.5*$distance && $expolygon_off->contains_line($line)) {
|
||||
$polylines[-1]->append_polyline($polyline);
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# make a clone before $collection goes out of scope
|
||||
push @polylines, $polyline->clone;
|
||||
}
|
||||
}
|
||||
|
||||
# TODO: return ExtrusionLoop objects to get better chained paths
|
||||
return { flow => $flow}, @polylines;
|
||||
}
|
||||
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Creates a contiguous sequence of points at a specified height that make
|
||||
up a horizontal slice of the edges of a space filling truncated
|
||||
octahedron tesselation. The octahedrons are oriented so that the
|
||||
square faces are in the horizontal plane with edges parallel to the X
|
||||
and Y axes.
|
||||
|
||||
Credits: David Eccles (gringer).
|
||||
|
||||
=head2 makeGrid(z, gridSize, gridWidth, gridHeight, curveType)
|
||||
|
||||
Generate a set of curves (array of array of 2d points) that describe a
|
||||
horizontal slice of a truncated regular octahedron with a specified
|
||||
grid square size.
|
||||
|
||||
=cut
|
||||
|
||||
sub makeGrid {
|
||||
my ($z, $gridSize, $gridWidth, $gridHeight, $curveType) = @_;
|
||||
my $scaleFactor = $gridSize;
|
||||
my $normalisedZ = $z / $scaleFactor;
|
||||
my @points = makeNormalisedGrid($normalisedZ, $gridWidth, $gridHeight, $curveType);
|
||||
foreach my $lineRef (@points) {
|
||||
foreach my $pointRef (@$lineRef) {
|
||||
$pointRef->[0] *= $scaleFactor;
|
||||
$pointRef->[1] *= $scaleFactor;
|
||||
}
|
||||
}
|
||||
return @points;
|
||||
}
|
||||
|
||||
=head1 FUNCTIONS
|
||||
=cut
|
||||
|
||||
=head2 colinearPoints(offset, gridLength)
|
||||
|
||||
Generate an array of points that are in the same direction as the
|
||||
basic printing line (i.e. Y points for columns, X points for rows)
|
||||
|
||||
Note: a negative offset only causes a change in the perpendicular
|
||||
direction
|
||||
|
||||
=cut
|
||||
|
||||
sub colinearPoints {
|
||||
my ($offset, $baseLocation, $gridLength) = @_;
|
||||
|
||||
my @points = ();
|
||||
push @points, $baseLocation - abs($offset/2);
|
||||
for (my $i = 0; $i < $gridLength; $i++) {
|
||||
push @points, $baseLocation + $i + abs($offset/2);
|
||||
push @points, $baseLocation + ($i+1) - abs($offset/2);
|
||||
}
|
||||
push @points, $baseLocation + $gridLength + abs($offset/2);
|
||||
return @points;
|
||||
}
|
||||
|
||||
=head2 colinearPoints(offset, baseLocation, gridLength)
|
||||
|
||||
Generate an array of points for the dimension that is perpendicular to
|
||||
the basic printing line (i.e. X points for columns, Y points for rows)
|
||||
|
||||
=cut
|
||||
|
||||
sub perpendPoints {
|
||||
my ($offset, $baseLocation, $gridLength) = @_;
|
||||
|
||||
my @points = ();
|
||||
my $side = 2*(($baseLocation) % 2) - 1;
|
||||
push @points, $baseLocation - $offset/2 * $side;
|
||||
for (my $i = 0; $i < $gridLength; $i++) {
|
||||
$side = 2*(($i+$baseLocation) % 2) - 1;
|
||||
push @points, $baseLocation + $offset/2 * $side;
|
||||
push @points, $baseLocation + $offset/2 * $side;
|
||||
}
|
||||
push @points, $baseLocation - $offset/2 * $side;
|
||||
|
||||
return @points;
|
||||
}
|
||||
|
||||
=head2 trim(pointArrayRef, minX, minY, maxX, maxY)
|
||||
|
||||
Trims an array of points to specified rectangular limits. Point
|
||||
components that are outside these limits are set to the limits.
|
||||
|
||||
=cut
|
||||
|
||||
sub trim {
|
||||
my ($pointArrayRef, $minX, $minY, $maxX, $maxY) = @_;
|
||||
|
||||
foreach (@$pointArrayRef) {
|
||||
$_->[0] = ($_->[0] < $minX) ? $minX : (($_->[0] > $maxX) ? $maxX : $_->[0]);
|
||||
$_->[1] = ($_->[1] < $minY) ? $minY : (($_->[1] > $maxY) ? $maxY : $_->[1]);
|
||||
}
|
||||
}
|
||||
|
||||
=head2 makeNormalisedGrid(z, gridWidth, gridHeight, curveType)
|
||||
|
||||
Generate a set of curves (array of array of 2d points) that describe a
|
||||
horizontal slice of a truncated regular octahedron with edge length 1.
|
||||
|
||||
curveType specifies which lines to print, 1 for vertical lines
|
||||
(columns), 2 for horizontal lines (rows), and 3 for both.
|
||||
|
||||
=cut
|
||||
|
||||
sub makeNormalisedGrid {
|
||||
my ($z, $gridWidth, $gridHeight, $curveType) = @_;
|
||||
|
||||
## offset required to create a regular octagram
|
||||
my $octagramGap = 0.5;
|
||||
|
||||
# sawtooth wave function for range f($z) = [-$octagramGap .. $octagramGap]
|
||||
my $a = sqrt(2); # period
|
||||
my $wave = abs(fmod($z, $a) - $a/2)/$a*4 - 1;
|
||||
my $offset = $wave * $octagramGap;
|
||||
|
||||
my @points = ();
|
||||
if (($curveType & 1) != 0) {
|
||||
for (my $x = 0; $x <= $gridWidth; $x++) {
|
||||
my @xPoints = perpendPoints($offset, $x, $gridHeight);
|
||||
my @yPoints = colinearPoints($offset, 0, $gridHeight);
|
||||
# This is essentially @newPoints = zip(@xPoints, @yPoints)
|
||||
my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints;
|
||||
|
||||
# trim points to grid edges
|
||||
#trim(\@newPoints, 0, 0, $gridWidth, $gridHeight);
|
||||
|
||||
if ($x % 2 == 0){
|
||||
push @points, [ @newPoints ];
|
||||
} else {
|
||||
push @points, [ reverse @newPoints ];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (($curveType & 2) != 0) {
|
||||
for (my $y = 0; $y <= $gridHeight; $y++) {
|
||||
my @xPoints = colinearPoints($offset, 0, $gridWidth);
|
||||
my @yPoints = perpendPoints($offset, $y, $gridWidth);
|
||||
my @newPoints = map [ $xPoints[$_], $yPoints[$_] ], 0..$#xPoints;
|
||||
|
||||
# trim points to grid edges
|
||||
#trim(\@newPoints, 0, 0, $gridWidth, $gridHeight);
|
||||
|
||||
if ($y % 2 == 0) {
|
||||
push @points, [ @newPoints ];
|
||||
} else {
|
||||
push @points, [ reverse @newPoints ];
|
||||
}
|
||||
}
|
||||
}
|
||||
return @points;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,7 +0,0 @@
|
||||
package Slic3r::Fill::ArchimedeanChords;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::ArchimedeanChords;
|
||||
|
||||
1;
|
||||
@@ -1,86 +0,0 @@
|
||||
package Slic3r::Fill::Base;
|
||||
use Moo;
|
||||
|
||||
has 'layer_id' => (is => 'rw');
|
||||
has 'z' => (is => 'rw'); # in unscaled coordinates
|
||||
has 'angle' => (is => 'rw'); # in radians, ccw, 0 = East
|
||||
has 'bounding_box' => (is => 'ro', required => 0); # Slic3r::Geometry::BoundingBox object
|
||||
|
||||
sub adjust_solid_spacing {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
my $number_of_lines = int($params{width} / $params{distance}) + 1;
|
||||
return $params{distance} if $number_of_lines <= 1;
|
||||
|
||||
my $extra_space = $params{width} % $params{distance};
|
||||
return $params{distance} + $extra_space / ($number_of_lines - 1);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::Fill::WithDirection;
|
||||
use Moo::Role;
|
||||
|
||||
use Slic3r::Geometry qw(PI rad2deg);
|
||||
|
||||
sub angles () { [0, PI/2] }
|
||||
|
||||
sub infill_direction {
|
||||
my $self = shift;
|
||||
my ($surface) = @_;
|
||||
|
||||
if (!defined $self->angle) {
|
||||
warn "Using undefined infill angle";
|
||||
$self->angle(0);
|
||||
}
|
||||
|
||||
# set infill angle
|
||||
my (@rotate);
|
||||
$rotate[0] = $self->angle;
|
||||
$rotate[1] = $self->bounding_box
|
||||
? $self->bounding_box->center
|
||||
: $surface->expolygon->bounding_box->center;
|
||||
my $shift = $rotate[1]->clone;
|
||||
|
||||
if (defined $self->layer_id) {
|
||||
# alternate fill direction
|
||||
my $layer_num = $self->layer_id / $surface->thickness_layers;
|
||||
my $angle = $self->angles->[$layer_num % @{$self->angles}];
|
||||
$rotate[0] = $self->angle + $angle if $angle;
|
||||
}
|
||||
|
||||
# use bridge angle
|
||||
if ($surface->bridge_angle >= 0) {
|
||||
Slic3r::debugf "Filling bridge with angle %d\n", rad2deg($surface->bridge_angle);
|
||||
$rotate[0] = $surface->bridge_angle;
|
||||
}
|
||||
|
||||
$rotate[0] += PI/2;
|
||||
$shift->rotate(@rotate);
|
||||
return [\@rotate, $shift];
|
||||
}
|
||||
|
||||
# this method accepts any object that implements rotate() and translate()
|
||||
sub rotate_points {
|
||||
my $self = shift;
|
||||
my ($expolygon, $rotate_vector) = @_;
|
||||
|
||||
# rotate points
|
||||
my ($rotate, $shift) = @$rotate_vector;
|
||||
$rotate = [ -$rotate->[0], $rotate->[1] ];
|
||||
$expolygon->rotate(@$rotate);
|
||||
$expolygon->translate(@$shift);
|
||||
}
|
||||
|
||||
sub rotate_points_back {
|
||||
my $self = shift;
|
||||
my ($paths, $rotate_vector) = @_;
|
||||
|
||||
my ($rotate, $shift) = @$rotate_vector;
|
||||
$shift = [ map -$_, @$shift ];
|
||||
|
||||
$_->translate(@$shift) for @$paths;
|
||||
$_->rotate(@$rotate) for @$paths;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,65 +0,0 @@
|
||||
package Slic3r::Fill::Concentric;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Base';
|
||||
|
||||
use Slic3r::Geometry qw(scale unscale X);
|
||||
use Slic3r::Geometry::Clipper qw(offset offset2 union_pt_chained);
|
||||
|
||||
sub fill_surface {
|
||||
my $self = shift;
|
||||
my ($surface, %params) = @_;
|
||||
|
||||
# no rotation is supported for this infill pattern
|
||||
|
||||
my $expolygon = $surface->expolygon;
|
||||
my $bounding_box = $expolygon->bounding_box;
|
||||
|
||||
my $flow = $params{flow};
|
||||
my $min_spacing = $flow->scaled_spacing;
|
||||
my $distance = $min_spacing / $params{density};
|
||||
|
||||
my $flow_spacing = $flow->spacing;
|
||||
if ($params{density} == 1 && !$params{dont_adjust}) {
|
||||
$distance = $self->adjust_solid_spacing(
|
||||
width => $bounding_box->size->[X],
|
||||
distance => $distance,
|
||||
);
|
||||
$flow = Slic3r::Flow->new_from_spacing(
|
||||
spacing => unscale($distance),
|
||||
nozzle_diameter => $flow->nozzle_diameter,
|
||||
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
|
||||
bridge => $flow->bridge,
|
||||
);
|
||||
}
|
||||
|
||||
# compensate the overlap which is good for rectilinear but harmful for concentric
|
||||
# where the perimeter/infill spacing should be equal to any other loop spacing
|
||||
my @loops = my @last = @{offset(\@$expolygon, -&Slic3r::INFILL_OVERLAP_OVER_SPACING * $min_spacing / 2)};
|
||||
while (@last) {
|
||||
push @loops, @last = @{offset2(\@last, -($distance + 0.5*$min_spacing), +0.5*$min_spacing)};
|
||||
}
|
||||
|
||||
# generate paths from the outermost to the innermost, to avoid
|
||||
# adhesion problems of the first central tiny loops
|
||||
@loops = map Slic3r::Polygon->new(@$_),
|
||||
reverse @{union_pt_chained(\@loops)};
|
||||
|
||||
# order paths using a nearest neighbor search
|
||||
my @paths = ();
|
||||
my $last_pos = Slic3r::Point->new(0,0);
|
||||
foreach my $loop (@loops) {
|
||||
push @paths, $loop->split_at_index($last_pos->nearest_point_index(\@$loop));
|
||||
$last_pos = $paths[-1]->last_point;
|
||||
}
|
||||
|
||||
# clip the paths to prevent the extruder from getting exactly on the first point of the loop
|
||||
my $clip_length = scale($flow->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER;
|
||||
$_->clip_end($clip_length) for @paths;
|
||||
@paths = grep $_->is_valid, @paths; # remove empty paths (too short, thus eaten by clipping)
|
||||
|
||||
# TODO: return ExtrusionLoop objects to get better chained paths
|
||||
return { flow => $flow, no_sort => 1 }, @paths;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,18 +0,0 @@
|
||||
package Slic3r::Fill::Flowsnake;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
|
||||
use Math::PlanePath::Flowsnake;
|
||||
use Slic3r::Geometry qw(X);
|
||||
|
||||
# Sorry, this fill is currently broken.
|
||||
|
||||
sub process_polyline {
|
||||
my $self = shift;
|
||||
my ($polyline, $bounding_box) = @_;
|
||||
|
||||
$_->[X] += $bounding_box->center->[X] for @$polyline;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,7 +0,0 @@
|
||||
package Slic3r::Fill::HilbertCurve;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::HilbertCurve;
|
||||
|
||||
1;
|
||||
@@ -1,129 +0,0 @@
|
||||
package Slic3r::Fill::Honeycomb;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Base';
|
||||
with qw(Slic3r::Fill::WithDirection);
|
||||
|
||||
has 'cache' => (is => 'rw', default => sub {{}});
|
||||
|
||||
use Slic3r::Geometry qw(PI X Y MIN MAX scale scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection intersection_pl);
|
||||
|
||||
sub angles () { [0, PI/3, PI/3*2] }
|
||||
|
||||
sub fill_surface {
|
||||
my $self = shift;
|
||||
my ($surface, %params) = @_;
|
||||
|
||||
my $rotate_vector = $self->infill_direction($surface);
|
||||
|
||||
# cache hexagons math
|
||||
my $cache_id = sprintf "d%s_s%s", $params{density}, $params{flow}->spacing;
|
||||
my $m;
|
||||
if (!($m = $self->cache->{$cache_id})) {
|
||||
$m = $self->cache->{$cache_id} = {};
|
||||
my $min_spacing = $params{flow}->scaled_spacing;
|
||||
$m->{distance} = $min_spacing / $params{density};
|
||||
$m->{hex_side} = $m->{distance} / (sqrt(3)/2);
|
||||
$m->{hex_width} = $m->{distance} * 2; # $m->{hex_width} == $m->{hex_side} * sqrt(3);
|
||||
my $hex_height = $m->{hex_side} * 2;
|
||||
$m->{pattern_height} = $hex_height + $m->{hex_side};
|
||||
$m->{y_short} = $m->{distance} * sqrt(3)/3;
|
||||
$m->{x_offset} = $min_spacing / 2;
|
||||
$m->{y_offset} = $m->{x_offset} * sqrt(3)/3;
|
||||
$m->{hex_center} = Slic3r::Point->new($m->{hex_width}/2, $m->{hex_side});
|
||||
}
|
||||
|
||||
my @polygons = ();
|
||||
{
|
||||
# adjust actual bounding box to the nearest multiple of our hex pattern
|
||||
# and align it so that it matches across layers
|
||||
|
||||
my $bounding_box = $surface->expolygon->bounding_box;
|
||||
{
|
||||
# rotate bounding box according to infill direction
|
||||
my $bb_polygon = $bounding_box->polygon;
|
||||
$bb_polygon->rotate($rotate_vector->[0][0], $m->{hex_center});
|
||||
$bounding_box = $bb_polygon->bounding_box;
|
||||
|
||||
# extend bounding box so that our pattern will be aligned with other layers
|
||||
# $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one
|
||||
$bounding_box->merge_point(Slic3r::Point->new(
|
||||
$bounding_box->x_min - ($bounding_box->x_min % $m->{hex_width}),
|
||||
$bounding_box->y_min - ($bounding_box->y_min % $m->{pattern_height}),
|
||||
));
|
||||
}
|
||||
|
||||
my $x = $bounding_box->x_min;
|
||||
while ($x <= $bounding_box->x_max) {
|
||||
my $p = [];
|
||||
|
||||
my @x = ($x + $m->{x_offset}, $x + $m->{distance} - $m->{x_offset});
|
||||
for (1..2) {
|
||||
@$p = reverse @$p; # turn first half upside down
|
||||
my @p = ();
|
||||
for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y += $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side}) {
|
||||
push @$p,
|
||||
[ $x[1], $y + $m->{y_offset} ],
|
||||
[ $x[0], $y + $m->{y_short} - $m->{y_offset} ],
|
||||
[ $x[0], $y + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ],
|
||||
[ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} - $m->{y_offset} ],
|
||||
[ $x[1], $y + $m->{y_short} + $m->{hex_side} + $m->{y_short} + $m->{hex_side} + $m->{y_offset} ];
|
||||
}
|
||||
@x = map $_ + $m->{distance}, reverse @x; # draw symmetrical pattern
|
||||
$x += $m->{distance};
|
||||
}
|
||||
|
||||
push @polygons, Slic3r::Polygon->new(@$p);
|
||||
}
|
||||
|
||||
$_->rotate(-$rotate_vector->[0][0], $m->{hex_center}) for @polygons;
|
||||
}
|
||||
|
||||
my @paths;
|
||||
if ($params{complete}) {
|
||||
# we were requested to complete each loop;
|
||||
# in this case we don't try to make more continuous paths
|
||||
@paths = map $_->split_at_first_point,
|
||||
@{intersection([ $surface->p ], \@polygons)};
|
||||
|
||||
} else {
|
||||
# consider polygons as polylines without re-appending the initial point:
|
||||
# this cuts the last segment on purpose, so that the jump to the next
|
||||
# path is more straight
|
||||
@paths = @{intersection_pl(
|
||||
[ map Slic3r::Polyline->new(@$_), @polygons ],
|
||||
[ @{$surface->expolygon} ],
|
||||
)};
|
||||
|
||||
# connect paths
|
||||
if (@paths) { # prevent calling leftmost_point() on empty collections
|
||||
my $collection = Slic3r::Polyline::Collection->new(@paths);
|
||||
@paths = ();
|
||||
foreach my $path (@{$collection->chained_path_from($collection->leftmost_point, 0)}) {
|
||||
if (@paths) {
|
||||
# distance between first point of this path and last point of last path
|
||||
my $distance = $paths[-1]->last_point->distance_to($path->first_point);
|
||||
|
||||
if ($distance <= $m->{hex_width}) {
|
||||
$paths[-1]->append_polyline($path);
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# make a clone before $collection goes out of scope
|
||||
push @paths, $path->clone;
|
||||
}
|
||||
}
|
||||
|
||||
# clip paths again to prevent connection segments from crossing the expolygon boundaries
|
||||
@paths = @{intersection_pl(
|
||||
\@paths,
|
||||
[ map @$_, @{$surface->expolygon->offset_ex(scaled_epsilon)} ],
|
||||
)};
|
||||
}
|
||||
|
||||
return { flow => $params{flow} }, @paths;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,8 +0,0 @@
|
||||
package Slic3r::Fill::Line;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Rectilinear';
|
||||
|
||||
# Sorry for breaking OOP, but Line is implemented inside Rectilinear.
|
||||
|
||||
1;
|
||||
@@ -1,9 +0,0 @@
|
||||
package Slic3r::Fill::OctagramSpiral;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::PlanePath';
|
||||
use Math::PlanePath::OctagramSpiral;
|
||||
|
||||
sub multiplier () { sqrt(2) }
|
||||
|
||||
1;
|
||||
@@ -1,62 +0,0 @@
|
||||
package Slic3r::Fill::PlanePath;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Base';
|
||||
with qw(Slic3r::Fill::WithDirection);
|
||||
|
||||
use Slic3r::Geometry qw(scale X1 Y1 X2 Y2);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
|
||||
sub multiplier () { 1 }
|
||||
|
||||
sub get_n {
|
||||
my $self = shift;
|
||||
my ($path, $bounding_box) = @_;
|
||||
|
||||
my ($n_lo, $n_hi) = $path->rect_to_n_range(@$bounding_box);
|
||||
return ($n_lo .. $n_hi);
|
||||
}
|
||||
|
||||
sub process_polyline {}
|
||||
|
||||
sub fill_surface {
|
||||
my $self = shift;
|
||||
my ($surface, %params) = @_;
|
||||
|
||||
# rotate polygons
|
||||
my $expolygon = $surface->expolygon->clone;
|
||||
my $rotate_vector = $self->infill_direction($surface);
|
||||
$self->rotate_points($expolygon, $rotate_vector);
|
||||
|
||||
my $flow = $params{flow};
|
||||
my $distance_between_lines = $flow->scaled_spacing / $params{density} * $self->multiplier;
|
||||
my $bounding_box = $expolygon->bounding_box;
|
||||
|
||||
(ref $self) =~ /::([^:]+)$/;
|
||||
my $path = "Math::PlanePath::$1"->new;
|
||||
my @n = $self->get_n($path, [ map +($_ / $distance_between_lines), @{$bounding_box->min_point}, @{$bounding_box->max_point} ]);
|
||||
|
||||
my $polyline = Slic3r::Polyline->new(
|
||||
map [ map {$_*$distance_between_lines} $path->n_to_xy($_) ], @n,
|
||||
);
|
||||
return {} if @$polyline <= 1;
|
||||
|
||||
$self->process_polyline($polyline, $bounding_box);
|
||||
|
||||
my @paths = @{intersection_pl([$polyline], \@$expolygon)};
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("fill.svg",
|
||||
polygons => $expolygon,
|
||||
polylines => [map $_->p, @paths],
|
||||
);
|
||||
}
|
||||
|
||||
# paths must be rotated back
|
||||
$self->rotate_points_back(\@paths, $rotate_vector);
|
||||
|
||||
return { flow => $flow }, @paths;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,111 +0,0 @@
|
||||
package Slic3r::Fill::Rectilinear;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::Fill::Base';
|
||||
with qw(Slic3r::Fill::WithDirection);
|
||||
|
||||
has 'cache' => (is => 'rw', default => sub {{}});
|
||||
|
||||
use Slic3r::Geometry qw(A B X Y MIN scale unscale scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl offset);
|
||||
|
||||
sub fill_surface {
|
||||
my $self = shift;
|
||||
my ($surface, %params) = @_;
|
||||
|
||||
# rotate polygons so that we can work with vertical lines here
|
||||
my $expolygon = $surface->expolygon->clone;
|
||||
my $rotate_vector = $self->infill_direction($surface);
|
||||
$self->rotate_points($expolygon, $rotate_vector);
|
||||
|
||||
my $flow = $params{flow} or die "No flow supplied to fill_surface()";
|
||||
my $min_spacing = $flow->scaled_spacing;
|
||||
my $line_spacing = $min_spacing / $params{density};
|
||||
my $line_oscillation = $line_spacing - $min_spacing;
|
||||
my $is_line_pattern = $self->isa('Slic3r::Fill::Line');
|
||||
my $bounding_box = $expolygon->bounding_box;
|
||||
|
||||
# define flow spacing according to requested density
|
||||
if ($params{density} == 1 && !$params{dont_adjust}) {
|
||||
$line_spacing = $self->adjust_solid_spacing(
|
||||
width => $bounding_box->size->[X],
|
||||
distance => $line_spacing,
|
||||
);
|
||||
$flow = Slic3r::Flow->new_from_spacing(
|
||||
spacing => unscale($line_spacing),
|
||||
nozzle_diameter => $flow->nozzle_diameter,
|
||||
layer_height => ($params{layer_height} or die "No layer_height supplied to fill_surface()"),
|
||||
bridge => $flow->bridge,
|
||||
);
|
||||
} else {
|
||||
# extend bounding box so that our pattern will be aligned with other layers
|
||||
$bounding_box->merge_point(Slic3r::Point->new(
|
||||
$bounding_box->x_min - ($bounding_box->x_min % $line_spacing),
|
||||
$bounding_box->y_min - ($bounding_box->y_min % $line_spacing),
|
||||
));
|
||||
}
|
||||
|
||||
# generate the basic pattern
|
||||
my $i = 0;
|
||||
my $x = $bounding_box->x_min;
|
||||
my $x_max = $bounding_box->x_max + scaled_epsilon;
|
||||
my @vertical_lines = ();
|
||||
while ($x <= $x_max) {
|
||||
my $vertical_line = [ [$x, $bounding_box->y_max], [$x, $bounding_box->y_min] ];
|
||||
if ($is_line_pattern && $i % 2) {
|
||||
$vertical_line->[A][X] += $line_oscillation;
|
||||
$vertical_line->[B][X] -= $line_oscillation;
|
||||
}
|
||||
push @vertical_lines, Slic3r::Polyline->new(@$vertical_line);
|
||||
$i++;
|
||||
$x += $line_spacing;
|
||||
}
|
||||
|
||||
# clip paths against a slightly larger expolygon, so that the first and last paths
|
||||
# are kept even if the expolygon has vertical sides
|
||||
# the minimum offset for preventing edge lines from being clipped is scaled_epsilon;
|
||||
# however we use a larger offset to support expolygons with slightly skewed sides and
|
||||
# not perfectly straight
|
||||
my @polylines = @{intersection_pl(\@vertical_lines, $expolygon->offset(scale 0.02))};
|
||||
|
||||
# connect lines
|
||||
unless ($params{dont_connect} || !@polylines) { # prevent calling leftmost_point() on empty collections
|
||||
my ($expolygon_off) = @{$expolygon->offset_ex($min_spacing/2)};
|
||||
my $collection = Slic3r::Polyline::Collection->new(@polylines);
|
||||
@polylines = ();
|
||||
|
||||
my $tolerance = 10 * scaled_epsilon;
|
||||
my $diagonal_distance = $line_spacing * 2;
|
||||
my $can_connect = $is_line_pattern
|
||||
? sub {
|
||||
($_[X] >= ($line_spacing - $line_oscillation) - $tolerance) && ($_[X] <= ($line_spacing + $line_oscillation) + $tolerance)
|
||||
&& $_[Y] <= $diagonal_distance
|
||||
}
|
||||
: sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance };
|
||||
|
||||
foreach my $polyline (@{$collection->chained_path_from($collection->leftmost_point, 0)}) {
|
||||
if (@polylines) {
|
||||
my $first_point = $polyline->first_point;
|
||||
my $last_point = $polylines[-1]->last_point;
|
||||
my @distance = map abs($first_point->$_ - $last_point->$_), qw(x y);
|
||||
|
||||
# TODO: we should also check that both points are on a fill_boundary to avoid
|
||||
# connecting paths on the boundaries of internal regions
|
||||
if ($can_connect->(@distance) && $expolygon_off->contains_line(Slic3r::Line->new($last_point, $first_point))) {
|
||||
$polylines[-1]->append_polyline($polyline);
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# make a clone before $collection goes out of scope
|
||||
push @polylines, $polyline->clone;
|
||||
}
|
||||
}
|
||||
|
||||
# paths must be rotated back
|
||||
$self->rotate_points_back(\@polylines, $rotate_vector);
|
||||
|
||||
return { flow => $flow }, @polylines;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,128 +0,0 @@
|
||||
package Slic3r::Format::AMF;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(X Y Z);
|
||||
|
||||
sub read_file {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
eval qq{
|
||||
require Slic3r::Format::AMF::Parser;
|
||||
use XML::SAX::ParserFactory;
|
||||
1;
|
||||
} or die "AMF parsing requires XML::SAX\n";
|
||||
|
||||
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
|
||||
|
||||
my $model = Slic3r::Model->new;
|
||||
XML::SAX::ParserFactory
|
||||
->parser(Handler => Slic3r::Format::AMF::Parser->new(_model => $model))
|
||||
->parse_file($fh);
|
||||
close $fh;
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my $self = shift;
|
||||
my ($file, $model, %params) = @_;
|
||||
|
||||
my %vertices_offset = ();
|
||||
|
||||
Slic3r::open(\my $fh, '>', $file);
|
||||
binmode $fh, ':utf8';
|
||||
printf $fh qq{<?xml version="1.0" encoding="UTF-8"?>\n};
|
||||
printf $fh qq{<amf unit="millimeter">\n};
|
||||
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
|
||||
for my $material_id (sort @{ $model->material_names }) {
|
||||
my $material = $model->get_material($material_id);
|
||||
printf $fh qq{ <material id="%s">\n}, $material_id;
|
||||
for (keys %{$material->attributes}) {
|
||||
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
|
||||
}
|
||||
my $config = $material->config;
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
printf $fh qq{ <metadata type=\"slic3r.%s\">%s</metadata>\n}, $opt_key, $config->serialize($opt_key);
|
||||
}
|
||||
printf $fh qq{ </material>\n};
|
||||
}
|
||||
my $instances = '';
|
||||
for my $object_id (0 .. $#{ $model->objects }) {
|
||||
my $object = $model->objects->[$object_id];
|
||||
printf $fh qq{ <object id="%d">\n}, $object_id;
|
||||
|
||||
my $config = $object->config;
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
printf $fh qq{ <metadata type=\"slic3r.%s\">%s</metadata>\n}, $opt_key, $config->serialize($opt_key);
|
||||
}
|
||||
if ($object->name) {
|
||||
printf $fh qq{ <metadata type=\"name\">%s</metadata>\n}, $object->name;
|
||||
}
|
||||
|
||||
printf $fh qq{ <mesh>\n};
|
||||
printf $fh qq{ <vertices>\n};
|
||||
my @vertices_offset = ();
|
||||
{
|
||||
my $vertices_offset = 0;
|
||||
foreach my $volume (@{ $object->volumes }) {
|
||||
push @vertices_offset, $vertices_offset;
|
||||
my $vertices = $volume->mesh->vertices;
|
||||
foreach my $vertex (@$vertices) {
|
||||
printf $fh qq{ <vertex>\n};
|
||||
printf $fh qq{ <coordinates>\n};
|
||||
printf $fh qq{ <x>%s</x>\n}, $vertex->[X];
|
||||
printf $fh qq{ <y>%s</y>\n}, $vertex->[Y];
|
||||
printf $fh qq{ <z>%s</z>\n}, $vertex->[Z];
|
||||
printf $fh qq{ </coordinates>\n};
|
||||
printf $fh qq{ </vertex>\n};
|
||||
}
|
||||
$vertices_offset += scalar(@$vertices);
|
||||
}
|
||||
}
|
||||
printf $fh qq{ </vertices>\n};
|
||||
foreach my $volume (@{ $object->volumes }) {
|
||||
my $vertices_offset = shift @vertices_offset;
|
||||
printf $fh qq{ <volume%s>\n},
|
||||
($volume->material_id eq '') ? '' : (sprintf ' materialid="%s"', $volume->material_id);
|
||||
|
||||
my $config = $volume->config;
|
||||
foreach my $opt_key (@{$config->get_keys}) {
|
||||
printf $fh qq{ <metadata type=\"slic3r.%s\">%s</metadata>\n}, $opt_key, $config->serialize($opt_key);
|
||||
}
|
||||
if ($volume->name) {
|
||||
printf $fh qq{ <metadata type=\"name\">%s</metadata>\n}, $volume->name;
|
||||
}
|
||||
if ($volume->modifier) {
|
||||
printf $fh qq{ <metadata type=\"slic3r.modifier\">1</metadata>\n};
|
||||
}
|
||||
|
||||
foreach my $facet (@{$volume->mesh->facets}) {
|
||||
printf $fh qq{ <triangle>\n};
|
||||
printf $fh qq{ <v%d>%d</v%d>\n}, $_, $facet->[$_-1] + $vertices_offset, $_ for 1..3;
|
||||
printf $fh qq{ </triangle>\n};
|
||||
}
|
||||
printf $fh qq{ </volume>\n};
|
||||
}
|
||||
printf $fh qq{ </mesh>\n};
|
||||
printf $fh qq{ </object>\n};
|
||||
if ($object->instances) {
|
||||
foreach my $instance (@{$object->instances}) {
|
||||
$instances .= sprintf qq{ <instance objectid="%d">\n}, $object_id;
|
||||
$instances .= sprintf qq{ <deltax>%s</deltax>\n}, $instance->offset->[X];
|
||||
$instances .= sprintf qq{ <deltay>%s</deltay>\n}, $instance->offset->[Y];
|
||||
$instances .= sprintf qq{ <rz>%s</rz>\n}, $instance->rotation;
|
||||
$instances .= sprintf qq{ </instance>\n};
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($instances) {
|
||||
printf $fh qq{ <constellation id="1">\n};
|
||||
printf $fh $instances;
|
||||
printf $fh qq{ </constellation>\n};
|
||||
}
|
||||
printf $fh qq{</amf>\n};
|
||||
close $fh;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,161 +0,0 @@
|
||||
package Slic3r::Format::AMF::Parser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'XML::SAX::Base';
|
||||
|
||||
my %xyz_index = (x => 0, y => 1, z => 2); #=
|
||||
|
||||
sub new {
|
||||
my $self = shift->SUPER::new(@_);
|
||||
$self->{_tree} = [];
|
||||
$self->{_objects_map} = {}; # this hash maps AMF object IDs to object indexes in $model->objects
|
||||
$self->{_instances} = {}; # apply these lazily to make sure all objects have been parsed
|
||||
$self;
|
||||
}
|
||||
|
||||
sub start_element {
|
||||
my $self = shift;
|
||||
my $data = shift;
|
||||
|
||||
if ($data->{LocalName} eq 'object') {
|
||||
$self->{_object} = $self->{_model}->add_object;
|
||||
$self->{_object_vertices} = [];
|
||||
$self->{_objects_map}{ $self->_get_attribute($data, 'id') } = $#{ $self->{_model}->objects };
|
||||
} elsif ($data->{LocalName} eq 'vertex') {
|
||||
$self->{_vertex} = ["", "", ""];
|
||||
} elsif ($self->{_vertex} && $data->{LocalName} =~ /^[xyz]$/ && $self->{_tree}[-1] eq 'coordinates') {
|
||||
$self->{_coordinate} = $data->{LocalName};
|
||||
} elsif ($data->{LocalName} eq 'volume') {
|
||||
$self->{_volume} = $self->{_object}->add_volume(
|
||||
material_id => $self->_get_attribute($data, 'materialid') // undef,
|
||||
mesh => Slic3r::TriangleMesh->new,
|
||||
);
|
||||
$self->{_volume_facets} = [];
|
||||
} elsif ($data->{LocalName} eq 'triangle') {
|
||||
$self->{_triangle} = ["", "", ""];
|
||||
} elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') {
|
||||
$self->{_vertex_idx} = $1-1;
|
||||
} elsif ($data->{LocalName} eq 'material') {
|
||||
my $material_id = $self->_get_attribute($data, 'id') // '_';
|
||||
$self->{_material} = $self->{_model}->set_material($material_id);
|
||||
} elsif ($data->{LocalName} eq 'metadata') {
|
||||
$self->{_metadata_type} = $self->_get_attribute($data, 'type');
|
||||
$self->{_metadata_value} = '';
|
||||
} elsif ($data->{LocalName} eq 'constellation') {
|
||||
$self->{_constellation} = 1; # we merge all constellations as we don't support more than one
|
||||
} elsif ($data->{LocalName} eq 'instance' && $self->{_constellation}) {
|
||||
my $object_id = $self->_get_attribute($data, 'objectid');
|
||||
$self->{_instances}{$object_id} ||= [];
|
||||
push @{ $self->{_instances}{$object_id} }, $self->{_instance} = {};
|
||||
} elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
|
||||
$self->{_instance_property} = $data->{LocalName};
|
||||
}
|
||||
|
||||
push @{$self->{_tree}}, $data->{LocalName};
|
||||
}
|
||||
|
||||
sub characters {
|
||||
my $self = shift;
|
||||
my $data = shift;
|
||||
|
||||
if ($self->{_vertex} && $self->{_coordinate}) {
|
||||
$self->{_vertex}[ $xyz_index{$self->{_coordinate}} ] .= $data->{Data};
|
||||
} elsif ($self->{_triangle} && defined $self->{_vertex_idx}) {
|
||||
$self->{_triangle}[ $self->{_vertex_idx} ] .= $data->{Data};
|
||||
} elsif ($self->{_metadata_type}) {
|
||||
$self->{_metadata_value} .= $data->{Data};
|
||||
} elsif ($self->{_instance_property}) {
|
||||
$self->{_instance}{ $self->{_instance_property} } .= $data->{Data};
|
||||
}
|
||||
}
|
||||
|
||||
sub end_element {
|
||||
my $self = shift;
|
||||
my $data = shift;
|
||||
|
||||
pop @{$self->{_tree}};
|
||||
|
||||
if ($data->{LocalName} eq 'object') {
|
||||
$self->{_object} = undef;
|
||||
$self->{_object_vertices} = undef;
|
||||
} elsif ($data->{LocalName} eq 'vertex') {
|
||||
push @{$self->{_object_vertices}}, $self->{_vertex};
|
||||
$self->{_vertex} = undef;
|
||||
} elsif ($self->{_coordinate} && $data->{LocalName} =~ /^[xyz]$/) {
|
||||
$self->{_coordinate} = undef;
|
||||
} elsif ($data->{LocalName} eq 'volume') {
|
||||
$self->{_volume}->mesh->ReadFromPerl($self->{_object_vertices}, $self->{_volume_facets});
|
||||
$self->{_volume}->mesh->repair;
|
||||
$self->{_volume} = undef;
|
||||
$self->{_volume_facets} = undef;
|
||||
} elsif ($data->{LocalName} eq 'triangle') {
|
||||
push @{$self->{_volume_facets}}, $self->{_triangle};
|
||||
$self->{_triangle} = undef;
|
||||
} elsif (defined $self->{_vertex_idx} && $data->{LocalName} =~ /^v[123]$/) {
|
||||
$self->{_vertex_idx} = undef;
|
||||
} elsif ($data->{LocalName} eq 'material') {
|
||||
$self->{_material} = undef;
|
||||
} elsif ($data->{LocalName} eq 'metadata') {
|
||||
my $value = $self->{_metadata_value};
|
||||
if ($self->{_metadata_type} =~ /^slic3r\.(.+)/) {
|
||||
my $opt_key = $1;
|
||||
if (exists $Slic3r::Config::Options->{$opt_key}) {
|
||||
my $config;
|
||||
if ($self->{_material}) {
|
||||
$config = $self->{_material}->config;
|
||||
} elsif ($self->{_volume}) {
|
||||
$config = $self->{_volume}->config;
|
||||
} elsif ($self->{_object}) {
|
||||
$config = $self->{_object}->config;
|
||||
}
|
||||
|
||||
$config->set_deserialize($opt_key, $value) if defined $config;
|
||||
} elsif ($opt_key eq 'modifier' && $self->{_volume}) {
|
||||
$self->{_volume}->set_modifier($value);
|
||||
}
|
||||
} elsif ($self->{_metadata_type} eq 'name' && $self->{_volume}) {
|
||||
$self->{_volume}->set_name($value);
|
||||
} elsif ($self->{_metadata_type} eq 'name' && $self->{_object}) {
|
||||
$self->{_object}->set_name($value);
|
||||
} elsif ($self->{_material}) {
|
||||
$self->{_material}->set_attribute($self->{_metadata_type}, $value);
|
||||
}
|
||||
$self->{_metadata_type} = undef;
|
||||
$self->{_metadata_value} = undef;
|
||||
} elsif ($data->{LocalName} eq 'constellation') {
|
||||
$self->{_constellation} = undef;
|
||||
} elsif ($data->{LocalName} eq 'instance') {
|
||||
$self->{_instance} = undef;
|
||||
} elsif ($data->{LocalName} =~ /^(?:deltax|deltay|rz)$/ && $self->{_instance}) {
|
||||
$self->{_instance_property} = undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub end_document {
|
||||
my $self = shift;
|
||||
|
||||
foreach my $object_id (keys %{ $self->{_instances} }) {
|
||||
my $new_object_id = $self->{_objects_map}{$object_id};
|
||||
if (!defined $new_object_id) {
|
||||
warn "Undefined object $object_id referenced in constellation\n";
|
||||
next;
|
||||
}
|
||||
|
||||
foreach my $instance (@{ $self->{_instances}{$object_id} }) {
|
||||
$self->{_model}->objects->[$new_object_id]->add_instance(
|
||||
rotation => $instance->{rz} || 0,
|
||||
offset => Slic3r::Pointf->new($instance->{deltax} || 0, $instance->{deltay} || 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _get_attribute {
|
||||
my $self = shift;
|
||||
my ($data, $name) = @_;
|
||||
|
||||
return +(map $_->{Value}, grep $_->{Name} eq $name, values %{$data->{Attributes}})[0];
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,34 +0,0 @@
|
||||
package Slic3r::Format::OBJ;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
|
||||
sub read_file {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n";
|
||||
my $vertices = [];
|
||||
my $facets = [];
|
||||
while (<$fh>) {
|
||||
if (/^v ([^ ]+)\s+([^ ]+)\s+([^ ]+)/) {
|
||||
push @$vertices, [$1, $2, $3];
|
||||
} elsif (/^f (\d+).*? (\d+).*? (\d+).*?/) {
|
||||
push @$facets, [ $1-1, $2-1, $3-1 ];
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
|
||||
my $mesh = Slic3r::TriangleMesh->new;
|
||||
$mesh->ReadFromPerl($vertices, $facets);
|
||||
$mesh->repair;
|
||||
|
||||
my $model = Slic3r::Model->new;
|
||||
|
||||
my $basename = basename($file);
|
||||
my $object = $model->add_object(input_file => $file, name => $basename);
|
||||
my $volume = $object->add_volume(mesh => $mesh, name => $basename);
|
||||
return $model;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,38 +0,0 @@
|
||||
package Slic3r::Format::STL;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
|
||||
sub read_file {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
my $path = Slic3r::encode_path($file);
|
||||
die "Failed to open $file\n" if !-e $path;
|
||||
|
||||
my $mesh = Slic3r::TriangleMesh->new;
|
||||
$mesh->ReadSTLFile($path);
|
||||
$mesh->repair;
|
||||
|
||||
my $model = Slic3r::Model->new;
|
||||
|
||||
my $basename = basename($file);
|
||||
my $object = $model->add_object(input_file => $file, name => $basename);
|
||||
my $volume = $object->add_volume(mesh => $mesh, name => $basename);
|
||||
return $model;
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my $self = shift;
|
||||
my ($file, $mesh, %params) = @_;
|
||||
|
||||
$mesh = $mesh->mesh if $mesh->isa('Slic3r::Model');
|
||||
|
||||
my $path = Slic3r::encode_path($file);
|
||||
|
||||
$params{binary}
|
||||
? $mesh->write_binary($path)
|
||||
: $mesh->write_ascii($path);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,713 +0,0 @@
|
||||
package Slic3r::GCode;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex offset_ex);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::Full->new });
|
||||
has 'placeholder_parser' => (is => 'rw', default => sub { Slic3r::GCode::PlaceholderParser->new });
|
||||
has 'standby_points' => (is => 'rw');
|
||||
has 'enable_loop_clipping' => (is => 'rw', default => sub {1});
|
||||
has 'enable_wipe' => (is => 'rw', default => sub {0}); # at least one extruder has wipe enabled
|
||||
has 'layer_count' => (is => 'ro', required => 1 );
|
||||
has '_layer_index' => (is => 'rw', default => sub {-1}); # just a counter
|
||||
has 'layer' => (is => 'rw');
|
||||
has '_layer_islands' => (is => 'rw');
|
||||
has '_upper_layer_islands' => (is => 'rw');
|
||||
has '_seam_position' => (is => 'ro', default => sub { {} }); # $object => pos
|
||||
has 'shift_x' => (is => 'rw', default => sub {0} );
|
||||
has 'shift_y' => (is => 'rw', default => sub {0} );
|
||||
has 'z' => (is => 'rw');
|
||||
has 'extruders' => (is => 'ro', default => sub {{}});
|
||||
has 'multiple_extruders' => (is => 'rw', default => sub {0});
|
||||
has 'extruder' => (is => 'rw');
|
||||
has 'external_mp' => (is => 'rw');
|
||||
has 'layer_mp' => (is => 'rw');
|
||||
has 'new_object' => (is => 'rw', default => sub {0});
|
||||
has 'straight_once' => (is => 'rw', default => sub {1});
|
||||
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
|
||||
has 'lifted' => (is => 'rw', default => sub {0} );
|
||||
has 'last_pos' => (is => 'rw', default => sub { Slic3r::Point->new(0,0) } );
|
||||
has 'last_fan_speed' => (is => 'rw', default => sub {0});
|
||||
has 'last_acceleration' => (is => 'rw', default => sub {0});
|
||||
has 'wipe_path' => (is => 'rw');
|
||||
|
||||
sub set_extruders {
|
||||
my ($self, $extruder_ids, $print_config) = @_;
|
||||
|
||||
foreach my $i (@$extruder_ids) {
|
||||
$self->extruders->{$i} = my $e = Slic3r::Extruder->new($i, $print_config);
|
||||
$self->enable_wipe(1) if $e->wipe;
|
||||
}
|
||||
|
||||
# we enable support for multiple extruder if any extruder greater than 0 is used
|
||||
# (even if prints only uses that one) since we need to output Tx commands
|
||||
# first extruder has index 0
|
||||
$self->multiple_extruders(max(@$extruder_ids) > 0);
|
||||
}
|
||||
|
||||
sub set_shift {
|
||||
my ($self, @shift) = @_;
|
||||
|
||||
# if shift increases (goes towards right), last_pos decreases because it goes towards left
|
||||
my @translate = (
|
||||
scale ($self->shift_x - $shift[X]),
|
||||
scale ($self->shift_y - $shift[Y]),
|
||||
);
|
||||
$self->last_pos->translate(@translate);
|
||||
$self->wipe_path->translate(@translate) if $self->wipe_path;
|
||||
|
||||
$self->shift_x($shift[X]);
|
||||
$self->shift_y($shift[Y]);
|
||||
}
|
||||
|
||||
sub change_layer {
|
||||
my ($self, $layer) = @_;
|
||||
|
||||
$self->layer($layer);
|
||||
$self->_layer_index($self->_layer_index + 1);
|
||||
|
||||
# avoid computing islands and overhangs if they're not needed
|
||||
$self->_layer_islands($layer->islands);
|
||||
$self->_upper_layer_islands($layer->upper_layer ? $layer->upper_layer->islands : []);
|
||||
if ($self->config->avoid_crossing_perimeters) {
|
||||
$self->layer_mp(Slic3r::MotionPlanner->new(
|
||||
union_ex([ map @$_, @{$layer->slices} ], 1),
|
||||
));
|
||||
}
|
||||
|
||||
my $gcode = "";
|
||||
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
# TODO: cap this to 99% and add an explicit M73 P100 in the end G-code
|
||||
$gcode .= sprintf "M73 P%s%s\n",
|
||||
int(99 * ($self->_layer_index / ($self->layer_count - 1))),
|
||||
($self->config->gcode_comments ? ' ; update progress' : '');
|
||||
}
|
||||
|
||||
$gcode .= $self->move_z($layer->print_z);
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
# this method accepts Z in unscaled coordinates
|
||||
sub move_z {
|
||||
my ($self, $z, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
|
||||
$z += $self->config->z_offset;
|
||||
my $current_z = $self->z;
|
||||
my $nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
|
||||
|
||||
if (!defined $current_z || $z > $current_z || $z < $nominal_z) {
|
||||
# we're moving above the current actual Z (so above the lift height of the current
|
||||
# layer if any) or below the current nominal layer
|
||||
|
||||
# in both cases, we're going to the nominal Z of the next layer
|
||||
$self->lifted(0);
|
||||
|
||||
if ($self->extruder->retract_layer_change) {
|
||||
# this retraction may alter $self->z
|
||||
$gcode .= $self->retract(move_z => $z);
|
||||
$current_z = $self->z; # update current z in case retract() changed it
|
||||
$nominal_z = defined $current_z ? ($current_z - $self->lifted) : undef;
|
||||
}
|
||||
$gcode .= $self->G0(undef, $z, 0, $self->config->travel_speed*60, $comment || ('move to next layer (' . $self->layer->id . ')'))
|
||||
if !defined $current_z || abs($z - $nominal_z) > epsilon;
|
||||
} elsif ($z < $current_z) {
|
||||
# we're moving above the current nominal layer height and below the current actual one.
|
||||
# we're basically advancing to next layer, whose nominal Z is still lower than the previous
|
||||
# layer Z with lift.
|
||||
$self->lifted($current_z - $z);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub extrude {
|
||||
my $self = shift;
|
||||
|
||||
$_[0]->isa('Slic3r::ExtrusionLoop')
|
||||
? $self->extrude_loop(@_)
|
||||
: $self->extrude_path(@_);
|
||||
}
|
||||
|
||||
sub extrude_loop {
|
||||
my ($self, $loop, $description, $speed) = @_;
|
||||
|
||||
# make a copy; don't modify the orientation of the original loop object otherwise
|
||||
# next copies (if any) would not detect the correct orientation
|
||||
$loop = $loop->clone;
|
||||
|
||||
# extrude all loops ccw
|
||||
my $was_clockwise = $loop->make_counter_clockwise;
|
||||
|
||||
# find the point of the loop that is closest to the current extruder position
|
||||
# or randomize if requested
|
||||
my $last_pos = $self->last_pos;
|
||||
if ($self->config->spiral_vase) {
|
||||
$loop->split_at($last_pos);
|
||||
} elsif ($self->config->seam_position eq 'nearest' || $self->config->seam_position eq 'aligned') {
|
||||
# simplify polygon in order to skip false positives in concave/convex detection
|
||||
my $polygon = $loop->polygon;
|
||||
my @simplified = @{$polygon->simplify(scale $self->extruder->nozzle_diameter/2)};
|
||||
|
||||
# concave vertices have priority
|
||||
my @candidates = map @{$_->concave_points(PI*4/3)}, @simplified;
|
||||
|
||||
# if no concave points were found, look for convex vertices
|
||||
@candidates = map @{$_->convex_points(PI*2/3)}, @simplified if !@candidates;
|
||||
|
||||
# retrieve the last start position for this object
|
||||
my $obj_ptr;
|
||||
if (defined $self->layer) {
|
||||
$obj_ptr = $self->layer->object->ptr;
|
||||
if (defined $self->_seam_position->{$self->layer->object}) {
|
||||
$last_pos = $self->_seam_position->{$obj_ptr};
|
||||
}
|
||||
}
|
||||
|
||||
my $point;
|
||||
if ($self->config->seam_position eq 'nearest') {
|
||||
@candidates = @$polygon if !@candidates;
|
||||
$point = $last_pos->nearest_point(\@candidates);
|
||||
$loop->split_at_vertex($point);
|
||||
} elsif (@candidates) {
|
||||
my @non_overhang = grep !$loop->has_overhang_point($_), @candidates;
|
||||
@candidates = @non_overhang if @non_overhang;
|
||||
$point = $last_pos->nearest_point(\@candidates);
|
||||
$loop->split_at_vertex($point);
|
||||
} else {
|
||||
$point = $last_pos->projection_onto_polygon($polygon);
|
||||
$loop->split_at($point);
|
||||
}
|
||||
$self->_seam_position->{$obj_ptr} = $point;
|
||||
} elsif ($self->config->seam_position eq 'random') {
|
||||
if ($loop->role == EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER) {
|
||||
my $polygon = $loop->polygon;
|
||||
my $centroid = $polygon->centroid;
|
||||
$last_pos = Slic3r::Point->new($polygon->bounding_box->x_max, $centroid->y); #))
|
||||
$last_pos->rotate(rand(2*PI), $centroid);
|
||||
}
|
||||
$loop->split_at($last_pos);
|
||||
}
|
||||
|
||||
# clip the path to avoid the extruder to get exactly on the first point of the loop;
|
||||
# if polyline was shorter than the clipping distance we'd get a null polyline, so
|
||||
# we discard it in that case
|
||||
my $clip_length = $self->enable_loop_clipping
|
||||
? scale($self->extruder->nozzle_diameter) * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER
|
||||
: 0;
|
||||
|
||||
# get paths
|
||||
my @paths = @{$loop->clip_end($clip_length)};
|
||||
return '' if !@paths;
|
||||
|
||||
# apply the small perimeter speed
|
||||
if ($paths[0]->is_perimeter && $loop->length <= &Slic3r::SMALL_PERIMETER_LENGTH) {
|
||||
$speed //= $self->config->get_abs_value('small_perimeter_speed');
|
||||
}
|
||||
$speed //= -1;
|
||||
|
||||
# extrude along the path
|
||||
my $gcode = join '', map $self->_extrude_path($_, $description, $speed), @paths;
|
||||
|
||||
# reset acceleration
|
||||
$gcode .= $self->set_acceleration($self->config->default_acceleration);
|
||||
|
||||
$self->wipe_path($paths[-1]->polyline->clone) if $self->enable_wipe; # TODO: don't limit wipe to last path
|
||||
|
||||
# make a little move inwards before leaving loop
|
||||
if ($paths[-1]->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->config->perimeters > 1) {
|
||||
my $last_path_polyline = $paths[-1]->polyline;
|
||||
# detect angle between last and first segment
|
||||
# the side depends on the original winding order of the polygon (left for contours, right for holes)
|
||||
my @points = $was_clockwise ? (-2, 1) : (1, -2);
|
||||
my $angle = Slic3r::Geometry::angle3points(@$last_path_polyline[0, @points]) / 3;
|
||||
$angle *= -1 if $was_clockwise;
|
||||
|
||||
# create the destination point along the first segment and rotate it
|
||||
# we make sure we don't exceed the segment length because we don't know
|
||||
# the rotation of the second segment so we might cross the object boundary
|
||||
my $first_segment = Slic3r::Line->new(@$last_path_polyline[0,1]);
|
||||
my $distance = min(scale($self->extruder->nozzle_diameter), $first_segment->length);
|
||||
my $point = $first_segment->point_at($distance);
|
||||
$point->rotate($angle, $last_path_polyline->first_point);
|
||||
|
||||
# generate the travel move
|
||||
$gcode .= $self->travel_to($point, $paths[-1]->role, "move inwards before travel");
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub extrude_path {
|
||||
my ($self, $path, $description, $speed) = @_;
|
||||
|
||||
my $gcode = $self->_extrude_path($path, $description, $speed);
|
||||
|
||||
# reset acceleration
|
||||
$gcode .= $self->set_acceleration($self->config->default_acceleration);
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _extrude_path {
|
||||
my ($self, $path, $description, $speed) = @_;
|
||||
|
||||
$path->simplify(&Slic3r::SCALED_RESOLUTION);
|
||||
|
||||
# go to first point of extrusion path
|
||||
my $gcode = "";
|
||||
{
|
||||
my $first_point = $path->first_point;
|
||||
$gcode .= $self->travel_to($first_point, $path->role, "move to first $description point")
|
||||
if !defined $self->last_pos || !$self->last_pos->coincides_with($first_point);
|
||||
}
|
||||
|
||||
# compensate retraction
|
||||
$gcode .= $self->unretract;
|
||||
|
||||
# adjust acceleration
|
||||
{
|
||||
my $acceleration;
|
||||
if ($self->config->first_layer_acceleration && $self->layer->id == 0) {
|
||||
$acceleration = $self->config->first_layer_acceleration;
|
||||
} elsif ($self->config->perimeter_acceleration && $path->is_perimeter) {
|
||||
$acceleration = $self->config->perimeter_acceleration;
|
||||
} elsif ($self->config->infill_acceleration && $path->is_fill) {
|
||||
$acceleration = $self->config->infill_acceleration;
|
||||
} elsif ($self->config->infill_acceleration && $path->is_bridge) {
|
||||
$acceleration = $self->config->bridge_acceleration;
|
||||
} else {
|
||||
$acceleration = $self->config->default_acceleration;
|
||||
}
|
||||
$gcode .= $self->set_acceleration($acceleration);
|
||||
}
|
||||
|
||||
# calculate extrusion length per distance unit
|
||||
my $e = $self->extruder->e_per_mm3 * $path->mm3_per_mm;
|
||||
$e = 0 if !$self->config->get_extrusion_axis;
|
||||
|
||||
# set speed
|
||||
my $F;
|
||||
if ($path->role == EXTR_ROLE_PERIMETER) {
|
||||
$F = $self->config->get_abs_value('perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_EXTERNAL_PERIMETER) {
|
||||
$F = $self->config->get_abs_value('external_perimeter_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_OVERHANG_PERIMETER || $path->role == EXTR_ROLE_BRIDGE) {
|
||||
$F = $self->config->get_abs_value('bridge_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_FILL) {
|
||||
$F = $self->config->get_abs_value('infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_SOLIDFILL) {
|
||||
$F = $self->config->get_abs_value('solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_TOPSOLIDFILL) {
|
||||
$F = $self->config->get_abs_value('top_solid_infill_speed');
|
||||
} elsif ($path->role == EXTR_ROLE_GAPFILL) {
|
||||
$F = $self->config->get_abs_value('gap_fill_speed');
|
||||
} else {
|
||||
$F = $speed // -1;
|
||||
die "Invalid speed" if $F < 0; # $speed == -1
|
||||
}
|
||||
$F *= 60; # convert mm/sec to mm/min
|
||||
|
||||
if ($self->layer->id == 0) {
|
||||
$F = $self->config->get_abs_value_over('first_layer_speed', $F/60) * 60;
|
||||
}
|
||||
|
||||
# extrude arc or line
|
||||
$gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge;
|
||||
my $path_length = unscale $path->length;
|
||||
{
|
||||
$gcode .= $path->gcode($self->extruder, $e, $F,
|
||||
$self->shift_x - $self->extruder->extruder_offset->x,
|
||||
$self->shift_y - $self->extruder->extruder_offset->y, #,,
|
||||
$self->config->get_extrusion_axis,
|
||||
$self->config->gcode_comments ? " ; $description" : "");
|
||||
|
||||
if ($self->enable_wipe) {
|
||||
$self->wipe_path($path->polyline->clone);
|
||||
$self->wipe_path->reverse;
|
||||
}
|
||||
}
|
||||
$gcode .= ";_BRIDGE_FAN_END\n" if $path->is_bridge;
|
||||
$self->last_pos($path->last_point);
|
||||
|
||||
if ($self->config->cooling) {
|
||||
my $path_time = $path_length / $F * 60;
|
||||
$self->elapsed_time($self->elapsed_time + $path_time);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub travel_to {
|
||||
my ($self, $point, $role, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
my $travel = Slic3r::Line->new($self->last_pos, $point);
|
||||
|
||||
# move travel back to original layer coordinates for the island check.
|
||||
# note that we're only considering the current object's islands, while we should
|
||||
# build a more complete configuration space
|
||||
$travel->translate(-$self->shift_x, -$self->shift_y);
|
||||
|
||||
# skip retraction if the travel move is contained in an island in the current layer
|
||||
# *and* in an island in the upper layer (so that the ooze will not be visible)
|
||||
if ($travel->length < scale $self->extruder->retract_before_travel
|
||||
|| ($self->config->only_retract_when_crossing_perimeters
|
||||
&& $self->config->fill_density > 0
|
||||
&& (first { $_->contains_line($travel) } @{$self->_upper_layer_islands})
|
||||
&& (first { $_->contains_line($travel) } @{$self->_layer_islands}))
|
||||
|| (defined $role && $role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->contains_line($travel) } @{$self->layer->support_islands}))
|
||||
) {
|
||||
$self->straight_once(0);
|
||||
$gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
|
||||
} elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) {
|
||||
$self->straight_once(0);
|
||||
$gcode .= $self->retract;
|
||||
$gcode .= $self->G0($point, undef, 0, $self->config->travel_speed*60, $comment || "");
|
||||
} else {
|
||||
if ($self->new_object) {
|
||||
$self->new_object(0);
|
||||
|
||||
# represent $point in G-code coordinates
|
||||
$point = $point->clone;
|
||||
my @shift = ($self->shift_x, $self->shift_y);
|
||||
$point->translate(map scale $_, @shift);
|
||||
|
||||
# calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
|
||||
$self->set_shift(0,0);
|
||||
$gcode .= $self->_plan($self->external_mp, $point, $comment);
|
||||
$self->set_shift(@shift);
|
||||
} else {
|
||||
$gcode .= $self->_plan($self->layer_mp, $point, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _plan {
|
||||
my ($self, $mp, $point, $comment) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
my @travel = @{$mp->shortest_path($self->last_pos, $point)->lines};
|
||||
|
||||
# if the path is not contained in a single island we need to retract
|
||||
my $need_retract = !$self->config->only_retract_when_crossing_perimeters;
|
||||
if (!$need_retract) {
|
||||
$need_retract = 1;
|
||||
foreach my $island (@{$self->_upper_layer_islands}) {
|
||||
# discard the island if at any line is not enclosed in it
|
||||
next if first { !$island->contains_line($_) } @travel;
|
||||
# okay, this island encloses the full travel path
|
||||
$need_retract = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# do the retract (the travel_to argument is broken)
|
||||
$gcode .= $self->retract if $need_retract;
|
||||
|
||||
# append the actual path and return
|
||||
# use G1 because we rely on paths being straight (G0 may make round paths)
|
||||
$gcode .= join '', map $self->G1($_->b, undef, 0, $self->config->travel_speed*60, $comment || ""), @travel;
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub retract {
|
||||
my ($self, %params) = @_;
|
||||
|
||||
# get the retraction length and abort if none
|
||||
my ($length, $restart_extra, $comment) = $params{toolchange}
|
||||
? ($self->extruder->retract_length_toolchange, $self->extruder->retract_restart_extra_toolchange, "retract for tool change")
|
||||
: ($self->extruder->retract_length, $self->extruder->retract_restart_extra, "retract");
|
||||
|
||||
# if we already retracted, reduce the required amount of retraction
|
||||
$length -= $self->extruder->retracted;
|
||||
return "" unless $length > 0;
|
||||
my $gcode = "";
|
||||
|
||||
# wipe
|
||||
my $wipe_path;
|
||||
if ($self->extruder->wipe && $self->wipe_path) {
|
||||
my @points = @{$self->wipe_path};
|
||||
$wipe_path = Slic3r::Polyline->new($self->last_pos, @{$self->wipe_path}[1..$#{$self->wipe_path}]);
|
||||
$wipe_path->clip_end($wipe_path->length - $self->extruder->scaled_wipe_distance($self->config->travel_speed));
|
||||
}
|
||||
|
||||
# prepare moves
|
||||
my $retract = [undef, undef, -$length, $self->extruder->retract_speed_mm_min, $comment];
|
||||
my $lift = ($self->config->retract_lift->[0] == 0 || defined $params{move_z}) && !$self->lifted
|
||||
? undef
|
||||
: [undef, $self->z + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'lift plate during travel'];
|
||||
|
||||
# check that we have a positive wipe length
|
||||
if ($wipe_path) {
|
||||
# subdivide the retraction
|
||||
my $retracted = 0;
|
||||
foreach my $line (@{$wipe_path->lines}) {
|
||||
my $segment_length = $line->length;
|
||||
# reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
|
||||
# due to rounding
|
||||
my $e = $retract->[2] * ($segment_length / $self->extruder->scaled_wipe_distance($self->config->travel_speed)) * 0.95;
|
||||
$retracted += $e;
|
||||
$gcode .= $self->G1($line->b, undef, $e, $self->config->travel_speed*60*0.8, $retract->[3] . ";_WIPE");
|
||||
}
|
||||
if ($retracted > $retract->[2]) {
|
||||
# if we retracted less than we had to, retract the remainder
|
||||
# TODO: add regression test
|
||||
$gcode .= $self->G1(undef, undef, $retract->[2] - $retracted, $self->extruder->retract_speed_mm_min, $comment);
|
||||
}
|
||||
$gcode .= $self->reset_e;
|
||||
} elsif ($self->config->use_firmware_retraction) {
|
||||
$gcode .= "G10 ; retract\n";
|
||||
} else {
|
||||
$gcode .= $self->G1(@$retract);
|
||||
|
||||
# reset extrusion distance during retracts
|
||||
# this makes sure we leave sufficient precision in the firmware
|
||||
$gcode .= $self->reset_e;
|
||||
}
|
||||
if (!$self->lifted) {
|
||||
if (defined $params{move_z} && $self->config->retract_lift->[0] > 0) {
|
||||
my $travel = [undef, $params{move_z} + $self->config->retract_lift->[0], 0, $self->config->travel_speed*60, 'move to next layer (' . $self->layer->id . ') and lift'];
|
||||
$gcode .= $self->G0(@$travel);
|
||||
$self->lifted($self->config->retract_lift->[0]);
|
||||
} elsif ($lift) {
|
||||
$gcode .= $self->G1(@$lift);
|
||||
}
|
||||
}
|
||||
$self->extruder->set_retracted($self->extruder->retracted + $length);
|
||||
$self->extruder->set_restart_extra($restart_extra);
|
||||
$self->lifted($self->config->retract_lift->[0]) if $lift;
|
||||
|
||||
$gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware';
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub unretract {
|
||||
my ($self) = @_;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware';
|
||||
|
||||
if ($self->lifted) {
|
||||
$gcode .= $self->G0(undef, $self->z - $self->lifted, 0, $self->config->travel_speed*60, 'restore layer Z');
|
||||
$self->lifted(0);
|
||||
}
|
||||
|
||||
my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra;
|
||||
if ($to_unretract) {
|
||||
if ($self->config->use_firmware_retraction) {
|
||||
$gcode .= "G11 ; unretract\n";
|
||||
$gcode .= $self->reset_e;
|
||||
} elsif ($self->config->get_extrusion_axis) {
|
||||
# use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
$gcode .= sprintf "G1 %s%.5f F%.3f",
|
||||
$self->config->get_extrusion_axis,
|
||||
$self->extruder->extrude($to_unretract),
|
||||
$self->extruder->retract_speed_mm_min;
|
||||
$gcode .= " ; compensate retraction" if $self->config->gcode_comments;
|
||||
$gcode .= "\n";
|
||||
}
|
||||
$self->extruder->set_retracted(0);
|
||||
$self->extruder->set_restart_extra(0);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub reset_e {
|
||||
my ($self) = @_;
|
||||
return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/;
|
||||
|
||||
$self->extruder->set_E(0) if $self->extruder;
|
||||
return sprintf "G92 %s0%s\n", $self->config->get_extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '')
|
||||
if $self->config->get_extrusion_axis && !$self->config->use_relative_e_distances;
|
||||
}
|
||||
|
||||
sub set_acceleration {
|
||||
my ($self, $acceleration) = @_;
|
||||
|
||||
return "" if !$acceleration || $acceleration == $self->last_acceleration;
|
||||
|
||||
$self->last_acceleration($acceleration);
|
||||
return sprintf "M204 S%s%s\n",
|
||||
$acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : '');
|
||||
}
|
||||
|
||||
sub G0 {
|
||||
my $self = shift;
|
||||
return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3');
|
||||
return $self->_G0_G1("G0", @_);
|
||||
}
|
||||
|
||||
sub G1 {
|
||||
my $self = shift;
|
||||
return $self->_G0_G1("G1", @_);
|
||||
}
|
||||
|
||||
sub _G0_G1 {
|
||||
my ($self, $gcode, $point, $z, $e, $F, $comment) = @_;
|
||||
|
||||
if ($point) {
|
||||
$gcode .= sprintf " X%.3f Y%.3f",
|
||||
($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->x,
|
||||
($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->y; #**
|
||||
$self->last_pos($point->clone);
|
||||
}
|
||||
if (defined $z && (!defined $self->z || $z != $self->z)) {
|
||||
$self->z($z);
|
||||
$gcode .= sprintf " Z%.3f", $z;
|
||||
}
|
||||
|
||||
return $self->_Gx($gcode, $e, $F, $comment);
|
||||
}
|
||||
|
||||
sub _Gx {
|
||||
my ($self, $gcode, $e, $F, $comment) = @_;
|
||||
|
||||
$gcode .= sprintf " F%.3f", $F;
|
||||
|
||||
# output extrusion distance
|
||||
if ($e && $self->config->get_extrusion_axis) {
|
||||
$gcode .= sprintf " %s%.5f", $self->config->get_extrusion_axis, $self->extruder->extrude($e);
|
||||
}
|
||||
|
||||
$gcode .= " ; $comment" if $comment && $self->config->gcode_comments;
|
||||
return "$gcode\n";
|
||||
}
|
||||
|
||||
sub set_extruder {
|
||||
my ($self, $extruder_id) = @_;
|
||||
|
||||
# return nothing if this extruder was already selected
|
||||
return "" if (defined $self->extruder) && ($self->extruder->id == $extruder_id);
|
||||
|
||||
# if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
if (!$self->multiple_extruders) {
|
||||
$self->extruder($self->extruders->{$extruder_id});
|
||||
return "";
|
||||
}
|
||||
|
||||
# trigger retraction on the current extruder (if any)
|
||||
my $gcode = "";
|
||||
$gcode .= $self->retract(toolchange => 1) if defined $self->extruder;
|
||||
|
||||
# append custom toolchange G-code
|
||||
if (defined $self->extruder && $self->config->toolchange_gcode) {
|
||||
$gcode .= sprintf "%s\n", $self->placeholder_parser->process($self->config->toolchange_gcode, {
|
||||
previous_extruder => $self->extruder->id,
|
||||
next_extruder => $extruder_id,
|
||||
});
|
||||
}
|
||||
|
||||
# set the current extruder to the standby temperature
|
||||
if ($self->standby_points && defined $self->extruder) {
|
||||
# move to the nearest standby point
|
||||
{
|
||||
my $last_pos = $self->last_pos->clone;
|
||||
$last_pos->translate(scale +$self->shift_x, scale +$self->shift_y);
|
||||
my $standby_point = $last_pos->nearest_point($self->standby_points);
|
||||
$standby_point->translate(scale -$self->shift_x, scale -$self->shift_y);
|
||||
$gcode .= $self->travel_to($standby_point);
|
||||
}
|
||||
|
||||
if ($self->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $self->layer && $self->layer->id == 0
|
||||
? $self->extruder->first_layer_temperature
|
||||
: $self->extruder->temperature;
|
||||
# we assume that heating is always slower than cooling, so no need to block
|
||||
$gcode .= $self->set_temperature($temp + $self->config->standby_temperature_delta, 0);
|
||||
}
|
||||
}
|
||||
|
||||
# set the new extruder
|
||||
$self->extruder($self->extruders->{$extruder_id});
|
||||
$gcode .= sprintf "%s%d%s\n",
|
||||
($self->config->gcode_flavor eq 'makerware'
|
||||
? 'M135 T'
|
||||
: $self->config->gcode_flavor eq 'sailfish'
|
||||
? 'M108 T'
|
||||
: 'T'),
|
||||
$extruder_id,
|
||||
($self->config->gcode_comments ? ' ; change extruder' : '');
|
||||
|
||||
$gcode .= $self->reset_e;
|
||||
|
||||
# set the new extruder to the operating temperature
|
||||
if ($self->config->ooze_prevention && $self->config->standby_temperature_delta != 0) {
|
||||
my $temp = defined $self->layer && $self->layer->id == 0
|
||||
? $self->extruder->first_layer_temperature
|
||||
: $self->extruder->temperature;
|
||||
$gcode .= $self->set_temperature($temp, 1);
|
||||
}
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub set_fan {
|
||||
my ($self, $speed, $dont_save) = @_;
|
||||
|
||||
if ($self->last_fan_speed != $speed || $dont_save) {
|
||||
$self->last_fan_speed($speed) if !$dont_save;
|
||||
if ($speed == 0) {
|
||||
my $code = $self->config->gcode_flavor eq 'teacup'
|
||||
? 'M106 S0'
|
||||
: $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/
|
||||
? 'M127'
|
||||
: 'M107';
|
||||
return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : '');
|
||||
} else {
|
||||
if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) {
|
||||
return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : '');
|
||||
} else {
|
||||
return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'),
|
||||
(255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : '');
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub set_temperature {
|
||||
my ($self, $temperature, $wait, $tool) = @_;
|
||||
|
||||
return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/;
|
||||
|
||||
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
|
||||
? ('M109', 'wait for temperature to be reached')
|
||||
: ('M104', 'set temperature');
|
||||
my $gcode = sprintf "$code %s%d %s; $comment\n",
|
||||
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature,
|
||||
(defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : "";
|
||||
|
||||
$gcode .= "M116 ; wait for temperature to be reached\n"
|
||||
if $self->config->gcode_flavor eq 'teacup' && $wait;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub set_bed_temperature {
|
||||
my ($self, $temperature, $wait) = @_;
|
||||
|
||||
my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup')
|
||||
? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached')
|
||||
: ('M140', 'set bed temperature');
|
||||
my $gcode = sprintf "$code %s%d ; $comment\n",
|
||||
($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature;
|
||||
|
||||
$gcode .= "M116 ; wait for bed temperature to be reached\n"
|
||||
if $self->config->gcode_flavor eq 'teacup' && $wait;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,242 +0,0 @@
|
||||
package Slic3r::GCode::ArcFitting;
|
||||
use Moo;
|
||||
|
||||
use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points);
|
||||
|
||||
extends 'Slic3r::GCode::Reader';
|
||||
has 'config' => (is => 'ro', required => 0);
|
||||
has 'min_segments' => (is => 'rw', default => sub { 2 });
|
||||
has 'min_total_angle' => (is => 'rw', default => sub { deg2rad(30) });
|
||||
has 'max_relative_angle' => (is => 'rw', default => sub { deg2rad(15) });
|
||||
has 'len_epsilon' => (is => 'rw', default => sub { scale 0.2 });
|
||||
has 'angle_epsilon' => (is => 'rw', default => sub { abs(deg2rad(10)) });
|
||||
has '_extrusion_axis' => (is => 'lazy');
|
||||
has '_path' => (is => 'rw');
|
||||
has '_cur_F' => (is => 'rw');
|
||||
has '_cur_E' => (is => 'rw');
|
||||
has '_cur_E0' => (is => 'rw');
|
||||
has '_comment' => (is => 'rw');
|
||||
|
||||
sub _build__extrusion_axis {
|
||||
my ($self) = @_;
|
||||
return $self->config ? $self->config->get_extrusion_axis : 'E';
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my ($gcode) = @_;
|
||||
|
||||
die "Arc fitting is not available (incomplete feature)\n";
|
||||
die "Arc fitting doesn't support extrusion axis not being E\n" if $self->_extrusion_axis ne 'E';
|
||||
|
||||
my $new_gcode = "";
|
||||
|
||||
$self->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
# this is an extrusion segment
|
||||
|
||||
# get segment
|
||||
my $line = Slic3r::Line->new(
|
||||
Slic3r::Point->new_scale($self->X, $self->Y),
|
||||
Slic3r::Point->new_scale($args->{X}, $args->{Y}),
|
||||
);
|
||||
|
||||
# get segment speed
|
||||
my $F = $args->{F} // $reader->F;
|
||||
|
||||
# get extrusion per unscaled distance unit
|
||||
my $e = $info->{dist_E} / unscale($line->length);
|
||||
|
||||
if ($self->_path && $F == $self->_cur_F && abs($e - $self->_cur_E) < epsilon) {
|
||||
# if speed and extrusion per unit are the same as the previous segments,
|
||||
# append this segment to path
|
||||
$self->_path->append($line->b);
|
||||
} elsif ($self->_path) {
|
||||
# segment can't be appended to previous path, so we flush the previous one
|
||||
# and start over
|
||||
$new_gcode .= $self->path_to_gcode;
|
||||
$self->_path(undef);
|
||||
}
|
||||
|
||||
if (!$self->_path) {
|
||||
# if this is the first segment of a path, start it from scratch
|
||||
$self->_path(Slic3r::Polyline->new(@$line));
|
||||
$self->_cur_F($F);
|
||||
$self->_cur_E($e);
|
||||
$self->_cur_E0($self->E);
|
||||
$self->_comment($info->{comment});
|
||||
}
|
||||
} else {
|
||||
# if we have a path, we flush it and go on
|
||||
$new_gcode .= $self->path_to_gcode if $self->_path;
|
||||
$new_gcode .= $info->{raw} . "\n";
|
||||
$self->_path(undef);
|
||||
}
|
||||
});
|
||||
|
||||
$new_gcode .= $self->path_to_gcode if $self->_path;
|
||||
return $new_gcode;
|
||||
}
|
||||
|
||||
sub path_to_gcode {
|
||||
my ($self) = @_;
|
||||
|
||||
my @chunks = $self->detect_arcs($self->_path);
|
||||
|
||||
my $gcode = "";
|
||||
my $E = $self->_cur_E0;
|
||||
foreach my $chunk (@chunks) {
|
||||
if ($chunk->isa('Slic3r::Polyline')) {
|
||||
my @lines = @{$chunk->lines};
|
||||
|
||||
$gcode .= sprintf "G1 F%s\n", $self->_cur_F;
|
||||
foreach my $line (@lines) {
|
||||
$E += $self->_cur_E * unscale($line->length);
|
||||
$gcode .= sprintf "G1 X%.3f Y%.3f %s%.5f",
|
||||
(map unscale($_), @{$line->b}),
|
||||
$self->_extrusion_axis, $E;
|
||||
$gcode .= sprintf " ; %s", $self->_comment if $self->_comment;
|
||||
$gcode .= "\n";
|
||||
}
|
||||
} elsif ($chunk->isa('Slic3r::GCode::ArcFitting::Arc')) {
|
||||
$gcode .= !$chunk->is_ccw ? "G2" : "G3";
|
||||
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$chunk->end}; # destination point
|
||||
|
||||
# XY distance of the center from the start position
|
||||
$gcode .= sprintf " I%.3f", unscale($chunk->center->[X] - $chunk->start->[X]);
|
||||
$gcode .= sprintf " J%.3f", unscale($chunk->center->[Y] - $chunk->start->[Y]);
|
||||
|
||||
$E += $self->_cur_E * unscale($chunk->length);
|
||||
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $E;
|
||||
|
||||
$gcode .= sprintf " F%s\n", $self->_cur_F;
|
||||
}
|
||||
}
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub detect_arcs {
|
||||
my ($self, $path) = @_;
|
||||
|
||||
my @chunks = ();
|
||||
my @arc_points = ();
|
||||
my $polyline = undef;
|
||||
my $arc_start = undef;
|
||||
|
||||
my @points = @$path;
|
||||
for (my $i = 1; $i <= $#points; ++$i) {
|
||||
my $end = undef;
|
||||
|
||||
# we need at least three points to check whether they form an arc
|
||||
if ($i < $#points) {
|
||||
my $len = $points[$i-1]->distance_to($points[$i]);
|
||||
my $rel_angle = PI - angle3points(@points[$i, $i-1, $i+1]);
|
||||
if (abs($rel_angle) <= $self->max_relative_angle) {
|
||||
for (my $j = $i+1; $j <= $#points; ++$j) {
|
||||
# check whether @points[($i-1)..$j] form an arc
|
||||
last if abs($points[$j-1]->distance_to($points[$j]) - $len) > $self->len_epsilon;
|
||||
last if abs(PI - angle3points(@points[$j-1, $j-2, $j]) - $rel_angle) > $self->angle_epsilon;
|
||||
|
||||
$end = $j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defined $end && ($end - $i + 1) >= $self->min_segments) {
|
||||
my $arc = polyline_to_arc(Slic3r::Polyline->new(@points[($i-1)..$end]));
|
||||
|
||||
if (1||$arc->angle >= $self->min_total_angle) {
|
||||
push @chunks, $arc;
|
||||
|
||||
# continue scanning after arc points
|
||||
$i = $end;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# if last chunk was a polyline, append to it
|
||||
if (@chunks && $chunks[-1]->isa('Slic3r::Polyline')) {
|
||||
$chunks[-1]->append($points[$i]);
|
||||
} else {
|
||||
push @chunks, Slic3r::Polyline->new(@points[($i-1)..$i]);
|
||||
}
|
||||
}
|
||||
|
||||
return @chunks;
|
||||
}
|
||||
|
||||
sub polyline_to_arc {
|
||||
my ($polyline) = @_;
|
||||
|
||||
my @points = @$polyline;
|
||||
|
||||
my $is_ccw = $points[2]->ccw(@points[0,1]) > 0;
|
||||
|
||||
# to find the center, we intersect the perpendicular lines
|
||||
# passing by first and last vertex;
|
||||
# a better method would be to draw all the perpendicular lines
|
||||
# and find the centroid of the enclosed polygon, or to
|
||||
# intersect multiple lines and find the centroid of the convex hull
|
||||
# around the intersections
|
||||
my $arc_center;
|
||||
{
|
||||
my $first_ray = Slic3r::Line->new(@points[0,1]);
|
||||
$first_ray->rotate(PI/2 * ($is_ccw ? 1 : -1), $points[0]);
|
||||
|
||||
my $last_ray = Slic3r::Line->new(@points[-2,-1]);
|
||||
$last_ray->rotate(PI/2 * ($is_ccw ? -1 : 1), $points[-1]);
|
||||
|
||||
# require non-parallel rays in order to compute an accurate center
|
||||
return if abs($first_ray->atan2_ - $last_ray->atan2_) < deg2rad(30);
|
||||
|
||||
$arc_center = $first_ray->intersection($last_ray, 0) or return;
|
||||
}
|
||||
|
||||
# angle measured in ccw orientation
|
||||
my $abs_angle = Slic3r::Geometry::angle3points($arc_center, @points[0,-1]);
|
||||
|
||||
my $rel_angle = $is_ccw
|
||||
? $abs_angle
|
||||
: (2*PI - $abs_angle);
|
||||
|
||||
my $arc = Slic3r::GCode::ArcFitting::Arc->new(
|
||||
start => $points[0]->clone,
|
||||
end => $points[-1]->clone,
|
||||
center => $arc_center,
|
||||
is_ccw => $is_ccw || 0,
|
||||
angle => $rel_angle,
|
||||
);
|
||||
|
||||
if (0) {
|
||||
printf "points = %d, path length = %f, arc angle = %f, arc length = %f\n",
|
||||
scalar(@points),
|
||||
unscale(Slic3r::Polyline->new(@points)->length),
|
||||
Slic3r::Geometry::rad2deg($rel_angle),
|
||||
unscale($arc->length);
|
||||
}
|
||||
|
||||
return $arc;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::ArcFitting::Arc;
|
||||
use Moo;
|
||||
|
||||
has 'start' => (is => 'ro', required => 1);
|
||||
has 'end' => (is => 'ro', required => 1);
|
||||
has 'center' => (is => 'ro', required => 1);
|
||||
has 'is_ccw' => (is => 'ro', required => 1);
|
||||
has 'angle' => (is => 'ro', required => 1);
|
||||
|
||||
sub radius {
|
||||
my ($self) = @_;
|
||||
return $self->start->distance_to($self->center);
|
||||
}
|
||||
|
||||
sub length {
|
||||
my ($self) = @_;
|
||||
return $self->radius * $self->angle;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,80 +0,0 @@
|
||||
package Slic3r::GCode::CoolingBuffer;
|
||||
use Moo;
|
||||
|
||||
has 'config' => (is => 'ro', required => 1); # Slic3r::Config::Print
|
||||
has 'gcodegen' => (is => 'ro', required => 1);
|
||||
has 'gcode' => (is => 'rw', default => sub {""});
|
||||
has 'elapsed_time' => (is => 'rw', default => sub {0});
|
||||
has 'layer_id' => (is => 'rw');
|
||||
has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table)
|
||||
has 'min_print_speed' => (is => 'lazy');
|
||||
|
||||
sub _build_min_print_speed {
|
||||
my $self = shift;
|
||||
return 60 * $self->config->min_print_speed;
|
||||
}
|
||||
|
||||
sub append {
|
||||
my $self = shift;
|
||||
my ($gcode, $obj_id, $layer_id, $print_z) = @_;
|
||||
|
||||
my $return = "";
|
||||
if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) {
|
||||
$return = $self->flush;
|
||||
}
|
||||
|
||||
$self->layer_id($layer_id);
|
||||
$self->last_z->{$obj_id} = $print_z;
|
||||
$self->gcode($self->gcode . $gcode);
|
||||
$self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time);
|
||||
$self->gcodegen->elapsed_time(0);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
sub flush {
|
||||
my $self = shift;
|
||||
|
||||
my $gcode = $self->gcode;
|
||||
my $elapsed = $self->elapsed_time;
|
||||
$self->gcode("");
|
||||
$self->elapsed_time(0);
|
||||
$self->last_z({}); # reset the whole table otherwise we would compute overlapping times
|
||||
|
||||
my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0;
|
||||
my $speed_factor = 1;
|
||||
if ($self->config->cooling) {
|
||||
Slic3r::debugf "Layer %d estimated printing time: %d seconds\n", $self->layer_id, $elapsed;
|
||||
if ($elapsed < $self->config->slowdown_below_layer_time) {
|
||||
$fan_speed = $self->config->max_fan_speed;
|
||||
$speed_factor = $elapsed / $self->config->slowdown_below_layer_time;
|
||||
} elsif ($elapsed < $self->config->fan_below_layer_time) {
|
||||
$fan_speed = $self->config->max_fan_speed - ($self->config->max_fan_speed - $self->config->min_fan_speed)
|
||||
* ($elapsed - $self->config->slowdown_below_layer_time)
|
||||
/ ($self->config->fan_below_layer_time - $self->config->slowdown_below_layer_time); #/
|
||||
}
|
||||
Slic3r::debugf " fan = %d%%, speed = %d%%\n", $fan_speed, $speed_factor * 100;
|
||||
|
||||
if ($speed_factor < 1) {
|
||||
$gcode =~ s/^(?=.*? [XY])(?=.*? E)(?!;_WIPE)(?<!;_BRIDGE_FAN_START\n)(G1 .*?F)(\d+(?:\.\d+)?)/
|
||||
my $new_speed = $2 * $speed_factor;
|
||||
$1 . sprintf("%.3f", $new_speed < $self->min_print_speed ? $self->min_print_speed : $new_speed)
|
||||
/gexm;
|
||||
}
|
||||
}
|
||||
$fan_speed = 0 if $self->layer_id < $self->config->disable_fan_first_layers;
|
||||
$gcode = $self->gcodegen->set_fan($fan_speed) . $gcode;
|
||||
|
||||
# bridge fan speed
|
||||
if (!$self->config->cooling || $self->config->bridge_fan_speed == 0 || $self->layer_id < $self->config->disable_fan_first_layers) {
|
||||
$gcode =~ s/^;_BRIDGE_FAN_(?:START|END)\n//gm;
|
||||
} else {
|
||||
$gcode =~ s/^;_BRIDGE_FAN_START\n/ $self->gcodegen->set_fan($self->config->bridge_fan_speed, 1) /gmex;
|
||||
$gcode =~ s/^;_BRIDGE_FAN_END\n/ $self->gcodegen->set_fan($fan_speed, 1) /gmex;
|
||||
}
|
||||
$gcode =~ s/;_WIPE//g;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,238 +0,0 @@
|
||||
package Slic3r::GCode::Layer;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(X Y unscale);
|
||||
|
||||
has 'print' => (is => 'ro', required => 1);
|
||||
has 'gcodegen' => (is => 'ro', required => 1, handles => [qw(extruders)]);
|
||||
has 'shift' => (is => 'ro', default => sub { [0,0] });
|
||||
|
||||
has 'spiralvase' => (is => 'lazy');
|
||||
has 'vibration_limit' => (is => 'lazy');
|
||||
has 'arc_fitting' => (is => 'lazy');
|
||||
has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1
|
||||
has 'brim_done' => (is => 'rw');
|
||||
has 'second_layer_things_done' => (is => 'rw');
|
||||
has '_last_obj_copy' => (is => 'rw');
|
||||
|
||||
sub _build_spiralvase {
|
||||
my $self = shift;
|
||||
|
||||
return $self->print->config->spiral_vase
|
||||
? Slic3r::GCode::SpiralVase->new(config => $self->print->config)
|
||||
: undef;
|
||||
}
|
||||
|
||||
sub _build_vibration_limit {
|
||||
my $self = shift;
|
||||
|
||||
return $self->print->config->vibration_limit
|
||||
? Slic3r::GCode::VibrationLimit->new(config => $self->print->config)
|
||||
: undef;
|
||||
}
|
||||
|
||||
sub _build_arc_fitting {
|
||||
my $self = shift;
|
||||
|
||||
return $self->print->config->gcode_arcs
|
||||
? Slic3r::GCode::ArcFitting->new(config => $self->print->config)
|
||||
: undef;
|
||||
}
|
||||
|
||||
sub process_layer {
|
||||
my $self = shift;
|
||||
my ($layer, $object_copies) = @_;
|
||||
my $gcode = "";
|
||||
|
||||
my $object = $layer->object;
|
||||
$self->gcodegen->config->apply_object_config($object->config);
|
||||
|
||||
# check whether we're going to apply spiralvase logic
|
||||
if (defined $self->spiralvase) {
|
||||
$self->spiralvase->enable(
|
||||
($layer->id > 0 || $self->print->config->brim_width == 0)
|
||||
&& ($layer->id >= $self->print->config->skirt_height && $self->print->config->skirt_height != -1)
|
||||
&& !defined(first { $_->config->bottom_solid_layers > $layer->id } @{$layer->regions})
|
||||
&& !defined(first { @{$_->perimeters} > 1 } @{$layer->regions})
|
||||
&& !defined(first { @{$_->fills} > 0 } @{$layer->regions})
|
||||
);
|
||||
}
|
||||
|
||||
# if we're going to apply spiralvase to this layer, disable loop clipping
|
||||
$self->gcodegen->enable_loop_clipping(!defined $self->spiralvase || !$self->spiralvase->enable);
|
||||
|
||||
if (!$self->second_layer_things_done && $layer->id == 1) {
|
||||
for my $extruder_id (sort keys %{$self->extruders}) {
|
||||
my $extruder = $self->extruders->{$extruder_id};
|
||||
$gcode .= $self->gcodegen->set_temperature($extruder->temperature, 0, $extruder->id)
|
||||
if $extruder->temperature && $extruder->temperature != $extruder->first_layer_temperature;
|
||||
}
|
||||
$gcode .= $self->gcodegen->set_bed_temperature($self->print->config->bed_temperature)
|
||||
if $self->print->config->bed_temperature && $self->print->config->bed_temperature != $self->print->config->first_layer_bed_temperature;
|
||||
$self->second_layer_things_done(1);
|
||||
}
|
||||
|
||||
# set new layer - this will change Z and force a retraction if retract_layer_change is enabled
|
||||
$gcode .= $self->gcodegen->change_layer($layer);
|
||||
$gcode .= $self->gcodegen->placeholder_parser->process($self->print->config->layer_gcode, {
|
||||
layer_num => $self->gcodegen->layer->id,
|
||||
}) . "\n" if $self->print->config->layer_gcode;
|
||||
|
||||
# extrude skirt
|
||||
if (((values %{$self->skirt_done}) < $self->print->config->skirt_height || $self->print->config->skirt_height == -1)
|
||||
&& !$self->skirt_done->{$layer->print_z}) {
|
||||
$self->gcodegen->set_shift(@{$self->shift});
|
||||
my @extruder_ids = sort keys %{$self->extruders};
|
||||
$gcode .= $self->gcodegen->set_extruder($extruder_ids[0]);
|
||||
# skip skirt if we have a large brim
|
||||
if ($layer->id < $self->print->config->skirt_height || $self->print->config->skirt_height == -1) {
|
||||
# distribute skirt loops across all extruders
|
||||
my @skirt_loops = @{$self->print->skirt};
|
||||
for my $i (0 .. $#skirt_loops) {
|
||||
# when printing layers > 0 ignore 'min_skirt_length' and
|
||||
# just use the 'skirts' setting; also just use the current extruder
|
||||
last if ($layer->id > 0) && ($i >= $self->print->config->skirts);
|
||||
my $extruder_id = $extruder_ids[($i/@extruder_ids) % @extruder_ids];
|
||||
$gcode .= $self->gcodegen->set_extruder($extruder_id)
|
||||
if $layer->id == 0;
|
||||
$gcode .= $self->gcodegen->extrude_loop($skirt_loops[$i], 'skirt', $object->config->support_material_speed);
|
||||
}
|
||||
}
|
||||
$self->skirt_done->{$layer->print_z} = 1;
|
||||
$self->gcodegen->straight_once(1);
|
||||
}
|
||||
|
||||
# extrude brim
|
||||
if (!$self->brim_done) {
|
||||
$gcode .= $self->gcodegen->set_extruder($self->print->objects->[0]->config->support_material_extruder-1);
|
||||
$self->gcodegen->set_shift(@{$self->shift});
|
||||
$gcode .= $self->gcodegen->extrude_loop($_, 'brim', $object->config->support_material_speed)
|
||||
for @{$self->print->brim};
|
||||
$self->brim_done(1);
|
||||
$self->gcodegen->straight_once(1);
|
||||
}
|
||||
|
||||
for my $copy (@$object_copies) {
|
||||
$self->gcodegen->new_object(1) if ($self->_last_obj_copy // '') ne "$copy";
|
||||
$self->_last_obj_copy("$copy");
|
||||
|
||||
$self->gcodegen->set_shift(map $self->shift->[$_] + unscale $copy->[$_], X,Y);
|
||||
|
||||
# extrude support material before other things because it might use a lower Z
|
||||
# and also because we avoid travelling on other things when printing it
|
||||
if ($layer->isa('Slic3r::Layer::Support')) {
|
||||
if ($layer->support_interface_fills->count > 0) {
|
||||
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_interface_extruder-1);
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material interface', $object->config->get_abs_value('support_material_interface_speed'))
|
||||
for @{$layer->support_interface_fills->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
}
|
||||
if ($layer->support_fills->count > 0) {
|
||||
$gcode .= $self->gcodegen->set_extruder($object->config->support_material_extruder-1);
|
||||
$gcode .= $self->gcodegen->extrude_path($_, 'support material', $object->config->get_abs_value('support_material_speed'))
|
||||
for @{$layer->support_fills->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
}
|
||||
}
|
||||
|
||||
# tweak region ordering to save toolchanges
|
||||
my @region_ids = 0 .. ($self->print->region_count-1);
|
||||
if ($self->gcodegen->multiple_extruders) {
|
||||
my $last_extruder = $self->gcodegen->extruder;
|
||||
my $best_region_id = first { $self->print->regions->[$_]->config->perimeter_extruder-1 eq $last_extruder } @region_ids;
|
||||
@region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id;
|
||||
}
|
||||
|
||||
foreach my $region_id (@region_ids) {
|
||||
my $layerm = $layer->regions->[$region_id] or next;
|
||||
my $region = $self->print->regions->[$region_id];
|
||||
$self->gcodegen->config->apply_region_config($region->config);
|
||||
|
||||
# group extrusions by island
|
||||
my @perimeters_by_island = map [], 0..$#{$layer->slices}; # slice idx => @perimeters
|
||||
my @infill_by_island = map [], 0..$#{$layer->slices}; # slice idx => @fills
|
||||
|
||||
# NOTE: we assume $layer->slices was already ordered with chained_path()!
|
||||
|
||||
PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($perimeter->first_point)) {
|
||||
push @{ $perimeters_by_island[$i] }, $perimeter;
|
||||
next PERIMETER;
|
||||
}
|
||||
}
|
||||
push @{ $perimeters_by_island[-1] }, $perimeter; # optimization
|
||||
}
|
||||
FILL: foreach my $fill (@{$layerm->fills}) {
|
||||
for my $i (0 .. $#{$layer->slices}-1) {
|
||||
if ($layer->slices->[$i]->contour->contains_point($fill->first_point)) {
|
||||
push @{ $infill_by_island[$i] }, $fill;
|
||||
next FILL;
|
||||
}
|
||||
}
|
||||
push @{ $infill_by_island[-1] }, $fill; # optimization
|
||||
}
|
||||
|
||||
for my $i (0 .. $#{$layer->slices}) {
|
||||
# give priority to infill if we were already using its extruder and it wouldn't
|
||||
# be good for perimeters
|
||||
if ($self->print->config->infill_first
|
||||
|| ($self->gcodegen->multiple_extruders && $region->config->infill_extruder-1 == $self->gcodegen->extruder->id && $region->config->infill_extruder != $region->config->perimeter_extruder)) {
|
||||
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
|
||||
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
|
||||
} else {
|
||||
$gcode .= $self->_extrude_perimeters($perimeters_by_island[$i], $region);
|
||||
$gcode .= $self->_extrude_infill($infill_by_island[$i], $region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# apply spiral vase post-processing if this layer contains suitable geometry
|
||||
# (we must feed all the G-code into the post-processor, including the first
|
||||
# bottom non-spiral layers otherwise it will mess with positions)
|
||||
$gcode = $self->spiralvase->process_layer($gcode)
|
||||
if defined $self->spiralvase;
|
||||
|
||||
# apply vibration limit if enabled
|
||||
$gcode = $self->vibration_limit->process($gcode)
|
||||
if $self->print->config->vibration_limit != 0;
|
||||
|
||||
# apply arc fitting if enabled
|
||||
$gcode = $self->arc_fitting->process($gcode)
|
||||
if $self->print->config->gcode_arcs;
|
||||
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _extrude_perimeters {
|
||||
my $self = shift;
|
||||
my ($island_perimeters, $region) = @_;
|
||||
|
||||
return "" if !@$island_perimeters;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= $self->gcodegen->set_extruder($region->config->perimeter_extruder-1);
|
||||
$gcode .= $self->gcodegen->extrude($_, 'perimeter') for @$island_perimeters;
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
sub _extrude_infill {
|
||||
my $self = shift;
|
||||
my ($island_fills, $region) = @_;
|
||||
|
||||
return "" if !@$island_fills;
|
||||
|
||||
my $gcode = "";
|
||||
$gcode .= $self->gcodegen->set_extruder($region->config->infill_extruder-1);
|
||||
for my $fill (@$island_fills) {
|
||||
if ($fill->isa('Slic3r::ExtrusionPath::Collection')) {
|
||||
$gcode .= $self->gcodegen->extrude($_, 'fill')
|
||||
for @{$fill->chained_path_from($self->gcodegen->last_pos, 0)};
|
||||
} else {
|
||||
$gcode .= $self->gcodegen->extrude($fill, 'fill') ;
|
||||
}
|
||||
}
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,317 +0,0 @@
|
||||
package Slic3r::GCode::MotionPlanner;
|
||||
use Moo;
|
||||
|
||||
has 'islands' => (is => 'ro', required => 1); # arrayref of ExPolygons
|
||||
has 'internal' => (is => 'ro', default => sub { 1 });
|
||||
has '_space' => (is => 'ro', default => sub { Slic3r::GCode::MotionPlanner::ConfigurationSpace->new });
|
||||
has '_inner' => (is => 'ro', default => sub { [] }); # arrayref of ExPolygons
|
||||
|
||||
use List::Util qw(first max);
|
||||
use Slic3r::Geometry qw(A B scale epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(offset offset_ex diff_ex intersection_pl);
|
||||
|
||||
# clearance (in mm) from the perimeters
|
||||
has '_inner_margin' => (is => 'ro', default => sub { scale 1 });
|
||||
has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
|
||||
|
||||
# this factor weigths the crossing of a perimeter
|
||||
# vs. the alternative path. a value of 5 means that
|
||||
# a perimeter will be crossed if the alternative path
|
||||
# is >= 5x the length of the straight line we could
|
||||
# follow if we decided to cross the perimeter.
|
||||
# a nearly-infinite value for this will only permit
|
||||
# perimeter crossing when there's no alternative path.
|
||||
use constant CROSSING_PENALTY => 20;
|
||||
|
||||
use constant POINT_DISTANCE => 10; # unscaled
|
||||
|
||||
# setup our configuration space
|
||||
sub BUILD {
|
||||
my $self = shift;
|
||||
|
||||
my $point_distance = scale POINT_DISTANCE;
|
||||
my $nodes = $self->_space->nodes;
|
||||
my $edges = $self->_space->edges;
|
||||
|
||||
# process individual islands
|
||||
for my $i (0 .. $#{$self->islands}) {
|
||||
my $expolygon = $self->islands->[$i];
|
||||
|
||||
# find external margin
|
||||
my $outer = offset([ @$expolygon ], +$self->_outer_margin);
|
||||
my @outer_points = map @{$_->equally_spaced_points($point_distance)}, @$outer;
|
||||
|
||||
# add outer points to graph
|
||||
my $o_outer = $self->_space->add_nodes(@outer_points);
|
||||
|
||||
# find pairs of visible outer points and add them to the graph
|
||||
for my $i (0 .. $#outer_points) {
|
||||
for my $j (($i+1) .. $#outer_points) {
|
||||
my ($a, $b) = ($outer_points[$i], $outer_points[$j]);
|
||||
my $line = Slic3r::Polyline->new($a, $b);
|
||||
# outer points are visible when their line has empty intersection with islands
|
||||
my $intersection = intersection_pl(
|
||||
[ $line ],
|
||||
[ map @$_, @{$self->islands} ],
|
||||
);
|
||||
if (!@$intersection) {
|
||||
$self->_space->add_edge($i+$o_outer, $j+$o_outer, $line->length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($self->internal) {
|
||||
# find internal margin
|
||||
my $inner = offset_ex([ @$expolygon ], -$self->_inner_margin);
|
||||
push @{ $self->_inner }, @$inner;
|
||||
my @inner_points = map @{$_->equally_spaced_points($point_distance)}, map @$_, @$inner;
|
||||
|
||||
# add points to graph and get their offset
|
||||
my $o_inner = $self->_space->add_nodes(@inner_points);
|
||||
|
||||
# find pairs of visible inner points and add them to the graph
|
||||
for my $i (0 .. $#inner_points) {
|
||||
for my $j (($i+1) .. $#inner_points) {
|
||||
my ($a, $b) = ($inner_points[$i], $inner_points[$j]);
|
||||
my $line = Slic3r::Line->new($a, $b);
|
||||
# turn $inner into an ExPolygonCollection and use $inner->contains_line()
|
||||
if (first { $_->contains_line($line) } @$inner) {
|
||||
$self->_space->add_edge($i+$o_inner, $j+$o_inner, $line->length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# generate the stripe around slice contours
|
||||
my $contour = diff_ex(
|
||||
$outer,
|
||||
[ map @$_, @$inner ],
|
||||
);
|
||||
|
||||
# find pairs of visible points in this area and add them to the graph
|
||||
for my $i (0 .. $#inner_points) {
|
||||
for my $j (0 .. $#outer_points) {
|
||||
my ($a, $b) = ($inner_points[$i], $outer_points[$j]);
|
||||
my $line = Slic3r::Line->new($a, $b);
|
||||
# turn $contour into an ExPolygonCollection and use $contour->contains_line()
|
||||
if (first { $_->contains_line($line) } @$contour) {
|
||||
$self->_space->add_edge($i+$o_inner, $j+$o_outer, $line->length * CROSSING_PENALTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# since Perl has no infinity symbol and we don't want to overcomplicate
|
||||
# the Dijkstra algorithm with string constants or -1 values
|
||||
$self->_space->_infinity(10 * (max(map values %$_, values %{$self->_space->edges}) // 0));
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("space.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => $self->islands,
|
||||
lines => $self->_space->get_lines,
|
||||
points => $self->_space->nodes,
|
||||
);
|
||||
printf "%d islands\n", scalar @{$self->islands};
|
||||
|
||||
eval "use Devel::Size";
|
||||
print "MEMORY USAGE:\n";
|
||||
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->$_)/1024/1024
|
||||
for qw(_space islands);
|
||||
printf " %-19s = %.1fMb\n", $_, Devel::Size::total_size($self->_space->$_)/1024/1024
|
||||
for qw(nodes edges);
|
||||
printf " %-19s = %.1fMb\n", 'self', Devel::Size::total_size($self)/1024/1024;
|
||||
|
||||
exit if $self->internal;
|
||||
}
|
||||
}
|
||||
|
||||
sub shortest_path {
|
||||
my $self = shift;
|
||||
my ($from, $to) = @_;
|
||||
|
||||
return Slic3r::Polyline->new($from, $to)
|
||||
if !@{$self->_space->nodes};
|
||||
|
||||
# create a temporary configuration space
|
||||
my $space = $self->_space->clone;
|
||||
|
||||
# add from/to points to the temporary configuration space
|
||||
my $node_from = $self->_add_point_to_space($from, $space);
|
||||
my $node_to = $self->_add_point_to_space($to, $space);
|
||||
|
||||
# compute shortest path
|
||||
my $path = $space->shortest_path($node_from, $node_to);
|
||||
|
||||
if (!$path->is_valid) {
|
||||
Slic3r::debugf "Failed to compute shortest path.\n";
|
||||
return Slic3r::Polyline->new($from, $to);
|
||||
}
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("path.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => $self->islands,
|
||||
lines => $space->get_lines,
|
||||
red_points => [$from, $to],
|
||||
red_polylines => [$path],
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
# returns the index of the new node
|
||||
sub _add_point_to_space {
|
||||
my ($self, $point, $space) = @_;
|
||||
|
||||
my $n = $space->add_nodes($point);
|
||||
|
||||
# check whether we are inside an island or outside
|
||||
my $inside = defined first { $self->islands->[$_]->contains_point($point) } 0..$#{$self->islands};
|
||||
|
||||
# find candidates by checking visibility from $from to them
|
||||
foreach my $idx (0..$#{$space->nodes}) {
|
||||
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
|
||||
# if $point is inside an island, it is visible from $idx when island contains their line
|
||||
# if $point is outside an island, it is visible from $idx when their line does not cross any island
|
||||
if (
|
||||
($inside && defined first { $_->contains_line($line) } @{$self->_inner})
|
||||
|| (!$inside && !@{intersection_pl(
|
||||
[ $line->as_polyline ],
|
||||
[ map @$_, @{$self->islands} ],
|
||||
)})
|
||||
) {
|
||||
# $n ($point) and $idx are visible
|
||||
$space->add_edge($n, $idx, $line->length);
|
||||
}
|
||||
}
|
||||
|
||||
# if we found no visibility, retry with larger margins
|
||||
if (!exists $space->edges->{$n} && $inside) {
|
||||
foreach my $idx (0..$#{$space->nodes}) {
|
||||
my $line = Slic3r::Line->new($point, $space->nodes->[$idx]);
|
||||
if (defined first { $_->contains_line($line) } @{$self->islands}) {
|
||||
# $n ($point) and $idx are visible
|
||||
$space->add_edge($n, $idx, $line->length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn "Temporary node is not visible from any other node"
|
||||
if !exists $space->edges->{$n};
|
||||
|
||||
return $n;
|
||||
}
|
||||
|
||||
package Slic3r::GCode::MotionPlanner::ConfigurationSpace;
|
||||
use Moo;
|
||||
|
||||
has 'nodes' => (is => 'rw', default => sub { [] }); # [ Point, ... ]
|
||||
has 'edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
|
||||
has '_infinity' => (is => 'rw');
|
||||
|
||||
sub clone {
|
||||
my $self = shift;
|
||||
|
||||
return (ref $self)->new(
|
||||
nodes => [ map $_->clone, @{$self->nodes} ],
|
||||
edges => { map { $_ => { %{$self->edges->{$_}} } } keys %{$self->edges} },
|
||||
_infinity => $self->_infinity,
|
||||
);
|
||||
}
|
||||
|
||||
sub nodes_count {
|
||||
my $self = shift;
|
||||
return scalar(@{ $self->nodes });
|
||||
}
|
||||
|
||||
sub add_nodes {
|
||||
my ($self, @nodes) = @_;
|
||||
|
||||
my $offset = $self->nodes_count;
|
||||
push @{ $self->nodes }, @nodes;
|
||||
return $offset;
|
||||
}
|
||||
|
||||
sub add_edge {
|
||||
my ($self, $a, $b, $dist) = @_;
|
||||
$self->edges->{$a}{$b} = $self->edges->{$b}{$a} = $dist;
|
||||
}
|
||||
|
||||
sub shortest_path {
|
||||
my ($self, $node_from, $node_to) = @_;
|
||||
|
||||
my $edges = $self->edges;
|
||||
my (%dist, %visited, %prev);
|
||||
$dist{$_} = $self->_infinity for keys %$edges;
|
||||
$dist{$node_from} = 0;
|
||||
|
||||
my @queue = ($node_from);
|
||||
while (@queue) {
|
||||
my $u = -1;
|
||||
{
|
||||
# find node in @queue with smallest distance in %dist and has not been visited
|
||||
my $d = -1;
|
||||
foreach my $n (@queue) {
|
||||
next if $visited{$n};
|
||||
if ($u == -1 || $dist{$n} < $d) {
|
||||
$u = $n;
|
||||
$d = $dist{$n};
|
||||
}
|
||||
}
|
||||
}
|
||||
last if $u == $node_to;
|
||||
|
||||
# remove $u from @queue
|
||||
@queue = grep $_ != $u, @queue;
|
||||
$visited{$u} = 1;
|
||||
|
||||
# loop through neighbors of $u
|
||||
foreach my $v (keys %{ $edges->{$u} }) {
|
||||
my $alt = $dist{$u} + $edges->{$u}{$v};
|
||||
if ($alt < $dist{$v}) {
|
||||
$dist{$v} = $alt;
|
||||
$prev{$v} = $u;
|
||||
if (!$visited{$v}) {
|
||||
push @queue, $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @points = ();
|
||||
{
|
||||
my $u = $node_to;
|
||||
while (exists $prev{$u}) {
|
||||
unshift @points, $self->nodes->[$u];
|
||||
$u = $prev{$u};
|
||||
}
|
||||
unshift @points, $self->nodes->[$node_from];
|
||||
}
|
||||
|
||||
return Slic3r::Polyline->new(@points);
|
||||
}
|
||||
|
||||
# for debugging purposes
|
||||
sub get_lines {
|
||||
my $self = shift;
|
||||
|
||||
my @lines = ();
|
||||
my %lines = ();
|
||||
for my $i (keys %{$self->edges}) {
|
||||
for my $j (keys %{$self->edges->{$i}}) {
|
||||
my $line_id = join '_', sort $i, $j;
|
||||
next if $lines{$line_id};
|
||||
$lines{$line_id} = 1;
|
||||
push @lines, Slic3r::Line->new(map $self->nodes->[$_], $i, $j);
|
||||
}
|
||||
}
|
||||
|
||||
return [@lines];
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,62 +0,0 @@
|
||||
package Slic3r::GCode::PlaceholderParser;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new {
|
||||
# TODO: move this code to C++ constructor, remove this method
|
||||
my ($class) = @_;
|
||||
my $self = $class->_new;
|
||||
$self->apply_env_variables;
|
||||
$self->update_timestamp;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub apply_env_variables {
|
||||
my ($self) = @_;
|
||||
$self->_single_set($_, $ENV{$_}) for grep /^SLIC3R_/, keys %ENV;
|
||||
}
|
||||
|
||||
sub update_timestamp {
|
||||
my ($self) = @_;
|
||||
|
||||
my @lt = localtime; $lt[5] += 1900; $lt[4] += 1;
|
||||
$self->_single_set('timestamp', sprintf '%04d%02d%02d-%02d%02d%02d', @lt[5,4,3,2,1,0]);
|
||||
$self->_single_set('year', "$lt[5]");
|
||||
$self->_single_set('month', "$lt[4]");
|
||||
$self->_single_set('day', "$lt[3]");
|
||||
$self->_single_set('hour', "$lt[2]");
|
||||
$self->_single_set('minute', "$lt[1]");
|
||||
$self->_single_set('second', "$lt[0]");
|
||||
$self->_single_set('version', $Slic3r::VERSION);
|
||||
}
|
||||
|
||||
# TODO: or this could be an alias
|
||||
sub set {
|
||||
my ($self, $key, $val) = @_;
|
||||
$self->_single_set($key, $val);
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self, $string, $extra) = @_;
|
||||
|
||||
# extra variables have priority over the stored ones
|
||||
if ($extra) {
|
||||
my $regex = join '|', keys %$extra;
|
||||
$string =~ s/\[($regex)\]/$extra->{$1}/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', @{$self->_single_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_single_get("$1")/eg;
|
||||
}
|
||||
{
|
||||
my $regex = join '|', @{$self->_multiple_keys};
|
||||
$string =~ s/\[($regex)\]/$self->_multiple_get("$1")/egx;
|
||||
|
||||
# unhandled indices are populated using the first value
|
||||
$string =~ s/\[($regex)_\d+\]/$self->_multiple_get("$1")/egx;
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,19 +1,31 @@
|
||||
# Helper module to parse and interpret a G-code file,
|
||||
# invoking a callback for each move extracted from the G-code.
|
||||
# Currently used by the automatic tests only.
|
||||
package Slic3r::GCode::Reader;
|
||||
use Moo;
|
||||
|
||||
has 'config' => (is => 'ro', default => sub { Slic3r::Config::GCode->new });
|
||||
has 'X' => (is => 'rw', default => sub {0});
|
||||
has 'Y' => (is => 'rw', default => sub {0});
|
||||
has 'Z' => (is => 'rw', default => sub {0});
|
||||
has 'E' => (is => 'rw', default => sub {0});
|
||||
has 'F' => (is => 'rw', default => sub {0});
|
||||
has '_extrusion_axis' => (is => 'rw', default => sub {"E"});
|
||||
|
||||
our $Verbose = 0;
|
||||
my @AXES = qw(X Y Z E);
|
||||
|
||||
sub apply_print_config {
|
||||
my ($self, $print_config) = @_;
|
||||
|
||||
$self->config->apply_static($print_config);
|
||||
$self->_extrusion_axis($self->config->get_extrusion_axis);
|
||||
}
|
||||
|
||||
sub clone {
|
||||
my $self = shift;
|
||||
return (ref $self)->new(
|
||||
map { $_ => $self->$_ } (@AXES, 'F'),
|
||||
map { $_ => $self->$_ } (@AXES, 'F', '_extrusion_axis', 'config'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,10 +44,16 @@ sub parse {
|
||||
$command //= '';
|
||||
my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args;
|
||||
|
||||
# convert extrusion axis
|
||||
if (exists $args{ $self->_extrusion_axis }) {
|
||||
$args{E} = $args{ $self->_extrusion_axis };
|
||||
}
|
||||
|
||||
# check motion
|
||||
if ($command =~ /^G[01]$/) {
|
||||
foreach my $axis (@AXES) {
|
||||
if (exists $args{$axis}) {
|
||||
$self->$axis(0) if $axis eq 'E' && $self->config->use_relative_e_distances;
|
||||
$info{"dist_$axis"} = $args{$axis} - $self->$axis;
|
||||
$info{"new_$axis"} = $args{$axis};
|
||||
} else {
|
||||
@@ -43,7 +61,7 @@ sub parse {
|
||||
$info{"new_$axis"} = $self->$axis;
|
||||
}
|
||||
}
|
||||
$info{dist_XY} = Slic3r::Geometry::unscale(Slic3r::Line->new_scale([0,0], [@info{qw(dist_X dist_Y)}])->length);
|
||||
$info{dist_XY} = sqrt(($info{dist_X}**2) + ($info{dist_Y}**2));
|
||||
if (exists $args{E}) {
|
||||
if ($info{dist_E} > 0) {
|
||||
$info{extruding} = 1;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package Slic3r::GCode::SpiralVase;
|
||||
use Moo;
|
||||
|
||||
has 'config' => (is => 'ro', required => 1);
|
||||
has 'enable' => (is => 'rw', default => sub { 0 });
|
||||
has 'reader' => (is => 'ro', default => sub { Slic3r::GCode::Reader->new });
|
||||
|
||||
use Slic3r::Geometry qw(unscale);
|
||||
|
||||
sub process_layer {
|
||||
my $self = shift;
|
||||
my ($gcode) = @_;
|
||||
|
||||
# This post-processor relies on several assumptions:
|
||||
# - all layers are processed through it, including those that are not supposed
|
||||
# to be transformed, in order to update the reader with the XY positions
|
||||
# - each call to this method includes a full layer, with a single Z move
|
||||
# at the beginning
|
||||
# - each layer is composed by suitable geometry (i.e. a single complete loop)
|
||||
# - loops were not clipped before calling this method
|
||||
|
||||
# if we're not going to modify G-code, just feed it to the reader
|
||||
# in order to update positions
|
||||
if (!$self->enable) {
|
||||
$self->reader->parse($gcode, sub {});
|
||||
return $gcode;
|
||||
}
|
||||
|
||||
# get total XY length for this layer by summing all extrusion moves
|
||||
my $total_layer_length = 0;
|
||||
my $layer_height = 0;
|
||||
my $z = undef;
|
||||
$self->reader->clone->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($info->{extruding}) {
|
||||
$total_layer_length += $info->{dist_XY};
|
||||
} elsif (exists $args->{Z}) {
|
||||
$layer_height += $info->{dist_Z};
|
||||
$z //= $args->{Z};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#use XXX; XXX [ $gcode, $layer_height, $z, $total_layer_length ];
|
||||
# remove layer height from initial Z
|
||||
$z -= $layer_height;
|
||||
|
||||
my $new_gcode = "";
|
||||
$self->reader->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && exists $args->{Z}) {
|
||||
# if this is the initial Z move of the layer, replace it with a
|
||||
# (redundant) move to the last Z of previous layer
|
||||
my $line = $info->{raw};
|
||||
$line =~ s/ Z[.0-9]+/ Z$z/;
|
||||
$new_gcode .= "$line\n";
|
||||
} elsif ($cmd eq 'G1' && !exists($args->{Z}) && $info->{dist_XY}) {
|
||||
# horizontal move
|
||||
my $line = $info->{raw};
|
||||
if ($info->{extruding}) {
|
||||
$z += $info->{dist_XY} * $layer_height / $total_layer_length;
|
||||
$line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e;
|
||||
$new_gcode .= "$line\n";
|
||||
}
|
||||
# skip travel moves: the move to first perimeter point will
|
||||
# cause a visible seam when loops are not aligned in XY; by skipping
|
||||
# it we blend the first loop move in the XY plane (although the smoothness
|
||||
# of such blend depend on how long the first segment is; maybe we should
|
||||
# enforce some minimum length?)
|
||||
} else {
|
||||
$new_gcode .= "$info->{raw}\n";
|
||||
}
|
||||
});
|
||||
|
||||
return $new_gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,64 +0,0 @@
|
||||
package Slic3r::GCode::VibrationLimit;
|
||||
use Moo;
|
||||
|
||||
extends 'Slic3r::GCode::Reader';
|
||||
|
||||
has 'config' => (is => 'ro', required => 1);
|
||||
has '_min_time' => (is => 'lazy');
|
||||
has '_last_dir' => (is => 'ro', default => sub { [0,0] });
|
||||
has '_dir_time' => (is => 'ro', default => sub { [0,0] });
|
||||
|
||||
# inspired by http://hydraraptor.blogspot.it/2010/12/frequency-limit.html
|
||||
|
||||
use List::Util qw(max);
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
|
||||
sub _build__min_time {
|
||||
my ($self) = @_;
|
||||
return 1 / ($self->config->vibration_limit * 60); # in minutes
|
||||
}
|
||||
|
||||
sub process {
|
||||
my $self = shift;
|
||||
my ($gcode) = @_;
|
||||
|
||||
my $new_gcode = "";
|
||||
$self->parse($gcode, sub {
|
||||
my ($reader, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1' && $info->{dist_XY} > 0) {
|
||||
my $point = Slic3r::Point->new($args->{X} // $reader->X, $args->{Y} // $reader->Y);
|
||||
my @dir = (
|
||||
($point->x <=> $reader->X),
|
||||
($point->y <=> $reader->Y), #$
|
||||
);
|
||||
my $time = $info->{dist_XY} / ($args->{F} // $reader->F); # in minutes
|
||||
if ($time > 0) {
|
||||
my @pause = ();
|
||||
foreach my $axis (X,Y) {
|
||||
if ($dir[$axis] != 0 && $self->_last_dir->[$axis] != $dir[$axis]) {
|
||||
if ($self->_last_dir->[$axis] != 0) {
|
||||
# this axis is changing direction: check whether we need to pause
|
||||
if ($self->_dir_time->[$axis] < $self->_min_time) {
|
||||
push @pause, ($self->_min_time - $self->_dir_time->[$axis]);
|
||||
}
|
||||
}
|
||||
$self->_last_dir->[$axis] = $dir[$axis];
|
||||
$self->_dir_time->[$axis] = 0;
|
||||
}
|
||||
$self->_dir_time->[$axis] += $time;
|
||||
}
|
||||
|
||||
if (@pause) {
|
||||
$new_gcode .= sprintf "G4 P%d\n", max(@pause) * 60 * 1000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$new_gcode .= $info->{raw} . "\n";
|
||||
});
|
||||
|
||||
return $new_gcode;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -5,142 +5,234 @@ use utf8;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use FindBin;
|
||||
use Slic3r::GUI::AboutDialog;
|
||||
use Slic3r::GUI::BedShapeDialog;
|
||||
use Slic3r::GUI::ConfigWizard;
|
||||
use List::Util qw(first);
|
||||
use Slic3r::GUI::2DBed;
|
||||
use Slic3r::GUI::Controller;
|
||||
use Slic3r::GUI::Controller::ManualControlDialog;
|
||||
use Slic3r::GUI::Controller::PrinterPanel;
|
||||
use Slic3r::GUI::MainFrame;
|
||||
use Slic3r::GUI::Notifier;
|
||||
use Slic3r::GUI::Plater;
|
||||
use Slic3r::GUI::Plater::2D;
|
||||
use Slic3r::GUI::Plater::2DToolpaths;
|
||||
use Slic3r::GUI::Plater::3D;
|
||||
use Slic3r::GUI::Plater::3DPreview;
|
||||
use Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use Slic3r::GUI::Plater::ObjectPreviewDialog;
|
||||
use Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use Slic3r::GUI::Plater::LambdaObjectDialog;
|
||||
use Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use Slic3r::GUI::Preferences;
|
||||
use Slic3r::GUI::ProgressStatusBar;
|
||||
use Slic3r::GUI::OptionsGroup;
|
||||
use Slic3r::GUI::OptionsGroup::Field;
|
||||
use Slic3r::GUI::SimpleTab;
|
||||
use Slic3r::GUI::Tab;
|
||||
use Slic3r::GUI::SystemInfo;
|
||||
|
||||
our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1";
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow
|
||||
:filedialog);
|
||||
use Wx::Event qw(EVT_IDLE);
|
||||
our $have_OpenGL = eval "use Slic3r::GUI::3DScene; 1";
|
||||
|
||||
use Wx 0.9901 qw(:bitmap :dialog :icon :id :misc :systemsettings :toplevelwindow :filedialog :font);
|
||||
use Wx::Event qw(EVT_IDLE EVT_COMMAND EVT_MENU);
|
||||
use base 'Wx::App';
|
||||
|
||||
use constant FILE_WILDCARDS => {
|
||||
known => 'Known files (*.stl, *.obj, *.amf, *.xml)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML',
|
||||
known => 'Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.zip.amf;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA',
|
||||
stl => 'STL files (*.stl)|*.stl;*.STL',
|
||||
obj => 'OBJ files (*.obj)|*.obj;*.OBJ',
|
||||
amf => 'AMF files (*.amf)|*.amf;*.AMF;*.xml;*.XML',
|
||||
amf => 'AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML',
|
||||
threemf => '3MF files (*.3mf)|*.3mf;*.3MF',
|
||||
prusa => 'Prusa Control files (*.prusa)|*.prusa;*.PRUSA',
|
||||
ini => 'INI files *.ini|*.ini;*.INI',
|
||||
gcode => 'G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC',
|
||||
svg => 'SVG files *.svg|*.svg;*.SVG',
|
||||
};
|
||||
use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf)};
|
||||
use constant MODEL_WILDCARD => join '|', @{&FILE_WILDCARDS}{qw(known stl obj amf threemf prusa)};
|
||||
|
||||
# Datadir provided on the command line.
|
||||
our $datadir;
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
our $no_plater;
|
||||
our $mode;
|
||||
our $autosave;
|
||||
our @cb;
|
||||
|
||||
our $Settings = {
|
||||
_ => {
|
||||
mode => 'simple',
|
||||
version_check => 1,
|
||||
autocenter => 1,
|
||||
background_processing => 1,
|
||||
},
|
||||
};
|
||||
|
||||
our $have_button_icons = &Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/;
|
||||
our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$small_font->SetPointSize(11) if !&Wx::wxMSW;
|
||||
$small_font->SetPointSize(11) if &Wx::wxMAC;
|
||||
our $small_bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$small_bold_font->SetPointSize(11) if &Wx::wxMAC;
|
||||
$small_bold_font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$medium_font->SetPointSize(12);
|
||||
our $grey = Wx::Colour->new(200,200,200);
|
||||
|
||||
# Events to be sent from a C++ menu implementation:
|
||||
# 1) To inform about a change of the application language.
|
||||
our $LANGUAGE_CHANGE_EVENT = Wx::NewEventType;
|
||||
# 2) To inform about a change of Preferences.
|
||||
our $PREFERENCES_EVENT = Wx::NewEventType;
|
||||
# To inform AppConfig about Slic3r version available online
|
||||
our $VERSION_ONLINE_EVENT = Wx::NewEventType;
|
||||
|
||||
sub OnInit {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->SetAppName('Slic3r');
|
||||
$self->SetAppName('Slic3rPE');
|
||||
$self->SetAppDisplayName('Slic3r Prusa Edition');
|
||||
Slic3r::debugf "wxWidgets version %s, Wx version %s\n", &Wx::wxVERSION_STRING, $Wx::VERSION;
|
||||
|
||||
$self->{notifier} = Slic3r::GUI::Notifier->new;
|
||||
|
||||
# locate or create data directory
|
||||
$datadir ||= Wx::StandardPaths::Get->GetUserDataDir;
|
||||
$datadir = Slic3r::encode_path($datadir);
|
||||
Slic3r::debugf "Data directory: %s\n", $datadir;
|
||||
|
||||
# just checking for existence of $datadir is not enough: it may be an empty directory
|
||||
|
||||
# Set the Slic3r data directory at the Slic3r XS module.
|
||||
# Unix: ~/.Slic3r
|
||||
# Windows: "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
|
||||
# Mac: "~/Library/Application Support/Slic3r"
|
||||
Slic3r::set_data_dir($datadir || Wx::StandardPaths::Get->GetUserDataDir);
|
||||
Slic3r::GUI::set_wxapp($self);
|
||||
|
||||
$self->{app_config} = Slic3r::GUI::AppConfig->new;
|
||||
Slic3r::GUI::set_app_config($self->{app_config});
|
||||
$self->{preset_bundle} = Slic3r::GUI::PresetBundle->new;
|
||||
Slic3r::GUI::set_preset_bundle($self->{preset_bundle});
|
||||
|
||||
# just checking for existence of Slic3r::data_dir is not enough: it may be an empty directory
|
||||
# supplied as argument to --datadir; in that case we should still run the wizard
|
||||
my $run_wizard = (-d $datadir && -e "$datadir/slic3r.ini") ? 0 : 1;
|
||||
for ($datadir, "$datadir/print", "$datadir/filament", "$datadir/printer") {
|
||||
mkdir or $self->fatal_error("Slic3r was unable to create its data directory at $_ (errno: $!).")
|
||||
unless -d $_;
|
||||
eval { $self->{preset_bundle}->setup_directories() };
|
||||
if ($@) {
|
||||
warn $@ . "\n";
|
||||
fatal_error(undef, $@);
|
||||
}
|
||||
|
||||
my $app_conf_exists = $self->{app_config}->exists;
|
||||
# load settings
|
||||
my $last_version;
|
||||
if (-f "$datadir/slic3r.ini") {
|
||||
my $ini = eval { Slic3r::Config->read_ini("$datadir/slic3r.ini") };
|
||||
$Settings = $ini if $ini;
|
||||
$last_version = $Settings->{_}{version};
|
||||
$Settings->{_}{mode} ||= 'expert';
|
||||
$Settings->{_}{autocenter} //= 1;
|
||||
$Settings->{_}{background_processing} //= 1;
|
||||
$self->{app_config}->load if $app_conf_exists;
|
||||
$self->{app_config}->set('version', $Slic3r::VERSION);
|
||||
$self->{app_config}->save;
|
||||
|
||||
$self->{preset_updater} = Slic3r::PresetUpdater->new($VERSION_ONLINE_EVENT);
|
||||
Slic3r::GUI::set_preset_updater($self->{preset_updater});
|
||||
|
||||
Slic3r::GUI::load_language();
|
||||
|
||||
# Suppress the '- default -' presets.
|
||||
$self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0);
|
||||
eval { $self->{preset_bundle}->load_presets($self->{app_config}); };
|
||||
if ($@) {
|
||||
warn $@ . "\n";
|
||||
show_error(undef, $@);
|
||||
}
|
||||
$Settings->{_}{version} = $Slic3r::VERSION;
|
||||
$self->save_settings;
|
||||
|
||||
|
||||
# application frame
|
||||
Wx::Image::AddHandler(Wx::PNGHandler->new);
|
||||
print STDERR "Creating main frame...\n";
|
||||
Wx::Image::FindHandlerType(wxBITMAP_TYPE_PNG) || Wx::Image::AddHandler(Wx::PNGHandler->new);
|
||||
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
|
||||
mode => $mode // $Settings->{_}{mode},
|
||||
no_plater => $no_plater,
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
no_controller => $self->{app_config}->get('no_controller'),
|
||||
no_plater => $no_plater,
|
||||
lang_ch_event => $LANGUAGE_CHANGE_EVENT,
|
||||
preferences_event => $PREFERENCES_EVENT,
|
||||
);
|
||||
$self->SetTopWindow($frame);
|
||||
|
||||
if (!$run_wizard && (!defined $last_version || $last_version ne $Slic3r::VERSION)) {
|
||||
# user was running another Slic3r version on this computer
|
||||
if (!defined $last_version || $last_version =~ /^0\./) {
|
||||
show_info($self->{mainframe}, "Hello! Support material was improved since the "
|
||||
. "last version of Slic3r you used. It is strongly recommended to revert "
|
||||
. "your support material settings to the factory defaults and start from "
|
||||
. "those. Enjoy and provide feedback!", "Support Material");
|
||||
}
|
||||
if (!defined $last_version || $last_version =~ /^(?:0|1\.[01])\./) {
|
||||
show_info($self->{mainframe}, "Hello! In this version a new Bed Shape option was "
|
||||
. "added. If the bed coordinates in the plater preview screen look wrong, go "
|
||||
. "to Print Settings and click the \"Set\" button next to \"Bed Shape\".", "Bed Shape");
|
||||
}
|
||||
}
|
||||
$self->{mainframe}->config_wizard if $run_wizard;
|
||||
|
||||
$self->check_version
|
||||
if $self->have_version_check
|
||||
&& ($Settings->{_}{version_check} // 1)
|
||||
&& (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400);
|
||||
|
||||
EVT_IDLE($frame, sub {
|
||||
|
||||
# This makes CallAfter() work
|
||||
EVT_IDLE($self->{mainframe}, sub {
|
||||
while (my $cb = shift @cb) {
|
||||
$cb->();
|
||||
}
|
||||
$self->{app_config}->save if $self->{app_config}->dirty;
|
||||
});
|
||||
|
||||
# On OS X the UI tends to freeze in weird ways if modal dialogs (config wizard, update notifications, ...)
|
||||
# are shown before or in the same event callback with the main frame creation.
|
||||
# Therefore we schedule them for later using CallAfter.
|
||||
$self->CallAfter(sub {
|
||||
eval {
|
||||
if (! $self->{preset_updater}->config_update()) {
|
||||
exit 0;
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
warn $@ . "\n";
|
||||
fatal_error(undef, $@);
|
||||
}
|
||||
});
|
||||
|
||||
$self->CallAfter(sub {
|
||||
if (! Slic3r::GUI::config_wizard_startup($app_conf_exists)) {
|
||||
# Only notify if there was not wizard so as not to bother too much ...
|
||||
$self->{preset_updater}->slic3r_update_notify();
|
||||
}
|
||||
$self->{preset_updater}->sync($self->{preset_bundle});
|
||||
});
|
||||
|
||||
# The following event is emited by the C++ menu implementation of application language change.
|
||||
EVT_COMMAND($self, -1, $LANGUAGE_CHANGE_EVENT, sub{
|
||||
print STDERR "LANGUAGE_CHANGE_EVENT\n";
|
||||
$self->recreate_GUI;
|
||||
});
|
||||
|
||||
# The following event is emited by the C++ menu implementation of preferences change.
|
||||
EVT_COMMAND($self, -1, $PREFERENCES_EVENT, sub{
|
||||
$self->update_ui_from_settings;
|
||||
});
|
||||
|
||||
# The following event is emited by PresetUpdater (C++)
|
||||
EVT_COMMAND($self, -1, $VERSION_ONLINE_EVENT, sub {
|
||||
my ($self, $event) = @_;
|
||||
my $version = $event->GetString;
|
||||
$self->{app_config}->set('version_online', $version);
|
||||
$self->{app_config}->save;
|
||||
});
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub about {
|
||||
sub recreate_GUI{
|
||||
print STDERR "recreate_GUI\n";
|
||||
my ($self) = @_;
|
||||
|
||||
my $about = Slic3r::GUI::AboutDialog->new(undef);
|
||||
my $topwindow = $self->GetTopWindow();
|
||||
$self->{mainframe} = my $frame = Slic3r::GUI::MainFrame->new(
|
||||
# If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
|
||||
no_controller => $self->{app_config}->get('no_controller'),
|
||||
no_plater => $no_plater,
|
||||
lang_ch_event => $LANGUAGE_CHANGE_EVENT,
|
||||
preferences_event => $PREFERENCES_EVENT,
|
||||
);
|
||||
|
||||
if($topwindow)
|
||||
{
|
||||
$self->SetTopWindow($frame);
|
||||
$topwindow->Destroy;
|
||||
}
|
||||
|
||||
EVT_IDLE($self->{mainframe}, sub {
|
||||
while (my $cb = shift @cb) {
|
||||
$cb->();
|
||||
}
|
||||
$self->{app_config}->save if $self->{app_config}->dirty;
|
||||
});
|
||||
|
||||
# On OSX the UI was not initialized correctly if the wizard was called
|
||||
# before the UI was up and running.
|
||||
$self->CallAfter(sub {
|
||||
# Run the config wizard, don't offer the "reset user profile" checkbox.
|
||||
Slic3r::GUI::config_wizard_startup(1);
|
||||
});
|
||||
}
|
||||
|
||||
sub system_info {
|
||||
my ($self) = @_;
|
||||
my $slic3r_info = Slic3r::slic3r_info(format => 'html');
|
||||
my $copyright_info = Slic3r::copyright_info(format => 'html');
|
||||
my $system_info = Slic3r::system_info(format => 'html');
|
||||
my $opengl_info;
|
||||
my $opengl_info_txt = '';
|
||||
if (defined($self->{mainframe}) && defined($self->{mainframe}->{plater}) &&
|
||||
defined($self->{mainframe}->{plater}->{canvas3D})) {
|
||||
$opengl_info = Slic3r::GUI::_3DScene::get_gl_info(1, 1);
|
||||
$opengl_info_txt = Slic3r::GUI::_3DScene::get_gl_info(0, 1);
|
||||
}
|
||||
my $about = Slic3r::GUI::SystemInfo->new(
|
||||
parent => undef,
|
||||
slic3r_info => $slic3r_info,
|
||||
# copyright_info => $copyright_info,
|
||||
system_info => $system_info,
|
||||
opengl_info => $opengl_info,
|
||||
text_info => Slic3r::slic3r_info . Slic3r::system_info . $opengl_info_txt,
|
||||
);
|
||||
$about->ShowModal;
|
||||
$about->Destroy;
|
||||
}
|
||||
@@ -160,22 +252,19 @@ sub catch_error {
|
||||
|
||||
# static method accepting a wxWindow object as first parameter
|
||||
sub show_error {
|
||||
my $self = shift;
|
||||
my ($message) = @_;
|
||||
Wx::MessageDialog->new($self, $message, 'Error', wxOK | wxICON_ERROR)->ShowModal;
|
||||
my ($parent, $message) = @_;
|
||||
Slic3r::GUI::show_error_id($parent ? $parent->GetId() : 0, $message);
|
||||
}
|
||||
|
||||
# static method accepting a wxWindow object as first parameter
|
||||
sub show_info {
|
||||
my $self = shift;
|
||||
my ($message, $title) = @_;
|
||||
Wx::MessageDialog->new($self, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
|
||||
my ($parent, $message, $title) = @_;
|
||||
Wx::MessageDialog->new($parent, $message, $title || 'Notice', wxOK | wxICON_INFORMATION)->ShowModal;
|
||||
}
|
||||
|
||||
# static method accepting a wxWindow object as first parameter
|
||||
sub fatal_error {
|
||||
my $self = shift;
|
||||
$self->show_error(@_);
|
||||
show_error(@_);
|
||||
exit 1;
|
||||
}
|
||||
|
||||
@@ -200,79 +289,24 @@ sub notify {
|
||||
$frame->RequestUserAttention(&Wx::wxMAC ? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO)
|
||||
unless ($frame->IsActive);
|
||||
|
||||
$self->{notifier}->notify($message);
|
||||
# There used to be notifier using a Growl application for OSX, but Growl is dead.
|
||||
# The notifier also supported the Linux X D-bus notifications, but that support was broken.
|
||||
#TODO use wxNotificationMessage?
|
||||
}
|
||||
|
||||
sub save_settings {
|
||||
# Called after the Preferences dialog is closed and the program settings are saved.
|
||||
# Update the UI based on the current preferences.
|
||||
sub update_ui_from_settings {
|
||||
my ($self) = @_;
|
||||
Slic3r::Config->write_ini("$datadir/slic3r.ini", $Settings);
|
||||
}
|
||||
|
||||
sub presets {
|
||||
my ($self, $section) = @_;
|
||||
|
||||
my %presets = ();
|
||||
opendir my $dh, "$Slic3r::GUI::datadir/$section" or die "Failed to read directory $Slic3r::GUI::datadir/$section (errno: $!)\n";
|
||||
foreach my $file (grep /\.ini$/i, readdir $dh) {
|
||||
my $name = basename($file);
|
||||
$name =~ s/\.ini$//;
|
||||
$presets{$name} = "$Slic3r::GUI::datadir/$section/$file";
|
||||
}
|
||||
closedir $dh;
|
||||
|
||||
return %presets;
|
||||
}
|
||||
|
||||
sub have_version_check {
|
||||
my ($self) = @_;
|
||||
|
||||
# return an explicit 0
|
||||
return ($Slic3r::have_threads && $Slic3r::build && eval "use LWP::UserAgent; 1") || 0;
|
||||
}
|
||||
|
||||
sub check_version {
|
||||
my ($self, %p) = @_;
|
||||
|
||||
Slic3r::debugf "Checking for updates...\n";
|
||||
|
||||
@_ = ();
|
||||
threads->create(sub {
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->timeout(10);
|
||||
my $response = $ua->get('http://slic3r.org/updatecheck');
|
||||
if ($response->is_success) {
|
||||
if ($response->decoded_content =~ /^obsolete ?= ?([a-z0-9.-]+,)*\Q$Slic3r::VERSION\E(?:,|$)/) {
|
||||
my $res = Wx::MessageDialog->new(undef, "A new version is available. Do you want to open the Slic3r website now?",
|
||||
'Update', wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_INFORMATION | wxICON_ERROR)->ShowModal;
|
||||
Wx::LaunchDefaultBrowser('http://slic3r.org/') if $res == wxID_YES;
|
||||
} else {
|
||||
Slic3r::GUI::show_info(undef, "You're using the latest version. No updates are available.") if $p{manual};
|
||||
}
|
||||
$Settings->{_}{last_version_check} = time();
|
||||
$self->save_settings;
|
||||
} else {
|
||||
Slic3r::GUI::show_error(undef, "Failed to check for updates. Try later.") if $p{manual};
|
||||
}
|
||||
Slic3r::thread_cleanup();
|
||||
})->detach;
|
||||
}
|
||||
|
||||
sub output_path {
|
||||
my ($self, $dir) = @_;
|
||||
|
||||
return ($Settings->{_}{last_output_path} && $Settings->{_}{remember_output_path})
|
||||
? $Settings->{_}{last_output_path}
|
||||
: $dir;
|
||||
$self->{mainframe}->update_ui_from_settings;
|
||||
}
|
||||
|
||||
sub open_model {
|
||||
my ($self, $window) = @_;
|
||||
|
||||
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory}
|
||||
|| $Slic3r::GUI::Settings->{recent}{config_directory}
|
||||
|| '';
|
||||
|
||||
my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, 'Choose one or more files (STL/OBJ/AMF):', $dir, "",
|
||||
|
||||
my $dlg_title = L('Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):');
|
||||
my $dialog = Wx::FileDialog->new($window // $self->GetTopWindow, $dlg_title,
|
||||
$self->{app_config}->get_last_dir, "",
|
||||
MODEL_WILDCARD, wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
if ($dialog->ShowModal != wxID_OK) {
|
||||
$dialog->Destroy;
|
||||
@@ -280,7 +314,6 @@ sub open_model {
|
||||
}
|
||||
my @input_files = $dialog->GetPaths;
|
||||
$dialog->Destroy;
|
||||
|
||||
return @input_files;
|
||||
}
|
||||
|
||||
@@ -289,4 +322,61 @@ sub CallAfter {
|
||||
push @cb, $cb;
|
||||
}
|
||||
|
||||
sub append_menu_item {
|
||||
my ($self, $menu, $string, $description, $cb, $id, $icon, $kind) = @_;
|
||||
|
||||
$id //= &Wx::NewId();
|
||||
my $item = Wx::MenuItem->new($menu, $id, $string, $description // '', $kind // 0);
|
||||
$self->set_menu_item_icon($item, $icon);
|
||||
$menu->Append($item);
|
||||
|
||||
EVT_MENU($self, $id, $cb);
|
||||
return $item;
|
||||
}
|
||||
|
||||
sub append_submenu {
|
||||
my ($self, $menu, $string, $description, $submenu, $id, $icon) = @_;
|
||||
|
||||
$id //= &Wx::NewId();
|
||||
my $item = Wx::MenuItem->new($menu, $id, $string, $description // '');
|
||||
$self->set_menu_item_icon($item, $icon);
|
||||
$item->SetSubMenu($submenu);
|
||||
$menu->Append($item);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
sub set_menu_item_icon {
|
||||
my ($self, $menuItem, $icon) = @_;
|
||||
|
||||
# SetBitmap was not available on OS X before Wx 0.9927
|
||||
if ($icon && $menuItem->can('SetBitmap')) {
|
||||
$menuItem->SetBitmap(Wx::Bitmap->new(Slic3r::var($icon), wxBITMAP_TYPE_PNG));
|
||||
}
|
||||
}
|
||||
|
||||
sub save_window_pos {
|
||||
my ($self, $window, $name) = @_;
|
||||
|
||||
$self->{app_config}->set("${name}_pos", join ',', $window->GetScreenPositionXY);
|
||||
$self->{app_config}->set("${name}_size", join ',', $window->GetSizeWH);
|
||||
$self->{app_config}->set("${name}_maximized", $window->IsMaximized);
|
||||
$self->{app_config}->save;
|
||||
}
|
||||
|
||||
sub restore_window_pos {
|
||||
my ($self, $window, $name) = @_;
|
||||
if ($self->{app_config}->has("${name}_pos")) {
|
||||
my $size = [ split ',', $self->{app_config}->get("${name}_size"), 2 ];
|
||||
$window->SetSize($size);
|
||||
|
||||
my $display = Wx::Display->new->GetClientArea();
|
||||
my $pos = [ split ',', $self->{app_config}->get("${name}_pos"), 2 ];
|
||||
if (($pos->[0] + $size->[0]/2) < $display->GetRight && ($pos->[1] + $size->[1]/2) < $display->GetBottom) {
|
||||
$window->Move($pos);
|
||||
}
|
||||
$window->Maximize(1) if $self->{app_config}->get("${name}_maximized");
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
216
lib/Slic3r/GUI/2DBed.pm
Normal file
216
lib/Slic3r/GUI/2DBed.pm
Normal file
@@ -0,0 +1,216 @@
|
||||
# Bed shape dialog
|
||||
|
||||
package Slic3r::GUI::2DBed;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(min max);
|
||||
use Slic3r::Geometry qw(X Y unscale deg2rad);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
use Wx qw(:misc :pen :brush :font :systemsettings wxTAB_TRAVERSAL wxSOLID);
|
||||
use Wx::Event qw(EVT_PAINT EVT_ERASE_BACKGROUND EVT_MOUSE_EVENTS EVT_SIZE);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(bed_shape interactive pos _scale_factor _shift on_move _painted));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $bed_shape) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [250,-1], wxTAB_TRAVERSAL);
|
||||
$self->{user_drawn_background} = $^O ne 'darwin';
|
||||
$self->bed_shape($bed_shape // []);
|
||||
EVT_PAINT($self, \&_repaint);
|
||||
EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
|
||||
EVT_MOUSE_EVENTS($self, \&_mouse_event);
|
||||
EVT_SIZE($self, sub { $self->Refresh; });
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::AutoBufferedPaintDC->new($self);
|
||||
my ($cw, $ch) = $self->GetSizeWH;
|
||||
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
|
||||
|
||||
if ($self->{user_drawn_background}) {
|
||||
# On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
# On MacOS the background is erased, on Windows the background is not erased
|
||||
# and on Linux/GTK the background is erased to gray color.
|
||||
# Fill DC with the background on Windows & Linux/GTK.
|
||||
my $color = Wx::SystemSettings::GetSystemColour(wxSYS_COLOUR_3DLIGHT);
|
||||
$dc->SetPen(Wx::Pen->new($color, 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new($color, wxSOLID));
|
||||
my $rect = $self->GetUpdateRegion()->GetBox();
|
||||
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
|
||||
}
|
||||
|
||||
# turn $cw and $ch from sizes to max coordinates
|
||||
$cw--;
|
||||
$ch--;
|
||||
|
||||
my $cbb = Slic3r::Geometry::BoundingBoxf->new_from_points([
|
||||
Slic3r::Pointf->new(0, 0),
|
||||
Slic3r::Pointf->new($cw, $ch),
|
||||
]);
|
||||
|
||||
# leave space for origin point
|
||||
$cbb->set_x_min($cbb->x_min + 4);
|
||||
$cbb->set_x_max($cbb->x_max - 4);
|
||||
$cbb->set_y_max($cbb->y_max - 4);
|
||||
|
||||
# leave space for origin label
|
||||
$cbb->set_y_max($cbb->y_max - 13);
|
||||
|
||||
# read new size
|
||||
($cw, $ch) = @{$cbb->size};
|
||||
my $ccenter = $cbb->center;
|
||||
|
||||
# get bounding box of bed shape in G-code coordinates
|
||||
my $bed_shape = $self->bed_shape;
|
||||
my $bed_polygon = Slic3r::Polygon->new_scale(@$bed_shape);
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape);
|
||||
$bb->merge_point(Slic3r::Pointf->new(0,0)); # origin needs to be in the visible area
|
||||
my ($bw, $bh) = @{$bb->size};
|
||||
my $bcenter = $bb->center;
|
||||
|
||||
# calculate the scaling factor for fitting bed shape in canvas area
|
||||
my $sfactor = min($cw/$bw, $ch/$bh);
|
||||
my $shift = Slic3r::Pointf->new(
|
||||
$ccenter->x - $bcenter->x * $sfactor,
|
||||
$ccenter->y - $bcenter->y * $sfactor, #-
|
||||
);
|
||||
$self->_scale_factor($sfactor);
|
||||
$self->_shift(Slic3r::Pointf->new(
|
||||
$shift->x + $cbb->x_min,
|
||||
$shift->y - ($cbb->y_max-$self->GetSize->GetHeight), #++
|
||||
));
|
||||
|
||||
# draw bed fill
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxSOLID));
|
||||
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
# draw grid
|
||||
{
|
||||
my $step = 10; # 1cm grid
|
||||
my @polylines = ();
|
||||
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$x, $bb->y_min], [$x, $bb->y_max]);
|
||||
}
|
||||
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
|
||||
$dc->DrawLine(map @{$self->to_pixels([map unscale($_), @$_])}, @$_[0,-1]) for @polylines;
|
||||
}
|
||||
|
||||
# draw bed contour
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxTRANSPARENT));
|
||||
$dc->DrawPolygon([ map $self->to_pixels($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
my $origin_px = $self->to_pixels(Slic3r::Pointf->new(0,0));
|
||||
|
||||
# draw axes
|
||||
{
|
||||
my $axes_len = 50;
|
||||
my $arrow_len = 6;
|
||||
my $arrow_angle = deg2rad(45);
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(255,0,0), 2, wxSOLID)); # red
|
||||
my $x_end = Slic3r::Pointf->new($origin_px->[X] + $axes_len, $origin_px->[Y]);
|
||||
$dc->DrawLine(@$origin_px, @$x_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $x_end->clone;
|
||||
$end->translate(-$arrow_len, 0);
|
||||
$end->rotate($angle, $x_end);
|
||||
$dc->DrawLine(@$x_end, @$end);
|
||||
}
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,255,0), 2, wxSOLID)); # green
|
||||
my $y_end = Slic3r::Pointf->new($origin_px->[X], $origin_px->[Y] - $axes_len);
|
||||
$dc->DrawLine(@$origin_px, @$y_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $y_end->clone;
|
||||
$end->translate(0, +$arrow_len);
|
||||
$end->rotate($angle, $y_end);
|
||||
$dc->DrawLine(@$y_end, @$end);
|
||||
}
|
||||
}
|
||||
|
||||
# draw origin
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(0,0,0), wxSOLID));
|
||||
$dc->DrawCircle(@$origin_px, 3);
|
||||
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawText("(0,0)", $origin_px->[X] + 1, $origin_px->[Y] + 2);
|
||||
}
|
||||
|
||||
# draw current position
|
||||
if (defined $self->pos) {
|
||||
my $pos_px = $self->to_pixels($self->pos);
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(200,0,0), 2, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(200,0,0), wxTRANSPARENT));
|
||||
$dc->DrawCircle(@$pos_px, 5);
|
||||
|
||||
$dc->DrawLine($pos_px->[X]-15, $pos_px->[Y], $pos_px->[X]+15, $pos_px->[Y]);
|
||||
$dc->DrawLine($pos_px->[X], $pos_px->[Y]-15, $pos_px->[X], $pos_px->[Y]+15);
|
||||
}
|
||||
|
||||
$self->_painted(1);
|
||||
}
|
||||
|
||||
sub _mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
return if !$self->interactive;
|
||||
return if !$self->_painted;
|
||||
|
||||
my $pos = $event->GetPosition;
|
||||
my $point = $self->to_units([ $pos->x, $pos->y ]); #]]
|
||||
if ($event->LeftDown || $event->Dragging) {
|
||||
$self->on_move->($point) if $self->on_move;
|
||||
$self->Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
# convert G-code coordinates into pixels
|
||||
sub to_pixels {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $p = Slic3r::Pointf->new(@$point);
|
||||
$p->scale($self->_scale_factor);
|
||||
$p->translate(@{$self->_shift});
|
||||
return [$p->x, $self->GetSize->GetHeight - $p->y]; #]]
|
||||
}
|
||||
|
||||
# convert pixels into G-code coordinates
|
||||
sub to_units {
|
||||
my ($self, $point) = @_;
|
||||
|
||||
my $p = Slic3r::Pointf->new(
|
||||
$point->[X],
|
||||
$self->GetSize->GetHeight - $point->[Y],
|
||||
);
|
||||
$p->translate(@{$self->_shift->negative});
|
||||
$p->scale(1/$self->_scale_factor);
|
||||
return $p;
|
||||
}
|
||||
|
||||
sub set_pos {
|
||||
my ($self, $pos) = @_;
|
||||
|
||||
$self->pos($pos);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
1;
|
||||
2231
lib/Slic3r/GUI/3DScene.pm
Normal file
2231
lib/Slic3r/GUI/3DScene.pm
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,110 +0,0 @@
|
||||
package Slic3r::GUI::AboutDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:font :html :misc :sizer :systemsettings);
|
||||
use Wx::Event qw(EVT_HTML_LINK_CLICKED);
|
||||
use Wx::Print;
|
||||
use Wx::Html;
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, 'About Slic3r', wxDefaultPosition, [600, 270]);
|
||||
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$self->SetSizer($hsizer);
|
||||
|
||||
# logo
|
||||
my $logo = Slic3r::GUI::AboutDialog::Logo->new($self, -1, wxDefaultPosition, wxDefaultSize);
|
||||
$logo->SetBackgroundColour(Wx::wxWHITE);
|
||||
$hsizer->Add($logo, 0, wxEXPAND | wxLEFT | wxRIGHT, 30);
|
||||
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$hsizer->Add($vsizer, 1, wxEXPAND, 0);
|
||||
|
||||
# title
|
||||
my $title = Wx::StaticText->new($self, -1, 'Slic3r', wxDefaultPosition, wxDefaultSize);
|
||||
my $title_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$title_font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
$title_font->SetFamily(wxFONTFAMILY_ROMAN);
|
||||
$title_font->SetPointSize(24);
|
||||
$title->SetFont($title_font);
|
||||
$vsizer->Add($title, 0, wxALIGN_LEFT | wxTOP, 30);
|
||||
|
||||
# version
|
||||
my $version = Wx::StaticText->new($self, -1, "Version $Slic3r::VERSION", wxDefaultPosition, wxDefaultSize);
|
||||
my $version_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$version_font->SetPointSize(&Wx::wxMSW ? 9 : 11);
|
||||
$version->SetFont($version_font);
|
||||
$vsizer->Add($version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
|
||||
|
||||
# text
|
||||
my $text =
|
||||
'<html>' .
|
||||
'<body bgcolor="#ffffff" link="#808080">' .
|
||||
'<font color="#808080">' .
|
||||
'Copyright © 2011-2014 Alessandro Ranellucci. <br />' .
|
||||
'<a href="http://slic3r.org/">Slic3r</a> is licensed under the ' .
|
||||
'<a href="http://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License, version 3</a>.' .
|
||||
'<br /><br /><br />' .
|
||||
'Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Y. Sapir, Mike Sheldrake and numerous others. ' .
|
||||
'Manual by Gary Hodgson. Inspired by the RepRap community. <br />' .
|
||||
'Slic3r logo designed by Corey Daniels, <a href="http://www.famfamfam.com/lab/icons/silk/">Silk Icon Set</a> designed by Mark James. ' .
|
||||
'</font>' .
|
||||
'</body>' .
|
||||
'</html>';
|
||||
my $html = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
|
||||
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
my $size = &Wx::wxMSW ? 8 : 10;
|
||||
$html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size, $size, $size, $size, $size, $size, $size]);
|
||||
$html->SetBorders(2);
|
||||
$html->SetPage($text);
|
||||
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 20);
|
||||
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub link_clicked {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref);
|
||||
$event->Skip(0);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::AboutDialog::Logo;
|
||||
use Wx qw(:bitmap :dc);
|
||||
use Wx::Event qw(EVT_PAINT);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
|
||||
$self->{logo} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px.png", wxBITMAP_TYPE_PNG);
|
||||
$self->SetMinSize(Wx::Size->new($self->{logo}->GetWidth, $self->{logo}->GetHeight));
|
||||
|
||||
EVT_PAINT($self, \&repaint);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
$dc->SetBackgroundMode(wxTRANSPARENT);
|
||||
|
||||
my $size = $self->GetSize;
|
||||
my $logo_w = $self->{logo}->GetWidth;
|
||||
my $logo_h = $self->{logo}->GetHeight;
|
||||
$dc->DrawBitmap($self->{logo}, ($size->GetWidth - $logo_w) / 2, ($size->GetHeight - $logo_h) / 2, 1);
|
||||
|
||||
$event->Skip;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,412 +0,0 @@
|
||||
package Slic3r::GUI::BedShapeDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw(min max);
|
||||
use Slic3r::Geometry qw(PI X Y unscale);
|
||||
use Wx qw(:dialog :id :misc :sizer :choicebook wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $default) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Bed Shape", wxDefaultPosition, [350,700], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
|
||||
$self->{panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $default);
|
||||
|
||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$main_sizer->Add($panel, 1, wxEXPAND);
|
||||
$main_sizer->Add($self->CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND);
|
||||
|
||||
$self->SetSizer($main_sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$main_sizer->SetSizeHints($self);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub GetValue {
|
||||
my ($self) = @_;
|
||||
return $self->{panel}->GetValue;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::BedShapePanel;
|
||||
|
||||
use List::Util qw(min max sum first);
|
||||
use Slic3r::Geometry qw(PI X Y scale unscale scaled_epsilon deg2rad);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
use Wx qw(:font :id :misc :sizer :choicebook :filedialog :pen :brush wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_CHOICEBOOK_PAGE_CHANGED EVT_BUTTON EVT_PAINT);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant SHAPE_RECTANGULAR => 0;
|
||||
use constant SHAPE_CIRCULAR => 1;
|
||||
use constant SHAPE_CUSTOM => 2;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $default) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1);
|
||||
|
||||
$self->on_change(undef);
|
||||
|
||||
my $box = Wx::StaticBox->new($self, -1, "Shape");
|
||||
my $sbsizer = Wx::StaticBoxSizer->new($box, wxVERTICAL);
|
||||
|
||||
# shape options
|
||||
$self->{shape_options_book} = Wx::Choicebook->new($self, -1, wxDefaultPosition, [300,-1], wxCHB_TOP);
|
||||
$sbsizer->Add($self->{shape_options_book});
|
||||
|
||||
$self->{optgroups} = [];
|
||||
{
|
||||
my $optgroup = $self->_init_shape_options_page('Rectangular');
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'rect_size',
|
||||
type => 'point',
|
||||
label => 'Size',
|
||||
tooltip => 'Size in X and Y of the rectangular plate.',
|
||||
default => [200,200],
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'rect_origin',
|
||||
type => 'point',
|
||||
label => 'Origin',
|
||||
tooltip => 'Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.',
|
||||
default => [0,0],
|
||||
));
|
||||
$optgroup->on_change->($_) for qw(rect_size rect_origin); # set defaults
|
||||
}
|
||||
{
|
||||
my $optgroup = $self->_init_shape_options_page('Circular');
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'diameter',
|
||||
type => 'f',
|
||||
label => 'Diameter',
|
||||
tooltip => 'Diameter of the print bed. It is assumed that origin (0,0) is located in the center.',
|
||||
sidetext => 'mm',
|
||||
default => 200,
|
||||
));
|
||||
$optgroup->on_change->($_) for qw(diameter); # set defaults
|
||||
}
|
||||
{
|
||||
my $optgroup = $self->_init_shape_options_page('Custom');
|
||||
$optgroup->append_line(Slic3r::GUI::OptionsGroup::Line->new(
|
||||
full_width => 1,
|
||||
widget => sub {
|
||||
my ($parent) = @_;
|
||||
|
||||
my $btn = Wx::Button->new($parent, -1, "Load shape from STL...", wxDefaultPosition, wxDefaultSize);
|
||||
EVT_BUTTON($self, $btn, sub { $self->_load_stl });
|
||||
return $btn;
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
EVT_CHOICEBOOK_PAGE_CHANGED($self, -1, sub {
|
||||
$self->_update_shape;
|
||||
});
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas = $self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, [250,-1], wxTAB_TRAVERSAL);
|
||||
EVT_PAINT($canvas, sub {
|
||||
$self->_repaint_canvas;
|
||||
});
|
||||
|
||||
# main sizer
|
||||
my $top_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$top_sizer->Add($sbsizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
|
||||
$top_sizer->Add($canvas, 1, wxEXPAND | wxALL, 10) if $canvas;
|
||||
|
||||
$self->SetSizerAndFit($top_sizer);
|
||||
|
||||
$self->_set_shape($default);
|
||||
$self->_update_preview;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub on_change {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_change} = $cb // sub {};
|
||||
}
|
||||
|
||||
sub _set_shape {
|
||||
my ($self, $points) = @_;
|
||||
|
||||
$self->{bed_shape} = $points;
|
||||
|
||||
# is this a rectangle?
|
||||
if (@$points == 4) {
|
||||
my $polygon = Slic3r::Polygon->new_scale(@$points);
|
||||
my $lines = $polygon->lines;
|
||||
if ($lines->[0]->parallel_to_line($lines->[2]) && $lines->[1]->parallel_to_line($lines->[3])) {
|
||||
# okay, it's a rectangle
|
||||
|
||||
# find origin
|
||||
# the || 0 hack prevents "-0" which might confuse the user
|
||||
my $x_min = min(map $_->[X], @$points) || 0;
|
||||
my $x_max = max(map $_->[X], @$points) || 0;
|
||||
my $y_min = min(map $_->[Y], @$points) || 0;
|
||||
my $y_max = max(map $_->[Y], @$points) || 0;
|
||||
my $origin = [-$x_min, -$y_min];
|
||||
|
||||
$self->{shape_options_book}->SetSelection(SHAPE_RECTANGULAR);
|
||||
my $optgroup = $self->{optgroups}[SHAPE_RECTANGULAR];
|
||||
$optgroup->set_value('rect_size', [ $x_max-$x_min, $y_max-$y_min ]);
|
||||
$optgroup->set_value('rect_origin', $origin);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
# is this a circle?
|
||||
{
|
||||
my $polygon = Slic3r::Polygon->new_scale(@$points);
|
||||
my $center = $polygon->bounding_box->center;
|
||||
my @vertex_distances = map $center->distance_to($_), @$polygon;
|
||||
my $avg_dist = sum(@vertex_distances)/@vertex_distances;
|
||||
if (!defined first { abs($_ - $avg_dist) > scaled_epsilon } @vertex_distances) {
|
||||
# all vertices are equidistant to center
|
||||
$self->{shape_options_book}->SetSelection(SHAPE_CIRCULAR);
|
||||
my $optgroup = $self->{optgroups}[SHAPE_CIRCULAR];
|
||||
$optgroup->set_value('diameter', sprintf("%.0f", unscale($avg_dist*2)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$self->{shape_options_book}->SetSelection(SHAPE_CUSTOM);
|
||||
}
|
||||
|
||||
sub _update_shape {
|
||||
my ($self) = @_;
|
||||
|
||||
my $page_idx = $self->{shape_options_book}->GetSelection;
|
||||
if ($page_idx == SHAPE_RECTANGULAR) {
|
||||
return if grep !defined($self->{"_$_"}), qw(rect_size rect_origin); # not loaded yet
|
||||
my ($x, $y) = @{$self->{_rect_size}};
|
||||
return if !$x || !$y; # empty strings
|
||||
my ($x0, $y0) = (0,0);
|
||||
my ($x1, $y1) = ($x,$y);
|
||||
{
|
||||
my ($dx, $dy) = @{$self->{_rect_origin}};
|
||||
return if $dx eq '' || $dy eq ''; # empty strings
|
||||
$x0 -= $dx;
|
||||
$x1 -= $dx;
|
||||
$y0 -= $dy;
|
||||
$y1 -= $dy;
|
||||
}
|
||||
$self->{bed_shape} = [
|
||||
[$x0,$y0],
|
||||
[$x1,$y0],
|
||||
[$x1,$y1],
|
||||
[$x0,$y1],
|
||||
];
|
||||
} elsif ($page_idx == SHAPE_CIRCULAR) {
|
||||
return if grep !defined($self->{"_$_"}), qw(diameter); # not loaded yet
|
||||
return if !$self->{_diameter};
|
||||
my $r = $self->{_diameter}/2;
|
||||
my $twopi = 2*PI;
|
||||
my $edges = 60;
|
||||
my $polygon = Slic3r::Polygon->new_scale(
|
||||
map [ $r * cos $_, $r * sin $_ ],
|
||||
map { $twopi/$edges*$_ } 1..$edges
|
||||
);
|
||||
$self->{bed_shape} = [
|
||||
map [ unscale($_->x), unscale($_->y) ], @$polygon #))
|
||||
];
|
||||
}
|
||||
|
||||
$self->{on_change}->();
|
||||
$self->_update_preview;
|
||||
}
|
||||
|
||||
sub _update_preview {
|
||||
my ($self) = @_;
|
||||
$self->{canvas}->Refresh if $self->{canvas};
|
||||
}
|
||||
|
||||
sub _repaint_canvas {
|
||||
my ($self) = @_;
|
||||
|
||||
my $canvas = $self->{canvas};
|
||||
my $dc = Wx::PaintDC->new($canvas);
|
||||
my ($cw, $ch) = $canvas->GetSizeWH;
|
||||
return if $cw == 0; # when canvas is not rendered yet, size is 0,0
|
||||
|
||||
# turn $cw and $ch from sizes to max coordinates
|
||||
$cw--;
|
||||
$ch--;
|
||||
|
||||
my $cbb = Slic3r::Geometry::BoundingBoxf->new_from_points([
|
||||
Slic3r::Pointf->new(0, 0),
|
||||
Slic3r::Pointf->new($cw, $ch),
|
||||
]);
|
||||
|
||||
# leave space for origin point
|
||||
$cbb->set_x_min($cbb->x_min + 2);
|
||||
$cbb->set_y_max($cbb->y_max - 2);
|
||||
|
||||
# leave space for origin label
|
||||
$cbb->set_y_max($cbb->y_max - 10);
|
||||
|
||||
# read new size
|
||||
($cw, $ch) = @{$cbb->size};
|
||||
my $ccenter = $cbb->center;
|
||||
|
||||
# get bounding box of bed shape in G-code coordinates
|
||||
my $bed_shape = $self->{bed_shape};
|
||||
my $bed_polygon = Slic3r::Polygon->new_scale(@$bed_shape);
|
||||
my $bb = Slic3r::Geometry::BoundingBoxf->new_from_points($bed_shape);
|
||||
$bb->merge_point(Slic3r::Pointf->new(0,0)); # origin needs to be in the visible area
|
||||
my ($bw, $bh) = @{$bb->size};
|
||||
my $bcenter = $bb->center;
|
||||
|
||||
# calculate the scaling factor for fitting bed shape in canvas area
|
||||
my $sfactor = min($cw/$bw, $ch/$bh);
|
||||
my $shift = [
|
||||
$ccenter->x - $bcenter->x * $sfactor,
|
||||
$ccenter->y - $bcenter->y * $sfactor, #-
|
||||
];
|
||||
|
||||
# prepare function to convert G-code coordinates into pixels
|
||||
my $to_pixel = sub {
|
||||
my ($point) = @_;
|
||||
|
||||
my $p = Slic3r::Pointf->new(@$point);
|
||||
$p->scale($sfactor);
|
||||
$p->translate(@$shift);
|
||||
return [ $p->x + $cbb->x_min, $ch-$p->y + $cbb->y_min ]; #++
|
||||
};
|
||||
|
||||
# draw bed fill
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxSOLID));
|
||||
$dc->DrawPolygon([ map $to_pixel->($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
# draw grid
|
||||
{
|
||||
my $step = 10; # 1cm grid
|
||||
my @polylines = ();
|
||||
for (my $x = $bb->x_min - ($bb->x_min % $step) + $step; $x < $bb->x_max; $x += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$x, $bb->y_min], [$x, $bb->y_max]);
|
||||
}
|
||||
for (my $y = $bb->y_min - ($bb->y_min % $step) + $step; $y < $bb->y_max; $y += $step) {
|
||||
push @polylines, Slic3r::Polyline->new_scale([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$bed_polygon])};
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(230,230,230), 1, wxSOLID));
|
||||
$dc->DrawLine(map @{$to_pixel->([map unscale($_), @$_])}, @$_) for @polylines;
|
||||
}
|
||||
|
||||
# draw bed contour
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(255,255,255), wxTRANSPARENT));
|
||||
$dc->DrawPolygon([ map $to_pixel->($_), @$bed_shape ], 0, 0);
|
||||
}
|
||||
|
||||
my $origin_px = $to_pixel->(Slic3r::Pointf->new(0,0));
|
||||
|
||||
# draw axes
|
||||
{
|
||||
my $axes_len = 50;
|
||||
my $arrow_len = 6;
|
||||
my $arrow_angle = deg2rad(45);
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(255,0,0), 2, wxSOLID)); # red
|
||||
my $x_end = Slic3r::Pointf->new($origin_px->[X] + $axes_len, $origin_px->[Y]);
|
||||
$dc->DrawLine(@$origin_px, @$x_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $x_end->clone;
|
||||
$end->translate(-$arrow_len, 0);
|
||||
$end->rotate($angle, $x_end);
|
||||
$dc->DrawLine(@$x_end, @$end);
|
||||
}
|
||||
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,255,0), 2, wxSOLID)); # green
|
||||
my $y_end = Slic3r::Pointf->new($origin_px->[X], $origin_px->[Y] - $axes_len);
|
||||
$dc->DrawLine(@$origin_px, @$y_end);
|
||||
foreach my $angle (-$arrow_angle, +$arrow_angle) {
|
||||
my $end = $y_end->clone;
|
||||
$end->translate(0, +$arrow_len);
|
||||
$end->rotate($angle, $y_end);
|
||||
$dc->DrawLine(@$y_end, @$end);
|
||||
}
|
||||
}
|
||||
|
||||
# draw origin
|
||||
{
|
||||
$dc->SetPen(Wx::Pen->new(Wx::Colour->new(0,0,0), 1, wxSOLID));
|
||||
$dc->SetBrush(Wx::Brush->new(Wx::Colour->new(0,0,0), wxSOLID));
|
||||
$dc->DrawCircle(@$origin_px, 3);
|
||||
|
||||
$dc->SetTextForeground(Wx::Colour->new(0,0,0));
|
||||
$dc->SetFont(Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawText("(0,0)", $origin_px->[X] + 1, $origin_px->[Y] + 2);
|
||||
}
|
||||
}
|
||||
|
||||
sub _init_shape_options_page {
|
||||
my ($self, $title) = @_;
|
||||
|
||||
my $panel = Wx::Panel->new($self->{shape_options_book});
|
||||
my $optgroup;
|
||||
push @{$self->{optgroups}}, $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $panel,
|
||||
title => 'Settings',
|
||||
label_width => 100,
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
$self->{"_$opt_id"} = $optgroup->get_value($opt_id);
|
||||
$self->_update_shape;
|
||||
},
|
||||
);
|
||||
$panel->SetSizerAndFit($optgroup->sizer);
|
||||
$self->{shape_options_book}->AddPage($panel, $title);
|
||||
|
||||
return $optgroup;
|
||||
}
|
||||
|
||||
sub _load_stl {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dialog = Wx::FileDialog->new($self, 'Choose a file to import bed shape from (STL/OBJ/AMF):', "", "", &Slic3r::GUI::MODEL_WILDCARD, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if ($dialog->ShowModal != wxID_OK) {
|
||||
$dialog->Destroy;
|
||||
return;
|
||||
}
|
||||
my $input_file = $dialog->GetPaths;
|
||||
$dialog->Destroy;
|
||||
|
||||
my $model = Slic3r::Model->read_from_file($input_file);
|
||||
my $mesh = $model->raw_mesh;
|
||||
my $expolygons = $mesh->horizontal_projection;
|
||||
|
||||
if (@$expolygons == 0) {
|
||||
Slic3r::GUI::show_error($self, "The selected file contains no geometry.");
|
||||
return;
|
||||
}
|
||||
if (@$expolygons > 1) {
|
||||
Slic3r::GUI::show_error($self, "The selected file contains several disjoint areas. This is not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
my $polygon = $expolygons->[0]->contour;
|
||||
$self->{bed_shape} = [ map [ unscale($_->x), unscale($_->y) ], @$polygon ]; #))
|
||||
}
|
||||
|
||||
sub GetValue {
|
||||
my ($self) = @_;
|
||||
return $self->{bed_shape};
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,385 +0,0 @@
|
||||
package Slic3r::GUI::ConfigWizard;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx;
|
||||
use base 'Wx::Wizard';
|
||||
use Slic3r::Geometry qw(unscale);
|
||||
|
||||
# adhere to various human interface guidelines
|
||||
our $wizard = 'Wizard';
|
||||
$wizard = 'Assistant' if &Wx::wxMAC || &Wx::wxGTK;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Configuration $wizard");
|
||||
|
||||
# initialize an empty repository
|
||||
$self->{config} = Slic3r::Config->new;
|
||||
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Welcome->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Firmware->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Bed->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Nozzle->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Filament->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Temperature->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::BedTemperature->new($self));
|
||||
$self->add_page(Slic3r::GUI::ConfigWizard::Page::Finished->new($self));
|
||||
|
||||
$_->build_index for @{$self->{pages}};
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub add_page {
|
||||
my $self = shift;
|
||||
my ($page) = @_;
|
||||
|
||||
my $n = push @{$self->{pages}}, $page;
|
||||
# add first page to the page area sizer
|
||||
$self->GetPageAreaSizer->Add($page) if $n == 1;
|
||||
# link pages
|
||||
$self->{pages}[$n-2]->set_next_page($page) if $n >= 2;
|
||||
$page->set_previous_page($self->{pages}[$n-2]) if $n >= 2;
|
||||
}
|
||||
|
||||
sub run {
|
||||
my $self = shift;
|
||||
|
||||
if (Wx::Wizard::RunWizard($self, $self->{pages}[0])) {
|
||||
|
||||
# it would be cleaner to have these defined inside each page class,
|
||||
# in some event getting called before leaving the page
|
||||
{
|
||||
# set first_layer_height + layer_height based on nozzle_diameter
|
||||
my $nozzle = $self->{config}->nozzle_diameter;
|
||||
$self->{config}->set('first_layer_height', $nozzle->[0]);
|
||||
$self->{config}->set('layer_height', $nozzle->[0] - 0.1);
|
||||
|
||||
# set first_layer_temperature to temperature + 5
|
||||
$self->{config}->set('first_layer_temperature', [$self->{config}->temperature->[0] + 5]);
|
||||
|
||||
# set first_layer_bed_temperature to temperature + 5
|
||||
$self->{config}->set('first_layer_bed_temperature',
|
||||
($self->{config}->bed_temperature > 0) ? ($self->{config}->bed_temperature + 5) : 0);
|
||||
}
|
||||
|
||||
$self->Destroy;
|
||||
return $self->{config};
|
||||
} else {
|
||||
$self->Destroy;
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Index;
|
||||
use Wx qw(:bitmap :dc :font :misc :sizer :systemsettings :window);
|
||||
use Wx::Event qw(EVT_ERASE_BACKGROUND EVT_PAINT);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $title) = @_;
|
||||
my $self = $class->SUPER::new($parent);
|
||||
|
||||
push @{$self->{titles}}, $title;
|
||||
$self->{own_index} = 0;
|
||||
|
||||
$self->{bullets}->{before} = Wx::Bitmap->new("$Slic3r::var/bullet_black.png", wxBITMAP_TYPE_PNG);
|
||||
$self->{bullets}->{own} = Wx::Bitmap->new("$Slic3r::var/bullet_blue.png", wxBITMAP_TYPE_PNG);
|
||||
$self->{bullets}->{after} = Wx::Bitmap->new("$Slic3r::var/bullet_white.png", wxBITMAP_TYPE_PNG);
|
||||
|
||||
$self->{background} = Wx::Bitmap->new("$Slic3r::var/Slic3r_192px_transparent.png", wxBITMAP_TYPE_PNG);
|
||||
$self->SetMinSize(Wx::Size->new($self->{background}->GetWidth, $self->{background}->GetHeight));
|
||||
|
||||
EVT_PAINT($self, \&repaint);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
my $size = $self->GetClientSize;
|
||||
my $gap = 5;
|
||||
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
$dc->SetBackgroundMode(wxTRANSPARENT);
|
||||
$dc->SetFont($self->GetFont);
|
||||
$dc->SetTextForeground($self->GetForegroundColour);
|
||||
|
||||
my $background_h = $self->{background}->GetHeight;
|
||||
my $background_w = $self->{background}->GetWidth;
|
||||
$dc->DrawBitmap($self->{background}, ($size->GetWidth - $background_w) / 2, ($size->GetHeight - $background_h) / 2, 1);
|
||||
|
||||
my $label_h = $self->{bullets}->{own}->GetHeight;
|
||||
$label_h = $dc->GetCharHeight if $dc->GetCharHeight > $label_h;
|
||||
my $label_w = $size->GetWidth;
|
||||
|
||||
my $i = 0;
|
||||
foreach (@{$self->{titles}}) {
|
||||
my $bullet = $self->{bullets}->{own};
|
||||
$bullet = $self->{bullets}->{before} if $i < $self->{own_index};
|
||||
$bullet = $self->{bullets}->{after} if $i > $self->{own_index};
|
||||
|
||||
$dc->SetTextForeground(Wx::Colour->new(128, 128, 128)) if $i > $self->{own_index};
|
||||
$dc->DrawLabel($_, $bullet, Wx::Rect->new(0, $i * ($label_h + $gap), $label_w, $label_h));
|
||||
$i++;
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
}
|
||||
|
||||
sub prepend_title {
|
||||
my $self = shift;
|
||||
my ($title) = @_;
|
||||
|
||||
unshift @{$self->{titles}}, $title;
|
||||
$self->{own_index}++;
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
sub append_title {
|
||||
my $self = shift;
|
||||
my ($title) = @_;
|
||||
|
||||
push @{$self->{titles}}, $title;
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page;
|
||||
use Wx qw(:font :misc :sizer :staticline :systemsettings);
|
||||
use base 'Wx::WizardPage';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $title, $short_title) = @_;
|
||||
my $self = $class->SUPER::new($parent);
|
||||
|
||||
my $sizer = Wx::FlexGridSizer->new(0, 2, 10, 10);
|
||||
$sizer->AddGrowableCol(1, 1);
|
||||
$sizer->AddGrowableRow(1, 1);
|
||||
$sizer->AddStretchSpacer(0);
|
||||
$self->SetSizer($sizer);
|
||||
|
||||
# title
|
||||
my $text = Wx::StaticText->new($self, -1, $title, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
my $bold_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
$bold_font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
$bold_font->SetPointSize(14);
|
||||
$text->SetFont($bold_font);
|
||||
$sizer->Add($text, 0, wxALIGN_LEFT, 0);
|
||||
|
||||
# index
|
||||
$self->{short_title} = $short_title ? $short_title : $title;
|
||||
$self->{index} = Slic3r::GUI::ConfigWizard::Index->new($self, $self->{short_title});
|
||||
$sizer->Add($self->{index}, 1, wxEXPAND | wxTOP | wxRIGHT, 10);
|
||||
|
||||
# contents
|
||||
$self->{width} = 430;
|
||||
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($self->{vsizer}, 1);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub append_text {
|
||||
my $self = shift;
|
||||
my ($text) = @_;
|
||||
|
||||
my $para = Wx::StaticText->new($self, -1, $text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
|
||||
$para->Wrap($self->{width});
|
||||
$para->SetMinSize([$self->{width}, -1]);
|
||||
$self->{vsizer}->Add($para, 0, wxALIGN_LEFT | wxTOP | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
sub append_option {
|
||||
my $self = shift;
|
||||
my ($full_key) = @_;
|
||||
|
||||
# populate repository with the factory default
|
||||
my $opt_key = $full_key;
|
||||
$opt_key =~ s/#.+//;
|
||||
$self->config->apply(Slic3r::Config->new_from_defaults($opt_key));
|
||||
|
||||
# draw the control
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
parent => $self,
|
||||
title => '',
|
||||
config => $self->config,
|
||||
options => [$full_key],
|
||||
full_labels => 1,
|
||||
);
|
||||
$self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
sub append_panel {
|
||||
my ($self, $panel) = @_;
|
||||
$self->{vsizer}->Add($panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 10);
|
||||
}
|
||||
|
||||
sub set_previous_page {
|
||||
my $self = shift;
|
||||
my ($previous_page) = @_;
|
||||
$self->{previous_page} = $previous_page;
|
||||
}
|
||||
|
||||
sub GetPrev {
|
||||
my $self = shift;
|
||||
return $self->{previous_page};
|
||||
}
|
||||
|
||||
sub set_next_page {
|
||||
my $self = shift;
|
||||
my ($next_page) = @_;
|
||||
$self->{next_page} = $next_page;
|
||||
}
|
||||
|
||||
sub GetNext {
|
||||
my $self = shift;
|
||||
return $self->{next_page};
|
||||
}
|
||||
|
||||
sub get_short_title {
|
||||
my $self = shift;
|
||||
return $self->{short_title};
|
||||
}
|
||||
|
||||
sub build_index {
|
||||
my $self = shift;
|
||||
|
||||
my $page = $self;
|
||||
$self->{index}->prepend_title($page->get_short_title) while ($page = $page->GetPrev);
|
||||
$page = $self;
|
||||
$self->{index}->append_title($page->get_short_title) while ($page = $page->GetNext);
|
||||
}
|
||||
|
||||
sub config {
|
||||
my ($self) = @_;
|
||||
return $self->GetParent->{config};
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Welcome;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, "Welcome to the Slic3r Configuration $wizard", 'Welcome');
|
||||
|
||||
$self->append_text('Hello, welcome to Slic3r! This '.lc($wizard).' helps you with the initial configuration; just a few settings and you will be ready to print.');
|
||||
$self->append_text('To import an existing configuration instead, cancel this '.lc($wizard).' and use the Open Config menu item found in the File menu.');
|
||||
$self->append_text('To continue, click Next.');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Firmware;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Firmware Type');
|
||||
|
||||
$self->append_text('Choose the type of firmware used by your printer, then click Next.');
|
||||
$self->append_option('gcode_flavor');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Bed;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Bed Size');
|
||||
|
||||
$self->append_text('Set the shape of your printer\'s bed, then click Next.');
|
||||
|
||||
$self->config->apply(Slic3r::Config->new_from_defaults('bed_shape'));
|
||||
$self->{bed_shape_panel} = my $panel = Slic3r::GUI::BedShapePanel->new($self, $self->config->bed_shape);
|
||||
$self->{bed_shape_panel}->on_change(sub {
|
||||
$self->config->set('bed_shape', $self->{bed_shape_panel}->GetValue);
|
||||
});
|
||||
$self->append_panel($self->{bed_shape_panel});
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Nozzle;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Nozzle Diameter');
|
||||
|
||||
$self->append_text('Enter the diameter of your printer\'s hot end nozzle, then click Next.');
|
||||
$self->append_option('nozzle_diameter#0');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Filament;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Filament Diameter');
|
||||
|
||||
$self->append_text('Enter the diameter of your filament, then click Next.');
|
||||
$self->append_text('Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.');
|
||||
$self->append_option('filament_diameter#0');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Temperature;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Extrusion Temperature');
|
||||
|
||||
$self->append_text('Enter the temperature needed for extruding your filament, then click Next.');
|
||||
$self->append_text('A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.');
|
||||
$self->append_option('temperature#0');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::BedTemperature;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Bed Temperature');
|
||||
|
||||
$self->append_text('Enter the bed temperature needed for getting your filament to stick to your heated bed, then click Next.');
|
||||
$self->append_text('A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.');
|
||||
$self->append_option('bed_temperature');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::ConfigWizard::Page::Finished;
|
||||
use base 'Slic3r::GUI::ConfigWizard::Page';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, 'Congratulations!', 'Finish');
|
||||
|
||||
$self->append_text("You have successfully completed the Slic3r Configuration $wizard. " .
|
||||
'Slic3r is now configured for your printer and filament.');
|
||||
$self->append_text('To close this '.lc($wizard).' and apply the newly created configuration, click Finish.');
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
190
lib/Slic3r/GUI/Controller.pm
Normal file
190
lib/Slic3r/GUI/Controller.pm
Normal file
@@ -0,0 +1,190 @@
|
||||
# The "Controller" tab to control the printer using serial / USB.
|
||||
# This feature is rarely used. Much more often, the firmware reads the G-codes from a SD card.
|
||||
# May there be multiple subtabs per each printer connected?
|
||||
|
||||
package Slic3r::GUI::Controller;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :frame :id :misc :sizer :bitmap :button :icon :dialog wxBORDER_NONE);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_LEFT_DOWN EVT_MENU);
|
||||
use base qw(Wx::ScrolledWindow Class::Accessor);
|
||||
use List::Util qw(first);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(_selected_printer_preset));
|
||||
|
||||
our @ConfigOptions = qw(bed_shape serial_port serial_speed);
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [600,350]);
|
||||
|
||||
$self->SetScrollbars(0, 1, 0, 1);
|
||||
$self->{sizer} = my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# warning to show when there are no printers configured
|
||||
{
|
||||
$self->{text_no_printers} = Wx::StaticText->new($self, -1,
|
||||
"No printers were configured for USB/serial control.",
|
||||
wxDefaultPosition, wxDefaultSize);
|
||||
$self->{sizer}->Add($self->{text_no_printers}, 0, wxTOP | wxLEFT, 30);
|
||||
}
|
||||
|
||||
# button for adding new printer panels
|
||||
{
|
||||
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
|
||||
$btn->SetToolTipString("Add printer…")
|
||||
if $btn->can('SetToolTipString');
|
||||
|
||||
EVT_LEFT_DOWN($btn, sub {
|
||||
my $menu = Wx::Menu->new;
|
||||
my @panels = $self->print_panels;
|
||||
# remove printers that already exist
|
||||
# update configs of currently loaded print panels
|
||||
foreach my $preset (@{wxTheApp->{preset_bundle}->printer}) {
|
||||
my $preset_name = $preset->name;
|
||||
next if ! $preset->config->serial_port ||
|
||||
defined first { defined $_ && $_->printer_name eq $preset_name } @panels;
|
||||
my $myconfig = $preset->config->clone_only(\@ConfigOptions);
|
||||
my $id = &Wx::NewId();
|
||||
$menu->Append($id, $preset_name);
|
||||
EVT_MENU($menu, $id, sub {
|
||||
$self->add_printer($preset_name, $myconfig);
|
||||
});
|
||||
}
|
||||
$self->PopupMenu($menu, $btn->GetPosition);
|
||||
$menu->Destroy;
|
||||
});
|
||||
$self->{sizer}->Add($btn, 0, wxTOP | wxLEFT, 10);
|
||||
}
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
#$sizer->SetSizeHints($self);
|
||||
|
||||
EVT_CLOSE($self, sub {
|
||||
my (undef, $event) = @_;
|
||||
|
||||
if ($event->CanVeto) {
|
||||
foreach my $panel ($self->print_panels) {
|
||||
if ($panel->printing) {
|
||||
my $confirm = Wx::MessageDialog->new(
|
||||
$self, "Printer '" . $panel->printer_name . "' is printing.\n\nDo you want to stop printing?",
|
||||
'Unfinished Print', wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION,
|
||||
);
|
||||
if ($confirm->ShowModal == wxID_NO) {
|
||||
$event->Veto;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach my $panel ($self->print_panels) {
|
||||
$panel->disconnect;
|
||||
}
|
||||
|
||||
$event->Skip;
|
||||
});
|
||||
|
||||
$self->Layout;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub OnActivate {
|
||||
my ($self) = @_;
|
||||
|
||||
# get all available presets
|
||||
my %presets = map { $_->name => $_->config->clone_only(\@ConfigOptions) }
|
||||
grep { $_->config->serial_port } @{wxTheApp->{preset_bundle}->printer};
|
||||
|
||||
# decide which ones we want to keep
|
||||
my %active = ();
|
||||
|
||||
# keep the ones that are currently connected or have jobs in queue
|
||||
$active{$_} = 1 for map $_->printer_name,
|
||||
grep { $_->is_connected || @{$_->jobs} > 0 }
|
||||
$self->print_panels;
|
||||
|
||||
if (%presets) {
|
||||
# if there are no active panels, use sensible defaults
|
||||
if (!%active && keys %presets <= 2) {
|
||||
# if only one or two presets exist, load them
|
||||
$active{$_} = 1 for keys %presets;
|
||||
}
|
||||
if (!%active) {
|
||||
# enable printers whose port is available
|
||||
my %ports = map { $_ => 1 } Slic3r::GUI::scan_serial_ports;
|
||||
$active{$_} = 1
|
||||
for grep exists $ports{$presets{$_}->serial_port}, keys %presets;
|
||||
}
|
||||
if (!%active && $self->_selected_printer_preset) {
|
||||
# enable currently selected printer if it is configured
|
||||
$active{$self->_selected_printer_preset} = 1
|
||||
if $presets{$self->_selected_printer_preset};
|
||||
}
|
||||
}
|
||||
|
||||
# apply changes
|
||||
for my $panel ($self->print_panels) {
|
||||
next if $active{$panel->printer_name};
|
||||
|
||||
$self->{sizer}->DetachWindow($panel);
|
||||
$panel->Destroy;
|
||||
}
|
||||
$self->add_printer($_, $presets{$_}) for sort keys %active;
|
||||
|
||||
# show/hide the warning about no printers
|
||||
$self->{text_no_printers}->Show(!%presets);
|
||||
|
||||
# show/hide the Add button
|
||||
$self->{btn_add}->Show(keys %presets != keys %active);
|
||||
|
||||
$self->Layout;
|
||||
|
||||
# we need this in order to trigger the OnSize event of wxScrolledWindow which
|
||||
# recalculates the virtual size
|
||||
Wx::GetTopLevelParent($self)->SendSizeEvent;
|
||||
}
|
||||
|
||||
sub add_printer {
|
||||
my ($self, $printer_name, $config) = @_;
|
||||
|
||||
# check that printer doesn't exist already
|
||||
foreach my $panel ($self->print_panels) {
|
||||
if ($panel->printer_name eq $printer_name) {
|
||||
return $panel;
|
||||
}
|
||||
}
|
||||
|
||||
my $printer_panel = Slic3r::GUI::Controller::PrinterPanel->new($self, $printer_name, $config);
|
||||
$self->{sizer}->Prepend($printer_panel, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
$self->Layout;
|
||||
|
||||
return $printer_panel;
|
||||
}
|
||||
|
||||
sub print_panels {
|
||||
my ($self) = @_;
|
||||
return grep $_->isa('Slic3r::GUI::Controller::PrinterPanel'),
|
||||
map $_->GetWindow, $self->{sizer}->GetChildren;
|
||||
}
|
||||
|
||||
# Called by Slic3r::GUI::Tab::Printer::_on_presets_changed
|
||||
# when the presets are loaded or the user selects another preset.
|
||||
sub update_presets {
|
||||
my ($self, $presets) = @_;
|
||||
# update configs of currently loaded print panels
|
||||
my @presets = @$presets;
|
||||
foreach my $panel ($self->print_panels) {
|
||||
my $preset = $presets->find_preset($panel->printer_name, 0);
|
||||
$panel->config($preset->config->clone_only(\@ConfigOptions))
|
||||
if defined $preset;
|
||||
}
|
||||
|
||||
$self->_selected_printer_preset($presets->get_selected_preset->name);
|
||||
}
|
||||
|
||||
1;
|
||||
190
lib/Slic3r/GUI/Controller/ManualControlDialog.pm
Normal file
190
lib/Slic3r/GUI/Controller/ManualControlDialog.pm
Normal file
@@ -0,0 +1,190 @@
|
||||
# A printer "Controller" -> "ManualControlDialog" subtab, opened per 3D printer connected?
|
||||
|
||||
package Slic3r::GUI::Controller::ManualControlDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :choicebook :button :bitmap
|
||||
wxBORDER_NONE wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||
use base qw(Wx::Dialog Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(sender config2 x_homed y_homed));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $config, $sender) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, "Manual Control", wxDefaultPosition,
|
||||
[500,380], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->sender($sender);
|
||||
|
||||
$self->config2({
|
||||
xy_travel_speed => 130,
|
||||
z_travel_speed => 10,
|
||||
});
|
||||
|
||||
my $bed_sizer = Wx::FlexGridSizer->new(2, 3, 1, 1);
|
||||
$bed_sizer->AddGrowableCol(1, 1);
|
||||
$bed_sizer->AddGrowableRow(0, 1);
|
||||
|
||||
my $move_button = sub {
|
||||
my ($sizer, $label, $icon, $bold, $pos, $handler) = @_;
|
||||
|
||||
my $btn = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
|
||||
wxBU_LEFT | wxBU_EXACTFIT);
|
||||
$btn->SetFont($bold ? $Slic3r::GUI::small_bold_font : $Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("$icon.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapPosition($pos);
|
||||
EVT_BUTTON($self, $btn, $handler);
|
||||
$sizer->Add($btn, 1, wxEXPAND | wxALL, 0);
|
||||
};
|
||||
|
||||
# Y buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
for my $d (qw(+10 +1 +0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'Y', 'house', 1, wxLEFT, sub { $self->home('Y') });
|
||||
for my $d (qw(-0.1 -1 -10)) {
|
||||
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Y', $d) });
|
||||
};
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# Bed canvas
|
||||
{
|
||||
my $bed_shape = $config->bed_shape;
|
||||
$self->{canvas} = my $canvas = Slic3r::GUI::2DBed->new($self, $bed_shape);
|
||||
$canvas->interactive(1);
|
||||
$canvas->on_move(sub {
|
||||
my ($pos) = @_;
|
||||
|
||||
if (!($self->x_homed && $self->y_homed)) {
|
||||
Slic3r::GUI::show_error($self, "Please home both X and Y before moving.");
|
||||
return ;
|
||||
}
|
||||
|
||||
# delete any pending commands to get a smoother movement
|
||||
$self->sender->purge_queue(1);
|
||||
$self->abs_xy_move($pos);
|
||||
});
|
||||
$bed_sizer->Add($canvas, 0, wxEXPAND | wxRIGHT, 3);
|
||||
}
|
||||
|
||||
# Z buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
for my $d (qw(+10 +1 +0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_up', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'Z', 'house', 1, wxLEFT, sub { $self->home('Z') });
|
||||
for my $d (qw(-0.1 -1 -10)) {
|
||||
$move_button->($sizer, $d, 'arrow_down', 0, wxLEFT, sub { $self->rel_move('Z', $d) });
|
||||
};
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# XYZ home button
|
||||
$move_button->($bed_sizer, 'XYZ', 'house', 1, wxTOP, sub { $self->home(undef) });
|
||||
|
||||
# X buttons
|
||||
{
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
for my $d (qw(-10 -1 -0.1)) {
|
||||
$move_button->($sizer, $d, 'arrow_left', 0, wxTOP, sub { $self->rel_move('X', $d) });
|
||||
}
|
||||
$move_button->($sizer, 'X', 'house', 1, wxTOP, sub { $self->home('X') });
|
||||
for my $d (qw(+0.1 +1 +10)) {
|
||||
$move_button->($sizer, $d, 'arrow_right', 0, wxTOP, sub { $self->rel_move('X', $d) });
|
||||
}
|
||||
$bed_sizer->Add($sizer, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
my $optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Settings',
|
||||
on_change => sub {
|
||||
my ($opt_id, $value) = @_;
|
||||
$self->config2->{$opt_id} = $value;
|
||||
},
|
||||
);
|
||||
{
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Speed (mm/s)',
|
||||
);
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'xy_travel_speed',
|
||||
type => 'f',
|
||||
label => 'X/Y',
|
||||
tooltip => '',
|
||||
default => $self->config2->{xy_travel_speed},
|
||||
));
|
||||
$line->append_option(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'z_travel_speed',
|
||||
type => 'f',
|
||||
label => 'Z',
|
||||
tooltip => '',
|
||||
default => $self->config2->{z_travel_speed},
|
||||
));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
|
||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$main_sizer->Add($bed_sizer, 1, wxEXPAND | wxALL, 10);
|
||||
$main_sizer->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||
#$main_sizer->Add($self->CreateButtonSizer(wxCLOSE), 0, wxEXPAND);
|
||||
#EVT_BUTTON($self, wxID_CLOSE, sub { $self->Close });
|
||||
|
||||
$self->SetSizer($main_sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
#$main_sizer->SetSizeHints($self);
|
||||
$self->Layout;
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub abs_xy_move {
|
||||
my ($self, $pos) = @_;
|
||||
|
||||
$self->sender->send("G90", 1); # set absolute positioning
|
||||
$self->sender->send(sprintf("G1 X%.1f Y%.1f F%d", @$pos, $self->config2->{xy_travel_speed}*60), 1);
|
||||
$self->{canvas}->set_pos($pos);
|
||||
}
|
||||
|
||||
sub rel_move {
|
||||
my ($self, $axis, $distance) = @_;
|
||||
|
||||
my $speed = ($axis eq 'Z') ? $self->config2->{z_travel_speed} : $self->config2->{xy_travel_speed};
|
||||
$self->sender->send("G91", 1); # set relative positioning
|
||||
$self->sender->send(sprintf("G1 %s%.1f F%d", $axis, $distance, $speed*60), 1);
|
||||
$self->sender->send("G90", 1); # set absolute positioning
|
||||
|
||||
if (my $pos = $self->{canvas}->pos) {
|
||||
if ($axis eq 'X') {
|
||||
$pos->translate($distance, 0);
|
||||
} elsif ($axis eq 'Y') {
|
||||
$pos->translate(0, $distance);
|
||||
}
|
||||
$self->{canvas}->set_pos($pos);
|
||||
}
|
||||
}
|
||||
|
||||
sub home {
|
||||
my ($self, $axis) = @_;
|
||||
|
||||
$axis //= '';
|
||||
$self->sender->send(sprintf("G28 %s", $axis), 1);
|
||||
$self->{canvas}->set_pos(undef);
|
||||
$self->x_homed(1) if $axis eq 'X';
|
||||
$self->y_homed(1) if $axis eq 'Y';
|
||||
}
|
||||
|
||||
1;
|
||||
707
lib/Slic3r/GUI/Controller/PrinterPanel.pm
Normal file
707
lib/Slic3r/GUI/Controller/PrinterPanel.pm
Normal file
@@ -0,0 +1,707 @@
|
||||
package Slic3r::GUI::Controller::PrinterPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :window :gauge :timer
|
||||
:textctrl :font :systemsettings);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_MOUSEWHEEL EVT_TIMER EVT_SCROLLWIN);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(printer_name config sender jobs
|
||||
printing status_timer temp_timer));
|
||||
|
||||
use constant CONNECTION_TIMEOUT => 3; # seconds
|
||||
use constant STATUS_TIMER_INTERVAL => 1000; # milliseconds
|
||||
use constant TEMP_TIMER_INTERVAL => 5000; # milliseconds
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $printer_name, $config) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, [500, 250]);
|
||||
|
||||
$self->printer_name($printer_name || 'Printer');
|
||||
$self->config($config);
|
||||
$self->jobs([]);
|
||||
|
||||
# set up the timer that polls for updates
|
||||
{
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->status_timer(Wx::Timer->new($self, $timer_id));
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
if ($self->printing) {
|
||||
my $queue_size = $self->sender->queue_size;
|
||||
$self->{gauge}->SetValue($self->{gauge}->GetRange - $queue_size);
|
||||
if ($queue_size == 0) {
|
||||
$self->print_completed;
|
||||
}
|
||||
}
|
||||
$self->{log_textctrl}->AppendText("$_\n") for @{$self->sender->purge_log};
|
||||
{
|
||||
my $temp = $self->sender->getT;
|
||||
if ($temp eq '') {
|
||||
$self->{temp_panel}->Hide;
|
||||
} else {
|
||||
if (!$self->{temp_panel}->IsShown) {
|
||||
$self->{temp_panel}->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
$self->{temp_text}->SetLabel($temp . "°C");
|
||||
|
||||
$temp = $self->sender->getB;
|
||||
if ($temp eq '') {
|
||||
$self->{bed_temp_text}->SetLabel('n.a.');
|
||||
} else {
|
||||
$self->{bed_temp_text}->SetLabel($temp . "°C");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
# set up the timer that sends temperature requests
|
||||
# (responses are handled by status_timer)
|
||||
{
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->temp_timer(Wx::Timer->new($self, $timer_id));
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
$self->sender->send("M105", 1); # send it through priority queue
|
||||
});
|
||||
}
|
||||
|
||||
my $box = Wx::StaticBox->new($self, -1, "");
|
||||
my $sizer = Wx::StaticBoxSizer->new($box, wxHORIZONTAL);
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
# printer name
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, $self->printer_name, wxDefaultPosition, [220,-1]);
|
||||
my $font = $text->GetFont;
|
||||
$font->SetPointSize(20);
|
||||
$text->SetFont($font);
|
||||
$left_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
# connection info
|
||||
{
|
||||
my $conn_sizer = Wx::FlexGridSizer->new(2, 2, 1, 0);
|
||||
$conn_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
$conn_sizer->AddGrowableCol(1, 1);
|
||||
$left_sizer->Add($conn_sizer, 0, wxEXPAND | wxTOP, 5);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Port:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
my $serial_port_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
{
|
||||
$self->{serial_port_combobox} = Wx::ComboBox->new($box, -1, $config->serial_port, wxDefaultPosition, wxDefaultSize, []);
|
||||
$self->{serial_port_combobox}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->update_serial_ports;
|
||||
$serial_port_sizer->Add($self->{serial_port_combobox}, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 1);
|
||||
}
|
||||
{
|
||||
$self->{btn_rescan_serial} = my $btn = Wx::BitmapButton->new($box, -1, Wx::Bitmap->new(Slic3r::var("arrow_rotate_clockwise.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, &Wx::wxBORDER_NONE);
|
||||
$btn->SetToolTipString("Rescan serial ports")
|
||||
if $btn->can('SetToolTipString');
|
||||
$serial_port_sizer->Add($btn, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
EVT_BUTTON($self, $btn, sub { $self->update_serial_ports });
|
||||
}
|
||||
$conn_sizer->Add($serial_port_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Speed:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$conn_sizer->Add($text, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
my $serial_speed_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
{
|
||||
$self->{serial_speed_combobox} = Wx::ComboBox->new($box, -1, $config->serial_speed, wxDefaultPosition, wxDefaultSize,
|
||||
["115200", "250000"]);
|
||||
$self->{serial_speed_combobox}->SetFont($Slic3r::GUI::small_font);
|
||||
$serial_speed_sizer->Add($self->{serial_speed_combobox}, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
}
|
||||
{
|
||||
$self->{btn_disconnect} = my $btn = Wx::Button->new($box, -1, "Disconnect", wxDefaultPosition, wxDefaultSize);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
|
||||
$serial_speed_sizer->Add($btn, 0, wxLEFT, 5);
|
||||
EVT_BUTTON($self, $btn, \&disconnect);
|
||||
}
|
||||
$conn_sizer->Add($serial_speed_sizer, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 5);
|
||||
}
|
||||
|
||||
# buttons
|
||||
{
|
||||
$self->{btn_connect} = my $btn = Wx::Button->new($box, -1, "Connect to printer", wxDefaultPosition, [-1, 40]);
|
||||
my $font = $btn->GetFont;
|
||||
$font->SetPointSize($font->GetPointSize + 2);
|
||||
$btn->SetFont($font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("arrow_up.png"), wxBITMAP_TYPE_PNG));
|
||||
$left_sizer->Add($btn, 0, wxTOP, 15);
|
||||
EVT_BUTTON($self, $btn, \&connect);
|
||||
}
|
||||
|
||||
# print progress bar
|
||||
{
|
||||
my $gauge = $self->{gauge} = Wx::Gauge->new($self, wxGA_HORIZONTAL, 100, wxDefaultPosition, wxDefaultSize);
|
||||
$left_sizer->Add($self->{gauge}, 0, wxEXPAND | wxTOP, 15);
|
||||
$gauge->Hide;
|
||||
}
|
||||
|
||||
# status
|
||||
$self->{status_text} = Wx::StaticText->new($box, -1, "", wxDefaultPosition, [200,-1]);
|
||||
$left_sizer->Add($self->{status_text}, 1, wxEXPAND | wxTOP, 15);
|
||||
|
||||
# manual control
|
||||
{
|
||||
$self->{btn_manual_control} = my $btn = Wx::Button->new($box, -1, "Manual control", wxDefaultPosition, wxDefaultSize);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("cog.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->Hide;
|
||||
$left_sizer->Add($btn, 0, wxTOP, 15);
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $dlg = Slic3r::GUI::Controller::ManualControlDialog->new
|
||||
($self, $self->config, $self->sender);
|
||||
$dlg->ShowModal;
|
||||
});
|
||||
}
|
||||
|
||||
# temperature
|
||||
{
|
||||
my $temp_panel = $self->{temp_panel} = Wx::Panel->new($box, -1);
|
||||
my $temp_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
|
||||
my $temp_font = Wx::Font->new($Slic3r::GUI::small_font);
|
||||
$temp_font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
{
|
||||
my $text = Wx::StaticText->new($temp_panel, -1, "Temperature:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
$self->{temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||
$self->{temp_text}->SetFont($temp_font);
|
||||
$self->{temp_text}->SetForegroundColour(Wx::wxRED);
|
||||
$temp_sizer->Add($self->{temp_text}, 1, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
{
|
||||
my $text = Wx::StaticText->new($temp_panel, -1, "Bed:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$temp_sizer->Add($text, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
$self->{bed_temp_text} = Wx::StaticText->new($temp_panel, -1, "", wxDefaultPosition, wxDefaultSize);
|
||||
$self->{bed_temp_text}->SetFont($temp_font);
|
||||
$self->{bed_temp_text}->SetForegroundColour(Wx::wxRED);
|
||||
$temp_sizer->Add($self->{bed_temp_text}, 1, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
$temp_panel->SetSizer($temp_sizer);
|
||||
$temp_panel->Hide;
|
||||
$left_sizer->Add($temp_panel, 0, wxEXPAND | wxTOP | wxBOTTOM, 4);
|
||||
}
|
||||
|
||||
# print jobs panel
|
||||
$self->{print_jobs_sizer} = my $print_jobs_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Queue:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$print_jobs_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
|
||||
$self->{jobs_panel} = Wx::ScrolledWindow->new($box, -1, wxDefaultPosition, wxDefaultSize,
|
||||
wxVSCROLL | wxBORDER_NONE);
|
||||
$self->{jobs_panel}->SetScrollbars(0, 1, 0, 1);
|
||||
$self->{jobs_panel_sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{jobs_panel}->SetSizer($self->{jobs_panel_sizer});
|
||||
$print_jobs_sizer->Add($self->{jobs_panel}, 1, wxEXPAND, 0);
|
||||
|
||||
# TODO: fix this. We're trying to pass the scroll event to the parent but it
|
||||
# doesn't work.
|
||||
EVT_SCROLLWIN($self->{jobs_panel}, sub {
|
||||
my ($panel, $event) = @_;
|
||||
|
||||
my $controller = $self->GetParent;
|
||||
my $new_event = Wx::ScrollWinEvent->new(
|
||||
$event->GetEventType,
|
||||
$event->GetPosition,
|
||||
$event->GetOrientation,
|
||||
);
|
||||
$controller->ProcessEvent($new_event);
|
||||
}) if 0;
|
||||
}
|
||||
|
||||
my $log_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
my $text = Wx::StaticText->new($box, -1, "Log:", wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
$log_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
|
||||
my $log = $self->{log_textctrl} = Wx::TextCtrl->new($box, -1, "", wxDefaultPosition, wxDefaultSize,
|
||||
wxTE_MULTILINE | wxBORDER_NONE);
|
||||
$log->SetBackgroundColour($box->GetBackgroundColour);
|
||||
$log->SetFont($Slic3r::GUI::small_font);
|
||||
$log->SetEditable(0);
|
||||
$log_sizer->Add($self->{log_textctrl}, 1, wxEXPAND, 0);
|
||||
}
|
||||
|
||||
$sizer->Add($left_sizer, 0, wxEXPAND | wxALL, 0);
|
||||
$sizer->Add($print_jobs_sizer, 2, wxEXPAND | wxALL, 0);
|
||||
$sizer->Add($log_sizer, 1, wxEXPAND | wxLEFT, 15);
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
$self->_update_connection_controls;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub is_connected {
|
||||
my ($self) = @_;
|
||||
return $self->sender && $self->sender->is_connected;
|
||||
}
|
||||
|
||||
sub _update_connection_controls {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->{btn_connect}->Show;
|
||||
$self->{btn_disconnect}->Hide;
|
||||
$self->{serial_port_combobox}->Enable;
|
||||
$self->{serial_speed_combobox}->Enable;
|
||||
$self->{btn_rescan_serial}->Enable;
|
||||
$self->{btn_manual_control}->Hide;
|
||||
$self->{btn_manual_control}->Disable;
|
||||
|
||||
if ($self->is_connected) {
|
||||
$self->{btn_connect}->Hide;
|
||||
$self->{btn_manual_control}->Show;
|
||||
if (!$self->printing || $self->printing->paused) {
|
||||
$self->{btn_disconnect}->Show;
|
||||
$self->{btn_manual_control}->Enable;
|
||||
}
|
||||
$self->{serial_port_combobox}->Disable;
|
||||
$self->{serial_speed_combobox}->Disable;
|
||||
$self->{btn_rescan_serial}->Disable;
|
||||
}
|
||||
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub set_status {
|
||||
my ($self, $status) = @_;
|
||||
$self->{status_text}->SetLabel($status);
|
||||
$self->{status_text}->Wrap($self->{status_text}->GetSize->GetWidth);
|
||||
$self->{status_text}->Refresh;
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub connect {
|
||||
my ($self) = @_;
|
||||
|
||||
return if $self->is_connected;
|
||||
|
||||
$self->set_status("Connecting...");
|
||||
$self->sender(Slic3r::GCode::Sender->new);
|
||||
my $res = $self->sender->connect(
|
||||
$self->{serial_port_combobox}->GetValue,
|
||||
$self->{serial_speed_combobox}->GetValue,
|
||||
);
|
||||
if (!$res) {
|
||||
$self->set_status("Connection failed. Check serial port and speed.");
|
||||
} else {
|
||||
if ($self->sender->wait_connected) {
|
||||
$self->set_status("Printer is online. You can now start printing from the queue on the right.");
|
||||
$self->status_timer->Start(STATUS_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
|
||||
$self->temp_timer->Start(TEMP_TIMER_INTERVAL, wxTIMER_CONTINUOUS);
|
||||
|
||||
# request temperature now, without waiting for the timer
|
||||
$self->sender->send("M105", 1);
|
||||
} else {
|
||||
$self->set_status("Connection failed. Check serial port and speed.");
|
||||
}
|
||||
}
|
||||
$self->_update_connection_controls;
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub disconnect {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->status_timer->Stop;
|
||||
$self->temp_timer->Stop;
|
||||
return if !$self->is_connected;
|
||||
|
||||
$self->printing->printing(0) if $self->printing;
|
||||
$self->printing(undef);
|
||||
$self->{gauge}->Hide;
|
||||
$self->{temp_panel}->Hide;
|
||||
$self->sender->disconnect;
|
||||
$self->set_status("");
|
||||
$self->_update_connection_controls;
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub update_serial_ports {
|
||||
my ($self) = @_;
|
||||
|
||||
my $cb = $self->{serial_port_combobox};
|
||||
my $current = $cb->GetValue;
|
||||
$cb->Clear;
|
||||
$cb->Append($_) for Slic3r::GUI::scan_serial_ports;
|
||||
$cb->SetValue($current);
|
||||
}
|
||||
|
||||
sub load_print_job {
|
||||
my ($self, $gcode_file, $filament_stats) = @_;
|
||||
|
||||
push @{$self->jobs}, my $job = Slic3r::GUI::Controller::PrinterPanel::PrintJob->new(
|
||||
id => time() . $gcode_file . rand(1000),
|
||||
gcode_file => $gcode_file,
|
||||
filament_stats => $filament_stats,
|
||||
);
|
||||
$self->reload_jobs;
|
||||
return $job;
|
||||
}
|
||||
|
||||
sub delete_job {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
$self->jobs([ grep $_->id ne $job->id, @{$self->jobs} ]);
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub print_job {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
$self->printing($job);
|
||||
$job->printing(1);
|
||||
$self->reload_jobs;
|
||||
|
||||
open my $fh, '<', $job->gcode_file;
|
||||
my $line_count = 0;
|
||||
while (my $row = <$fh>) {
|
||||
$self->sender->send($row);
|
||||
$line_count++;
|
||||
}
|
||||
close $fh;
|
||||
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->SetRange($line_count);
|
||||
$self->{gauge}->SetValue(0);
|
||||
$self->{gauge}->Enable;
|
||||
$self->{gauge}->Show;
|
||||
$self->Layout;
|
||||
|
||||
$self->set_status('Printing...');
|
||||
$self->{log_textctrl}->AppendText(sprintf "=====\n");
|
||||
$self->{log_textctrl}->AppendText(sprintf "Printing %s\n", $job->name);
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print started at %s\n", $self->_timestamp);
|
||||
}
|
||||
|
||||
sub print_completed {
|
||||
my ($self) = @_;
|
||||
|
||||
my $job = $self->printing;
|
||||
$self->printing(undef);
|
||||
$job->printing(0);
|
||||
$job->printed(1);
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Hide;
|
||||
$self->Layout;
|
||||
|
||||
$self->set_status('Print completed.');
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print completed at %s\n", $self->_timestamp);
|
||||
|
||||
$self->reload_jobs;
|
||||
}
|
||||
|
||||
sub reload_jobs {
|
||||
my ($self) = @_;
|
||||
|
||||
# reorder jobs
|
||||
@{$self->jobs} = sort { ($a->printed <=> $b->printed) || ($a->timestamp <=> $b->timestamp) }
|
||||
@{$self->jobs};
|
||||
|
||||
# remove all panels
|
||||
foreach my $child ($self->{jobs_panel_sizer}->GetChildren) {
|
||||
my $window = $child->GetWindow;
|
||||
$self->{jobs_panel_sizer}->Detach($window);
|
||||
# now $child does not exist anymore
|
||||
$window->Destroy;
|
||||
}
|
||||
|
||||
# re-add all panels
|
||||
foreach my $job (@{$self->jobs}) {
|
||||
my $panel = Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel->new($self->{jobs_panel}, $job);
|
||||
$self->{jobs_panel_sizer}->Add($panel, 0, wxEXPAND | wxBOTTOM, 5);
|
||||
|
||||
$panel->on_delete_job(sub {
|
||||
my ($job) = @_;
|
||||
$self->delete_job($job);
|
||||
});
|
||||
$panel->on_print_job(sub {
|
||||
my ($job) = @_;
|
||||
$self->print_job($job);
|
||||
});
|
||||
$panel->on_pause_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->pause_queue;
|
||||
$job->paused(1);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Disable;
|
||||
$self->set_status('Print is paused. Click on Resume to continue.');
|
||||
});
|
||||
$panel->on_abort_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->purge_queue;
|
||||
$self->printing(undef);
|
||||
$job->printing(0);
|
||||
$job->paused(0);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Disable;
|
||||
$self->{gauge}->Hide;
|
||||
$self->set_status('Print was aborted.');
|
||||
$self->{log_textctrl}->AppendText(sprintf "Print aborted at %s\n", $self->_timestamp);
|
||||
});
|
||||
$panel->on_resume_print(sub {
|
||||
my ($job) = @_;
|
||||
$self->sender->resume_queue;
|
||||
$job->paused(0);
|
||||
$self->reload_jobs;
|
||||
$self->_update_connection_controls;
|
||||
$self->{gauge}->Enable;
|
||||
$self->set_status('Printing...');
|
||||
});
|
||||
$panel->enable_print if $self->is_connected && !$self->printing;
|
||||
|
||||
EVT_MOUSEWHEEL($panel, sub {
|
||||
my (undef, $event) = @_;
|
||||
Wx::PostEvent($self->{jobs_panel}, $event);
|
||||
$event->Skip;
|
||||
});
|
||||
}
|
||||
|
||||
$self->{jobs_panel}->Layout;
|
||||
$self->{print_jobs_sizer}->Layout;
|
||||
}
|
||||
|
||||
sub _timestamp {
|
||||
my ($self) = @_;
|
||||
|
||||
my @time = localtime(time);
|
||||
return sprintf '%02d:%02d:%02d', @time[2,1,0];
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Controller::PrinterPanel::PrintJob;
|
||||
use Moo;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
|
||||
has 'id' => (is => 'ro', required => 1);
|
||||
has 'timestamp' => (is => 'ro', default => sub { time });
|
||||
has 'gcode_file' => (is => 'ro', required => 1);
|
||||
has 'filament_stats' => (is => 'rw');
|
||||
has 'printing' => (is => 'rw', default => sub { 0 });
|
||||
has 'paused' => (is => 'rw', default => sub { 0 });
|
||||
has 'printed' => (is => 'rw', default => sub { 0 });
|
||||
|
||||
sub name {
|
||||
my ($self) = @_;
|
||||
return basename($self->gcode_file);
|
||||
}
|
||||
|
||||
package Slic3r::GUI::Controller::PrinterPanel::PrintJobPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :panel :id :misc :sizer :button :bitmap :font :dialog :icon :timer
|
||||
:colour :brush :pen);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TIMER EVT_ERASE_BACKGROUND);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(job on_delete_job on_print_job on_pause_print on_resume_print
|
||||
on_abort_print blink_timer));
|
||||
|
||||
sub new {
|
||||
my ($class, $parent, $job) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
$self->job($job);
|
||||
$self->SetBackgroundColour(wxWHITE);
|
||||
|
||||
{
|
||||
my $white_brush = Wx::Brush->new(wxWHITE, wxSOLID);
|
||||
my $pen = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
EVT_ERASE_BACKGROUND($self, sub {
|
||||
my ($self, $event) = @_;
|
||||
my $dc = $event->GetDC;
|
||||
my $size = $self->GetSize;
|
||||
$dc->SetBrush($white_brush);
|
||||
$dc->SetPen($pen);
|
||||
$dc->DrawRoundedRectangle(0, 0, $size->GetWidth,$size->GetHeight, 6);
|
||||
});
|
||||
}
|
||||
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
{
|
||||
$self->{job_name_textctrl} = my $text = Wx::StaticText->new($self, -1, $job->name, wxDefaultPosition, wxDefaultSize);
|
||||
my $font = $text->GetFont;
|
||||
$font->SetWeight(wxFONTWEIGHT_BOLD);
|
||||
$text->SetFont($font);
|
||||
if ($job->printed) {
|
||||
$text->SetForegroundColour($Slic3r::GUI::grey);
|
||||
}
|
||||
$left_sizer->Add($text, 0, wxEXPAND, 0);
|
||||
}
|
||||
{
|
||||
my $filament_stats = join "\n",
|
||||
map "$_ (" . sprintf("%.2f", $job->filament_stats->{$_}/1000) . "m)",
|
||||
sort keys %{$job->filament_stats};
|
||||
my $text = Wx::StaticText->new($self, -1, $filament_stats, wxDefaultPosition, wxDefaultSize);
|
||||
$text->SetFont($Slic3r::GUI::small_font);
|
||||
if ($job->printed && !$job->printing) {
|
||||
$text->SetForegroundColour($Slic3r::GUI::grey);
|
||||
}
|
||||
$left_sizer->Add($text, 0, wxEXPAND | wxTOP, 6);
|
||||
}
|
||||
|
||||
my $buttons_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
my $button_style = Wx::wxBORDER_NONE | wxBU_EXACTFIT;
|
||||
{
|
||||
my $btn = $self->{btn_delete} = Wx::Button->new($self, -1, 'Delete',
|
||||
wxDefaultPosition, wxDefaultSize, $button_style);
|
||||
$btn->SetToolTipString("Delete this job from print queue")
|
||||
if $btn->can('SetToolTipString');
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG));
|
||||
if ($job->printing) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
my $res = Wx::MessageDialog->new($self, "Are you sure you want to delete this print job?", 'Delete Job', wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION)->ShowModal;
|
||||
return unless $res == wxID_YES;
|
||||
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_delete_job->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $label = $job->printed ? 'Print Again' : 'Print This';
|
||||
my $btn = $self->{btn_print} = Wx::Button->new($self, -1, $label, wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_bold_font);
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
#$btn->SetBitmapPosition(wxRIGHT);
|
||||
$btn->Hide;
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_print_job->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_pause} = Wx::Button->new($self, -1, "Pause", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing || $job->paused) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_pause.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_pause_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_pause_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_resume} = Wx::Button->new($self, -1, "Resume", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing || !$job->paused) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_play.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_play_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_resume_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
{
|
||||
my $btn = $self->{btn_abort} = Wx::Button->new($self, -1, "Abort", wxDefaultPosition, wxDefaultSize,
|
||||
$button_style);
|
||||
$btn->SetFont($Slic3r::GUI::small_font);
|
||||
if (!$job->printing) {
|
||||
$btn->Hide;
|
||||
}
|
||||
$btn->SetBitmap(Wx::Bitmap->new(Slic3r::var("control_stop.png"), wxBITMAP_TYPE_PNG));
|
||||
$btn->SetBitmapCurrent(Wx::Bitmap->new(Slic3r::var("control_stop_blue.png"), wxBITMAP_TYPE_PNG));
|
||||
$buttons_sizer->Add($btn, 0, wxBOTTOM, 2);
|
||||
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->on_abort_print->($job);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($left_sizer, 1, wxEXPAND | wxALL, 6);
|
||||
$sizer->Add($buttons_sizer, 0, wxEXPAND | wxALL, 6);
|
||||
$self->SetSizer($sizer);
|
||||
|
||||
# set-up the timer that changes the job name color while printing
|
||||
if ($self->job->printing && !$self->job->paused) {
|
||||
my $timer_id = &Wx::NewId();
|
||||
$self->blink_timer(Wx::Timer->new($self, $timer_id));
|
||||
my $blink = 0; # closure
|
||||
my $colour = Wx::Colour->new(0, 190, 0);
|
||||
EVT_TIMER($self, $timer_id, sub {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
$self->{job_name_textctrl}->SetForegroundColour($blink ? Wx::wxBLACK : $colour);
|
||||
$blink = !$blink;
|
||||
});
|
||||
$self->blink_timer->Start(1000, wxTIMER_CONTINUOUS);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub enable_print {
|
||||
my ($self) = @_;
|
||||
|
||||
if (!$self->job->printing) {
|
||||
$self->{btn_print}->Show;
|
||||
}
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
|
||||
# There's a gap between the time Perl destroys the wxPanel object and
|
||||
# the blink_timer member, so the wxTimer might still fire an event which
|
||||
# isn't handled properly, causing a crash. So we ensure that blink_timer
|
||||
# is stopped before we destroy the wxPanel.
|
||||
$self->blink_timer->Stop if $self->blink_timer;
|
||||
return $self->SUPER::Destroy;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
package Slic3r::GUI::Notifier;
|
||||
use Moo;
|
||||
|
||||
has 'growler' => (is => 'rw');
|
||||
|
||||
my $icon = "$Slic3r::var/Slic3r.png";
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
if (eval 'use Growl::GNTP; 1') {
|
||||
# register with growl
|
||||
eval {
|
||||
$self->growler(Growl::GNTP->new(AppName => 'Slic3r', AppIcon => $icon));
|
||||
$self->growler->register([{Name => 'SKEIN_DONE', DisplayName => 'Slicing Done'}]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
sub notify {
|
||||
my ($self, $message) = @_;
|
||||
my $title = 'Slicing Done!';
|
||||
|
||||
eval {
|
||||
$self->growler->notify(Event => 'SKEIN_DONE', Title => $title, Message => $message)
|
||||
if $self->growler;
|
||||
};
|
||||
# Net::DBus is broken in multithreaded environment
|
||||
if (0 && eval 'use Net::DBus; 1') {
|
||||
eval {
|
||||
my $session = Net::DBus->session;
|
||||
my $serv = $session->get_service('org.freedesktop.Notifications');
|
||||
my $notifier = $serv->get_object('/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications');
|
||||
$notifier->Notify('Slic3r', 0, $icon, $title, $message, [], {}, -1);
|
||||
undef $Net::DBus::bus_session;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,3 +1,5 @@
|
||||
# A dialog group object. Used by the Tab, Preferences dialog, ManualControlDialog etc.
|
||||
|
||||
package Slic3r::GUI::OptionsGroup;
|
||||
use Moo;
|
||||
|
||||
@@ -83,7 +85,7 @@ sub append_line {
|
||||
# if we have a single option with no sidetext just add it directly to the grid sizer
|
||||
my @options = @{$line->get_options};
|
||||
$self->_options->{$_->opt_id} = $_ for @options;
|
||||
if (@options == 1 && !$options[0]->sidetext) {
|
||||
if (@options == 1 && !$options[0]->sidetext && !$options[0]->side_widget && !@{$line->get_extra_widgets}) {
|
||||
my $option = $options[0];
|
||||
my $field = $self->_build_field($option);
|
||||
$grid_sizer->Add($field, 0, ($option->full_width ? wxEXPAND : 0) | wxALIGN_CENTER_VERTICAL, 0);
|
||||
@@ -95,7 +97,9 @@ sub append_line {
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$grid_sizer->Add($sizer, 0, 0, 0);
|
||||
|
||||
foreach my $option (@options) {
|
||||
foreach my $i (0..$#options) {
|
||||
my $option = $options[$i];
|
||||
|
||||
# add label if any
|
||||
if ($option->label) {
|
||||
my $field_label = Wx::StaticText->new($self->parent, -1, $option->label . ":", wxDefaultPosition, wxDefaultSize);
|
||||
@@ -111,12 +115,26 @@ sub append_line {
|
||||
if ($option->sidetext) {
|
||||
my $sidetext = Wx::StaticText->new($self->parent, -1, $option->sidetext, wxDefaultPosition, wxDefaultSize);
|
||||
$sidetext->SetFont($self->sidetext_font);
|
||||
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
||||
$sizer->Add($sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
|
||||
}
|
||||
|
||||
# add side widget if any
|
||||
if ($option->side_widget) {
|
||||
$sizer->Add($option->side_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1);
|
||||
}
|
||||
|
||||
if ($option != $#options) {
|
||||
$sizer->AddSpacer(4);
|
||||
}
|
||||
}
|
||||
|
||||
# add extra sizers if any
|
||||
foreach my $extra_widget (@{$line->get_extra_widgets}) {
|
||||
$sizer->Add($extra_widget->($self->parent), 0, wxLEFT | wxALIGN_CENTER_VERTICAL , 4);
|
||||
}
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
sub create_single_option_line {
|
||||
my ($self, $option) = @_;
|
||||
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
@@ -125,18 +143,24 @@ sub append_single_option_line {
|
||||
);
|
||||
$option->label("");
|
||||
$line->append_option($option);
|
||||
$self->append_line($line);
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
my ($self, $option) = @_;
|
||||
return $self->append_line($self->create_single_option_line($option));
|
||||
}
|
||||
|
||||
sub _build_field {
|
||||
my $self = shift;
|
||||
my ($opt) = @_;
|
||||
|
||||
my $opt_id = $opt->opt_id;
|
||||
my $on_change = sub {
|
||||
#! This function will be called from Field.
|
||||
my ($opt_id, $value) = @_;
|
||||
#! Call OptionGroup._on_change(...)
|
||||
$self->_on_change($opt_id, $value)
|
||||
unless $self->_disabled;
|
||||
};
|
||||
@@ -158,12 +182,17 @@ sub _build_field {
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'color') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::ColourPicker->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type =~ /^(f|s|s@|percent)$/) {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::TextCtrl->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
);
|
||||
} elsif ($type eq 'select') {
|
||||
} elsif ($type eq 'select' || $type eq 'select_open') {
|
||||
$field = Slic3r::GUI::OptionsGroup::Field::Choice->new(
|
||||
parent => $self->parent,
|
||||
option => $opt,
|
||||
@@ -186,6 +215,8 @@ sub _build_field {
|
||||
}
|
||||
return undef if !$field;
|
||||
|
||||
#! setting up a function that will be triggered when the field changes
|
||||
#! think of it as $field->on_change = ($on_change)
|
||||
$field->on_change($on_change);
|
||||
$field->on_kill_focus($on_kill_focus);
|
||||
$self->_fields->{$opt_id} = $field;
|
||||
@@ -222,8 +253,20 @@ sub set_value {
|
||||
}
|
||||
|
||||
sub _on_change {
|
||||
my ($self, $opt_id) = @_;
|
||||
$self->on_change->($opt_id);
|
||||
my ($self, $opt_id, $value) = @_;
|
||||
$self->on_change->($opt_id, $value);
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
|
||||
$_->enable for values %{$self->_fields};
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$_->disable for values %{$self->_fields};
|
||||
}
|
||||
|
||||
sub _on_kill_focus {
|
||||
@@ -241,6 +284,8 @@ has 'label_tooltip' => (is => 'rw', default => sub { "" });
|
||||
has 'sizer' => (is => 'rw');
|
||||
has 'widget' => (is => 'rw');
|
||||
has '_options' => (is => 'ro', default => sub { [] });
|
||||
# Extra UI components after the label and the edit widget of the option.
|
||||
has '_extra_widgets' => (is => 'ro', default => sub { [] });
|
||||
|
||||
# this method accepts a Slic3r::GUI::OptionsGroup::Option object
|
||||
sub append_option {
|
||||
@@ -248,12 +293,24 @@ sub append_option {
|
||||
push @{$self->_options}, $option;
|
||||
}
|
||||
|
||||
sub append_widget {
|
||||
my ($self, $widget) = @_;
|
||||
push @{$self->_extra_widgets}, $widget;
|
||||
}
|
||||
|
||||
sub get_options {
|
||||
my ($self) = @_;
|
||||
return [ @{$self->_options} ];
|
||||
}
|
||||
|
||||
sub get_extra_widgets {
|
||||
my ($self) = @_;
|
||||
return [ @{$self->_extra_widgets} ];
|
||||
}
|
||||
|
||||
|
||||
# Configuration of an option.
|
||||
# This very much reflects the content of the C++ ConfigOptionDef class.
|
||||
package Slic3r::GUI::OptionsGroup::Option;
|
||||
use Moo;
|
||||
|
||||
@@ -274,6 +331,7 @@ has 'max' => (is => 'rw', default => sub { undef });
|
||||
has 'labels' => (is => 'rw', default => sub { [] });
|
||||
has 'values' => (is => 'rw', default => sub { [] });
|
||||
has 'readonly' => (is => 'rw', default => sub { 0 });
|
||||
has 'side_widget' => (is => 'rw', default => sub { undef });
|
||||
|
||||
|
||||
package Slic3r::GUI::ConfigOptionsGroup;
|
||||
@@ -298,6 +356,8 @@ sub get_option {
|
||||
my $opt_id = ($opt_index == -1 ? $opt_key : "${opt_key}#${opt_index}");
|
||||
$self->_opt_map->{$opt_id} = [ $opt_key, $opt_index ];
|
||||
|
||||
# Slic3r::Config::Options is a C++ Slic3r::PrintConfigDef exported as a Perl hash of hashes.
|
||||
# The C++ counterpart is a constant singleton.
|
||||
my $optdef = $Slic3r::Config::Options->{$opt_key}; # we should access this from $self->config
|
||||
my $default_value = $self->_get_config_value($opt_key, $opt_index, $optdef->{gui_flags} =~ /\bserialized\b/);
|
||||
|
||||
@@ -309,7 +369,8 @@ sub get_option {
|
||||
gui_flags => $optdef->{gui_flags},
|
||||
label => ($self->full_labels && defined $optdef->{full_label}) ? $optdef->{full_label} : $optdef->{label},
|
||||
sidetext => $optdef->{sidetext},
|
||||
tooltip => $optdef->{tooltip} . " (default: " . $default_value . ")",
|
||||
# calling serialize() ensures we get a stringified value
|
||||
tooltip => $optdef->{tooltip} . " (default: " . $self->config->serialize($opt_key) . ")",
|
||||
multiline => $optdef->{multiline},
|
||||
width => $optdef->{width},
|
||||
min => $optdef->{min},
|
||||
@@ -320,7 +381,7 @@ sub get_option {
|
||||
);
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
sub create_single_option_line {
|
||||
my ($self, $opt_key, $opt_index) = @_;
|
||||
|
||||
my $option;
|
||||
@@ -329,9 +390,15 @@ sub append_single_option_line {
|
||||
} else {
|
||||
$option = $self->get_option($opt_key, $opt_index);
|
||||
}
|
||||
return $self->SUPER::append_single_option_line($option);
|
||||
return $self->SUPER::create_single_option_line($option);
|
||||
}
|
||||
|
||||
sub append_single_option_line {
|
||||
my ($self, $option, $opt_index) = @_;
|
||||
return $self->append_line($self->create_single_option_line($option, $opt_index));
|
||||
}
|
||||
|
||||
# Initialize UI components with the config values.
|
||||
sub reload_config {
|
||||
my ($self) = @_;
|
||||
|
||||
@@ -355,8 +422,11 @@ sub _get_config_value {
|
||||
my ($self, $opt_key, $opt_index, $deserialize) = @_;
|
||||
|
||||
if ($deserialize) {
|
||||
# Want to edit a vector value (currently only multi-strings) in a single edit box.
|
||||
# Aggregate the strings the old way.
|
||||
# Currently used for the post_process config value only.
|
||||
die "Can't deserialize option indexed value" if $opt_index != -1;
|
||||
return $self->config->serialize($opt_key);
|
||||
return join(';', @{$self->config->get($opt_key)});
|
||||
} else {
|
||||
return $opt_index == -1
|
||||
? $self->config->get($opt_key)
|
||||
@@ -365,7 +435,7 @@ sub _get_config_value {
|
||||
}
|
||||
|
||||
sub _on_change {
|
||||
my ($self, $opt_id) = @_;
|
||||
my ($self, $opt_id, $value) = @_;
|
||||
|
||||
if (exists $self->_opt_map->{$opt_id}) {
|
||||
my ($opt_key, $opt_index) = @{ $self->_opt_map->{$opt_id} };
|
||||
@@ -375,7 +445,10 @@ sub _on_change {
|
||||
my $field_value = $self->get_value($opt_id);
|
||||
if ($option->gui_flags =~ /\bserialized\b/) {
|
||||
die "Can't set serialized option indexed value" if $opt_index != -1;
|
||||
$self->config->set_deserialize($opt_key, $field_value);
|
||||
# Split a string to multiple strings by a semi-colon. This is the old way of storing multi-string values.
|
||||
# Currently used for the post_process config value only.
|
||||
my @values = split /;/, $field_value;
|
||||
$self->config->set($opt_key, \@values);
|
||||
} else {
|
||||
if ($opt_index == -1) {
|
||||
$self->config->set($opt_key, $field_value);
|
||||
@@ -387,7 +460,7 @@ sub _on_change {
|
||||
}
|
||||
}
|
||||
|
||||
$self->SUPER::_on_change($opt_id);
|
||||
$self->SUPER::_on_change($opt_id, $value);
|
||||
}
|
||||
|
||||
sub _on_kill_focus {
|
||||
@@ -399,6 +472,8 @@ sub _on_kill_focus {
|
||||
$self->reload_config;
|
||||
}
|
||||
|
||||
# Static text shown among the options.
|
||||
# Currently used for the filament cooling legend only.
|
||||
package Slic3r::GUI::OptionsGroup::StaticText;
|
||||
use Wx qw(:misc :systemsettings);
|
||||
use base 'Wx::StaticText';
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# An input field class prototype.
|
||||
package Slic3r::GUI::OptionsGroup::Field;
|
||||
use Moo;
|
||||
|
||||
# This is a base class for option fields.
|
||||
|
||||
has 'parent' => (is => 'ro', required => 1);
|
||||
has 'option' => (is => 'ro', required => 1); # Slic3r::GUI::OptionsGroup::Option
|
||||
# Slic3r::GUI::OptionsGroup::Option
|
||||
has 'option' => (is => 'ro', required => 1);
|
||||
# On change callback
|
||||
has 'on_change' => (is => 'rw', default => sub { sub {} });
|
||||
has 'on_kill_focus' => (is => 'rw', default => sub { sub {} });
|
||||
has 'wxSsizer' => (is => 'rw'); # alternatively, wxSizer object
|
||||
# If set, the callback $self->on_change is not called.
|
||||
# This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
has 'disable_change_event' => (is => 'rw', default => sub { 0 });
|
||||
|
||||
# This method should not fire the on_change event
|
||||
@@ -36,7 +40,7 @@ sub toggle {
|
||||
sub _on_change {
|
||||
my ($self, $opt_id) = @_;
|
||||
|
||||
$self->on_change->($opt_id)
|
||||
$self->on_change->($opt_id, $self->get_value)
|
||||
unless $self->disable_change_event;
|
||||
}
|
||||
|
||||
@@ -120,6 +124,10 @@ sub BUILD {
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->wxWindow->GetValue ? 1 : 0;
|
||||
}
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::SpinCtrl;
|
||||
use Moo;
|
||||
@@ -128,6 +136,8 @@ extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
use Wx qw(:misc);
|
||||
use Wx::Event qw(EVT_SPINCTRL EVT_TEXT EVT_KILL_FOCUS);
|
||||
|
||||
has 'tmp_value' => (is => 'rw');
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
@@ -136,16 +146,34 @@ sub BUILD {
|
||||
$self->wxWindow($field);
|
||||
|
||||
EVT_SPINCTRL($self->parent, $field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
my ($s, $event) = @_;
|
||||
|
||||
# On OSX/Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
# when it was changed from the text control, so the on_change callback
|
||||
# gets the old one, and on_kill_focus resets the control to the old value.
|
||||
# As a workaround, we get the new value from $event->GetString and store
|
||||
# here temporarily so that we can return it from $self->get_value
|
||||
$self->tmp_value($event->GetString) if $event->GetString =~ /^\d+$/;
|
||||
$self->_on_change($self->option->opt_id);
|
||||
# We don't reset tmp_value here because _on_change might put callbacks
|
||||
# in the CallAfter queue, and we want the tmp value to be available from
|
||||
# them as well.
|
||||
});
|
||||
EVT_KILL_FOCUS($field, sub {
|
||||
$self->tmp_value(undef);
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->tmp_value // $self->wxWindow->GetValue;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::TextCtrl;
|
||||
use Moo;
|
||||
@@ -194,44 +222,86 @@ extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:misc :combobox);
|
||||
use Wx::Event qw(EVT_COMBOBOX);
|
||||
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $style = 0;
|
||||
$style |= wxCB_READONLY if defined $self->option->gui_type && $self->option->gui_type ne 'select_open';
|
||||
my $field = Wx::ComboBox->new($self->parent, -1, "", wxDefaultPosition, $self->_default_size,
|
||||
$self->option->labels || $self->option->values, wxCB_READONLY);
|
||||
$self->option->labels || $self->option->values || [], $style);
|
||||
$self->wxWindow($field);
|
||||
|
||||
$self->set_value($self->option->default);
|
||||
|
||||
EVT_COMBOBOX($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
EVT_TEXT($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
my $idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
|
||||
$self->disable_change_event(1);
|
||||
|
||||
my $idx;
|
||||
if ($self->option->values) {
|
||||
$idx = first { $self->option->values->[$_] eq $value } 0..$#{$self->option->values};
|
||||
# if value is not among indexes values we use SetValue()
|
||||
}
|
||||
|
||||
if (defined $idx) {
|
||||
$self->wxWindow->SetSelection($idx);
|
||||
} else {
|
||||
$self->wxWindow->SetValue($value);
|
||||
}
|
||||
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub set_values {
|
||||
my ($self, $values) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->wxWindow->SetSelection($idx);
|
||||
|
||||
# it looks that Clear() also clears the text field in recent wxWidgets versions,
|
||||
# but we want to preserve it
|
||||
my $ww = $self->wxWindow;
|
||||
my $value = $ww->GetValue;
|
||||
$ww->Clear;
|
||||
$ww->Append($_) for @$values;
|
||||
$ww->SetValue($value);
|
||||
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->option->values->[$self->wxWindow->GetSelection];
|
||||
|
||||
if ($self->option->values) {
|
||||
my $idx = $self->wxWindow->GetSelection;
|
||||
if ($idx != &Wx::wxNOT_FOUND) {
|
||||
return $self->option->values->[$idx];
|
||||
}
|
||||
}
|
||||
return $self->wxWindow->GetValue;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::NumericChoice;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:misc :combobox);
|
||||
use Wx qw(wxTheApp :misc :combobox);
|
||||
use Wx::Event qw(EVT_COMBOBOX EVT_TEXT);
|
||||
|
||||
# if option has no 'values', indices are values
|
||||
# if option has no 'labels', values are labels
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
@@ -245,18 +315,28 @@ sub BUILD {
|
||||
my $disable_change_event = $self->disable_change_event;
|
||||
$self->disable_change_event(1);
|
||||
|
||||
my $value = $field->GetSelection;
|
||||
my $idx = $field->GetSelection; # get index of selected value
|
||||
my $label;
|
||||
|
||||
if ($self->option->values) {
|
||||
$label = $value = $self->option->values->[$value];
|
||||
} elsif ($value <= $#{$self->option->labels}) {
|
||||
$label = $self->option->labels->[$value];
|
||||
if ($self->option->labels && $idx <= $#{$self->option->labels}) {
|
||||
$label = $self->option->labels->[$idx];
|
||||
} elsif ($self->option->values && $idx <= $#{$self->option->values}) {
|
||||
$label = $self->option->values->[$idx];
|
||||
} else {
|
||||
$label = $value;
|
||||
$label = $idx;
|
||||
}
|
||||
|
||||
$field->SetValue($label);
|
||||
# The MSW implementation of wxComboBox will leave the field blank if we call
|
||||
# SetValue() in the EVT_COMBOBOX event handler, so we postpone the call.
|
||||
wxTheApp->CallAfter(sub {
|
||||
my $dce = $self->disable_change_event;
|
||||
$self->disable_change_event(1);
|
||||
|
||||
# ChangeValue() is not exported in wxPerl
|
||||
$field->SetValue($label);
|
||||
|
||||
$self->disable_change_event($dce);
|
||||
});
|
||||
|
||||
$self->disable_change_event($disable_change_event);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
@@ -283,8 +363,8 @@ sub set_value {
|
||||
$self->disable_change_event(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($self->option->labels && $value <= $#{$self->option->labels}) {
|
||||
} elsif ($self->option->labels && $value <= $#{$self->option->labels}) {
|
||||
# if we have no values, we expect value to be an index
|
||||
$field->SetValue($self->option->labels->[$value]);
|
||||
$self->disable_change_event(0);
|
||||
return;
|
||||
@@ -299,17 +379,62 @@ sub get_value {
|
||||
my ($self) = @_;
|
||||
|
||||
my $label = $self->wxWindow->GetValue;
|
||||
my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
|
||||
if (defined $value_idx) {
|
||||
if ($self->option->values) {
|
||||
return $self->option->values->[$value_idx];
|
||||
if ($self->option->labels) {
|
||||
my $value_idx = first { $self->option->labels->[$_] eq $label } 0..$#{$self->option->labels};
|
||||
if (defined $value_idx) {
|
||||
if ($self->option->values) {
|
||||
return $self->option->values->[$value_idx];
|
||||
}
|
||||
return $value_idx;
|
||||
}
|
||||
return $value_idx;
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::ColourPicker;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field::wxWindow';
|
||||
|
||||
use Wx qw(:misc :colour);
|
||||
use Wx::Event qw(EVT_COLOURPICKER_CHANGED);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
my $field = Wx::ColourPickerCtrl->new($self->parent, -1,
|
||||
$self->_string_to_colour($self->option->default), wxDefaultPosition,
|
||||
$self->_default_size);
|
||||
$self->wxWindow($field);
|
||||
|
||||
EVT_COLOURPICKER_CHANGED($self->parent, $field, sub {
|
||||
$self->_on_change($self->option->opt_id);
|
||||
});
|
||||
}
|
||||
|
||||
sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->wxWindow->SetColour($self->_string_to_colour($value));
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
sub get_value {
|
||||
my ($self) = @_;
|
||||
return $self->wxWindow->GetColour->GetAsString(wxC2S_HTML_SYNTAX);
|
||||
}
|
||||
|
||||
sub _string_to_colour {
|
||||
my ($self, $string) = @_;
|
||||
|
||||
$string =~ s/^#//;
|
||||
# If the color is in an invalid format, set it to white.
|
||||
$string = 'FFFFFF' if ($string !~ m/^[[:xdigit:]]{6}/);
|
||||
return Wx::Colour->new(unpack 'C*', pack 'H*', $string);
|
||||
}
|
||||
|
||||
|
||||
package Slic3r::GUI::OptionsGroup::Field::wxSizer;
|
||||
use Moo;
|
||||
extends 'Slic3r::GUI::OptionsGroup::Field';
|
||||
@@ -335,6 +460,7 @@ sub BUILD {
|
||||
$self->wxSizer($sizer);
|
||||
|
||||
my $field_size = Wx::Size->new(40, -1);
|
||||
|
||||
$self->x_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[X], wxDefaultPosition, $field_size));
|
||||
$self->y_textctrl(Wx::TextCtrl->new($self->parent, -1, $self->option->default->[Y], wxDefaultPosition, $field_size));
|
||||
|
||||
@@ -397,11 +523,10 @@ extends 'Slic3r::GUI::OptionsGroup::Field::wxSizer';
|
||||
|
||||
has 'scale' => (is => 'rw', default => sub { 10 });
|
||||
has 'slider' => (is => 'rw');
|
||||
has 'statictext' => (is => 'rw');
|
||||
has 'textctrl' => (is => 'rw');
|
||||
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
use Wx qw(:misc :sizer);
|
||||
use Wx::Event qw(EVT_SLIDER);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_TEXT EVT_KILL_FOCUS);
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
@@ -419,14 +544,30 @@ sub BUILD {
|
||||
);
|
||||
$self->slider($slider);
|
||||
|
||||
my $statictext = Wx::StaticText->new($self->parent, -1, $slider->GetValue/$self->scale);
|
||||
$self->statictext($statictext);
|
||||
my $textctrl = Wx::TextCtrl->new($self->parent, -1, $slider->GetValue/$self->scale,
|
||||
wxDefaultPosition, [50,-1]);
|
||||
$self->textctrl($textctrl);
|
||||
|
||||
$sizer->Add($_, 0, wxALIGN_CENTER_VERTICAL, 0) for $slider, $statictext;
|
||||
$sizer->Add($slider, 1, wxALIGN_CENTER_VERTICAL, 0);
|
||||
$sizer->Add($textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
|
||||
|
||||
EVT_SLIDER($self->parent, $slider, sub {
|
||||
$self->_update_statictext;
|
||||
$self->_on_change($self->option->opt_id);
|
||||
if (! $self->disable_change_event) {
|
||||
$self->textctrl->SetLabel($self->get_value);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
}
|
||||
});
|
||||
EVT_TEXT($self->parent, $textctrl, sub {
|
||||
my $value = $textctrl->GetValue;
|
||||
if ($value =~ /^-?\d+(\.\d*)?$/) {
|
||||
$self->disable_change_event(1);
|
||||
$self->slider->SetValue($value*$self->scale);
|
||||
$self->disable_change_event(0);
|
||||
$self->_on_change($self->option->opt_id);
|
||||
}
|
||||
});
|
||||
EVT_KILL_FOCUS($textctrl, sub {
|
||||
$self->_on_kill_focus($self->option->opt_id, @_);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,8 +575,8 @@ sub set_value {
|
||||
my ($self, $value) = @_;
|
||||
|
||||
$self->disable_change_event(1);
|
||||
$self->slider->SetValue($value);
|
||||
$self->_update_statictext;
|
||||
$self->slider->SetValue($value*$self->scale);
|
||||
$self->textctrl->SetLabel($self->get_value);
|
||||
$self->disable_change_event(0);
|
||||
}
|
||||
|
||||
@@ -444,9 +585,20 @@ sub get_value {
|
||||
return $self->slider->GetValue/$self->scale;
|
||||
}
|
||||
|
||||
sub _update_statictext {
|
||||
sub enable {
|
||||
my ($self) = @_;
|
||||
$self->statictext->SetLabel($self->get_value);
|
||||
|
||||
$self->slider->Enable;
|
||||
$self->textctrl->Enable;
|
||||
$self->textctrl->SetEditable(1);
|
||||
}
|
||||
|
||||
sub disable {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->slider->Disable;
|
||||
$self->textctrl->Disable;
|
||||
$self->textctrl->SetEditable(0);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,6 @@
|
||||
# 2D preview on the platter.
|
||||
# 3D objects are visualized by their convex hulls.
|
||||
|
||||
package Slic3r::GUI::Plater::2D;
|
||||
use strict;
|
||||
use warnings;
|
||||
@@ -6,28 +9,27 @@ use utf8;
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::Geometry qw(X Y scale unscale convex_hull);
|
||||
use Slic3r::Geometry::Clipper qw(offset JT_ROUND intersection_pl);
|
||||
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_SIZE);
|
||||
use Wx qw(wxTheApp :misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_MOUSE_EVENTS EVT_PAINT EVT_ERASE_BACKGROUND EVT_SIZE);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8'
|
||||
? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
|
||||
: 'Drag your objects here';
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $size, $objects, $model, $config) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, $size, wxTAB_TRAVERSAL);
|
||||
# This has only effect on MacOS. On Windows and Linux/GTK, the background is painted by $self->repaint().
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
|
||||
|
||||
$self->{objects} = $objects;
|
||||
$self->{model} = $model;
|
||||
$self->{config} = $config;
|
||||
$self->{on_select_object} = sub {};
|
||||
$self->{on_double_click} = sub {};
|
||||
$self->{on_right_click} = sub {};
|
||||
$self->{on_instance_moved} = sub {};
|
||||
$self->{on_instances_moved} = sub {};
|
||||
|
||||
$self->{objects_brush} = Wx::Brush->new(Wx::Colour->new(210,210,210), wxSOLID);
|
||||
$self->{selected_brush} = Wx::Brush->new(Wx::Colour->new(255,128,128), wxSOLID);
|
||||
@@ -37,8 +39,11 @@ sub new {
|
||||
$self->{print_center_pen} = Wx::Pen->new(Wx::Colour->new(200,200,200), 1, wxSOLID);
|
||||
$self->{clearance_pen} = Wx::Pen->new(Wx::Colour->new(0,0,200), 1, wxSOLID);
|
||||
$self->{skirt_pen} = Wx::Pen->new(Wx::Colour->new(150,150,150), 1, wxSOLID);
|
||||
|
||||
$self->{user_drawn_background} = $^O ne 'darwin';
|
||||
|
||||
EVT_PAINT($self, \&repaint);
|
||||
EVT_ERASE_BACKGROUND($self, sub {}) if $self->{user_drawn_background};
|
||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||
EVT_SIZE($self, sub {
|
||||
$self->update_bed_size;
|
||||
@@ -63,18 +68,30 @@ sub on_right_click {
|
||||
$self->{on_right_click} = $cb;
|
||||
}
|
||||
|
||||
sub on_instance_moved {
|
||||
sub on_instances_moved {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_instance_moved} = $cb;
|
||||
$self->{on_instances_moved} = $cb;
|
||||
}
|
||||
|
||||
sub repaint {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
my $dc = Wx::AutoBufferedPaintDC->new($self);
|
||||
my $size = $self->GetSize;
|
||||
my @size = ($size->GetWidth, $size->GetHeight);
|
||||
|
||||
|
||||
if ($self->{user_drawn_background}) {
|
||||
# On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
# On MacOS the background is erased, on Windows the background is not erased
|
||||
# and on Linux/GTK the background is erased to gray color.
|
||||
# Fill DC with the background on Windows & Linux/GTK.
|
||||
my $brush_background = Wx::Brush->new(Wx::wxWHITE, wxSOLID);
|
||||
$dc->SetPen(wxWHITE_PEN);
|
||||
$dc->SetBrush($brush_background);
|
||||
my $rect = $self->GetUpdateRegion()->GetBox();
|
||||
$dc->DrawRectangle($rect->GetLeft(), $rect->GetTop(), $rect->GetWidth(), $rect->GetHeight());
|
||||
}
|
||||
|
||||
# draw grid
|
||||
$dc->SetPen($self->{grid_pen});
|
||||
$dc->DrawLine(map @$_, @$_) for @{$self->{grid}};
|
||||
@@ -87,7 +104,7 @@ sub repaint {
|
||||
}
|
||||
|
||||
# draw print center
|
||||
if (@{$self->{objects}} && $Slic3r::GUI::Settings->{_}{autocenter}) {
|
||||
if (@{$self->{objects}} && wxTheApp->{app_config}->get("autocenter")) {
|
||||
my $center = $self->unscaled_point_to_pixel($self->{print_center});
|
||||
$dc->SetPen($self->{print_center_pen});
|
||||
$dc->DrawLine($center->[X], 0, $center->[X], $size[Y]);
|
||||
@@ -109,7 +126,11 @@ sub repaint {
|
||||
if (!@{$self->{objects}}) {
|
||||
$dc->SetTextForeground(Wx::Colour->new(150,50,50));
|
||||
$dc->SetFont(Wx::Font->new(14, wxDEFAULT, wxNORMAL, wxNORMAL));
|
||||
$dc->DrawLabel(CANVAS_TEXT, Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
$dc->DrawLabel(
|
||||
join('-', +(localtime)[3,4]) eq '13-8'
|
||||
? L('What do you want to print today? ™') # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap.
|
||||
: L('Drag your objects here'),
|
||||
Wx::Rect->new(0, 0, $self->GetSize->GetWidth, $self->GetSize->GetHeight), wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
|
||||
# draw thumbnails
|
||||
@@ -154,7 +175,7 @@ sub repaint {
|
||||
|
||||
# if sequential printing is enabled and we have more than one object, draw clearance area
|
||||
if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) {
|
||||
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))};
|
||||
my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{clearance_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($clearance, 1), 0, 0);
|
||||
@@ -166,7 +187,7 @@ sub repaint {
|
||||
if (@{$self->{objects}} && $self->{config}->skirts) {
|
||||
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}};
|
||||
if (@points >= 3) {
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))};
|
||||
my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), JT_ROUND, scale(0.1))};
|
||||
$dc->SetPen($self->{skirt_pen});
|
||||
$dc->SetBrush($self->{transparent_brush});
|
||||
$dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0);
|
||||
@@ -178,14 +199,15 @@ sub repaint {
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
my $pos = $event->GetPosition;
|
||||
my $point = $self->point_to_model_units([ $pos->x, $pos->y ]); #]]
|
||||
if ($event->ButtonDown) {
|
||||
$self->{on_select_object}->(undef);
|
||||
OBJECTS: for my $obj_idx (0 .. $#{$self->{objects}}) {
|
||||
# traverse objects and instances in reverse order, so that if they're overlapping
|
||||
# we get the one that gets drawn last, thus on top (as user expects that to move)
|
||||
OBJECTS: for my $obj_idx (reverse 0 .. $#{$self->{objects}}) {
|
||||
my $object = $self->{objects}->[$obj_idx];
|
||||
for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) {
|
||||
for my $instance_idx (reverse 0 .. $#{ $object->instance_thumbnails }) {
|
||||
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
|
||||
if (defined first { $_->contour->contains_point($point) } @$thumbnail) {
|
||||
$self->{on_select_object}->($obj_idx);
|
||||
@@ -200,7 +222,7 @@ sub mouse_event {
|
||||
];
|
||||
$self->{drag_object} = [ $obj_idx, $instance_idx ];
|
||||
} elsif ($event->RightDown) {
|
||||
$self->{on_right_click}->($pos);
|
||||
$self->{on_right_click}->($pos->x, $pos->y);
|
||||
}
|
||||
|
||||
last OBJECTS;
|
||||
@@ -208,13 +230,13 @@ sub mouse_event {
|
||||
}
|
||||
}
|
||||
$self->Refresh;
|
||||
} elsif ($event->ButtonUp(&Wx::wxMOUSE_BTN_LEFT)) {
|
||||
$self->{on_instance_moved}->();
|
||||
$self->Refresh;
|
||||
} elsif ($event->LeftUp) {
|
||||
$self->{on_instances_moved}->()
|
||||
if $self->{drag_object};
|
||||
$self->{drag_start_pos} = undef;
|
||||
$self->{drag_object} = undef;
|
||||
$self->SetCursor(wxSTANDARD_CURSOR);
|
||||
} elsif ($event->ButtonDClick) {
|
||||
} elsif ($event->LeftDClick) {
|
||||
$self->{on_double_click}->();
|
||||
} elsif ($event->Dragging) {
|
||||
return if !$self->{drag_start_pos}; # concurrency problems
|
||||
@@ -225,7 +247,6 @@ sub mouse_event {
|
||||
unscale($point->[X] - $self->{drag_start_pos}[X]),
|
||||
unscale($point->[Y] - $self->{drag_start_pos}[Y]),
|
||||
));
|
||||
$model_object->update_bounding_box;
|
||||
$self->Refresh;
|
||||
} elsif ($event->Moving) {
|
||||
my $cursor = wxSTANDARD_CURSOR;
|
||||
@@ -237,7 +258,7 @@ sub mouse_event {
|
||||
}
|
||||
|
||||
sub update_bed_size {
|
||||
my $self = shift;
|
||||
my ($self) = @_;
|
||||
|
||||
# when the canvas is not rendered yet, its GetSize() method returns 0,0
|
||||
my $canvas_size = $self->GetSize;
|
||||
@@ -274,7 +295,7 @@ sub update_bed_size {
|
||||
push @polylines, Slic3r::Polyline->new([$bb->x_min, $y], [$bb->x_max, $y]);
|
||||
}
|
||||
@polylines = @{intersection_pl(\@polylines, [$polygon])};
|
||||
$self->{grid} = [ map $self->scaled_points_to_pixel(\@$_, 1), @polylines ];
|
||||
$self->{grid} = [ map $self->scaled_points_to_pixel([ @$_[0,-1] ], 1), @polylines ];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,4 +340,32 @@ sub point_to_model_units {
|
||||
);
|
||||
}
|
||||
|
||||
sub reload_scene {
|
||||
my ($self, $force) = @_;
|
||||
|
||||
if (! $self->IsShown && ! $force) {
|
||||
$self->{reload_delayed} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{reload_delayed} = 0;
|
||||
|
||||
foreach my $obj_idx (0..$#{$self->{model}->objects}) {
|
||||
my $plater_object = $self->{objects}[$obj_idx];
|
||||
next if $plater_object->thumbnail;
|
||||
# The thumbnail is not valid, update it with a convex hull of an object.
|
||||
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
|
||||
$plater_object->make_thumbnail($self->{model}, $obj_idx);
|
||||
$plater_object->transform_thumbnail($self->{model}, $obj_idx);
|
||||
}
|
||||
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
# Called by the Platter wxNotebook when this page is activated.
|
||||
sub OnActivate {
|
||||
my ($self) = @_;
|
||||
$self->reload_scene(1) if ($self->{reload_delayed});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# 2D preview of the tool paths of a single layer, using a thin line.
|
||||
# OpenGL is used to render the paths.
|
||||
# Vojtech also added a 2D simulation of under/over extrusion in a single layer.
|
||||
|
||||
package Slic3r::GUI::Plater::2DToolpaths;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw();
|
||||
use Slic3r::Geometry qw();
|
||||
use Wx qw(:misc :sizer :slider :statictext);
|
||||
use Slic3r::Print::State ':steps';
|
||||
use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxWANTS_CHARS);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN);
|
||||
use base 'Wx::Panel';
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(print enabled));
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $print) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
|
||||
|
||||
# init print
|
||||
$self->{print} = $print;
|
||||
$self->reload_print;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
|
||||
$self->SetBackgroundColour(wxWHITE);
|
||||
|
||||
# init GUI elements
|
||||
my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print);
|
||||
@@ -25,7 +27,9 @@ sub new {
|
||||
$self, -1,
|
||||
0, # default
|
||||
0, # min
|
||||
scalar(@{$self->{layers_z}})-1, # max
|
||||
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
|
||||
# will skip drawing the slider if max <= min:
|
||||
1, # max
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxVERTICAL | wxSL_INVERSE,
|
||||
@@ -43,26 +47,39 @@ sub new {
|
||||
$sizer->Add($vsizer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
EVT_SLIDER($self, $slider, sub {
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue])
|
||||
if $self->enabled;
|
||||
});
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($s, $event) = @_;
|
||||
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($key == 85 || $key == 315) {
|
||||
$slider->SetValue($slider->GetValue + 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key == 68 || $key == 317) {
|
||||
$slider->SetValue($slider->GetValue - 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
if ($event->HasModifiers) {
|
||||
$event->Skip;
|
||||
} else {
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($key == ord('D') || $key == WXK_LEFT) {
|
||||
# Keys: 'D' or WXK_LEFT
|
||||
$slider->SetValue($slider->GetValue - 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key == ord('U') || $key == WXK_RIGHT) {
|
||||
# Keys: 'U' or WXK_RIGHT
|
||||
$slider->SetValue($slider->GetValue + 1);
|
||||
$self->set_z($self->{layers_z}[$slider->GetValue]);
|
||||
} elsif ($key >= ord('1') && $key <= ord('3')) {
|
||||
# Keys: '1' to '3'
|
||||
$canvas->set_simulation_mode($key - ord('1'));
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
$self->set_z($self->{layers_z}[0]);
|
||||
# init print
|
||||
$self->{print} = $print;
|
||||
$self->reload_print;
|
||||
|
||||
return $self;
|
||||
}
|
||||
@@ -70,18 +87,42 @@ sub new {
|
||||
sub reload_print {
|
||||
my ($self) = @_;
|
||||
|
||||
# we require that there's at least one object and the posSlice step
|
||||
# is performed on all of them (this ensures that _shifted_copies was
|
||||
# populated and we know the number of layers)
|
||||
if (!$self->print->object_step_done(STEP_SLICE)) {
|
||||
$self->enabled(0);
|
||||
$self->{slider}->Hide;
|
||||
$self->{canvas}->Refresh; # clears canvas
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{canvas}->bb($self->print->total_bounding_box);
|
||||
$self->{canvas}->_dirty(1);
|
||||
|
||||
my %z = (); # z => 1
|
||||
foreach my $object (@{$self->{print}->objects}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
$z{$layer->print_z} = 1;
|
||||
}
|
||||
}
|
||||
$self->enabled(1);
|
||||
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
|
||||
$self->{slider}->SetRange(0, scalar(@{$self->{layers_z}})-1);
|
||||
if ((my $z_idx = $self->{slider}->GetValue) <= $#{$self->{layers_z}}) {
|
||||
$self->set_z($self->{layers_z}[$z_idx]);
|
||||
} else {
|
||||
$self->{slider}->SetValue(0);
|
||||
$self->set_z($self->{layers_z}[0]) if @{$self->{layers_z}};
|
||||
}
|
||||
$self->{slider}->Show;
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub set_z {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
return if !$self->enabled;
|
||||
$self->{z_label}->SetLabel(sprintf '%.2f', $z);
|
||||
$self->{canvas}->set_z($z);
|
||||
}
|
||||
@@ -89,14 +130,27 @@ sub set_z {
|
||||
|
||||
package Slic3r::GUI::Plater::2DToolpaths::Canvas;
|
||||
|
||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
||||
use OpenGL qw(:glconstants :glfunctions :glufunctions);
|
||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
||||
use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants);
|
||||
use base qw(Wx::GLCanvas Class::Accessor);
|
||||
use Wx::GLCanvas qw(:all);
|
||||
use List::Util qw(min first);
|
||||
use Slic3r::Geometry qw(scale unscale epsilon);
|
||||
use List::Util qw(min max first);
|
||||
use Slic3r::Geometry qw(scale epsilon X Y);
|
||||
use Slic3r::Print::State ':steps';
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(print z layers color init dirty bb));
|
||||
__PACKAGE__->mk_accessors(qw(
|
||||
print z layers color init
|
||||
bb
|
||||
_camera_bb
|
||||
_dirty
|
||||
_zoom
|
||||
_camera_target
|
||||
_drag_start_xy
|
||||
_texture_name
|
||||
_texture_size
|
||||
_extrusion_simulator
|
||||
_simulation_mode
|
||||
));
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
{
|
||||
@@ -107,25 +161,132 @@ __PACKAGE__->mk_accessors(qw(print z layers color init dirty bb));
|
||||
sub new {
|
||||
my ($class, $parent, $print) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent);
|
||||
my $self = (Wx::wxVERSION >= 3.000003) ?
|
||||
# The wxWidgets 3.0.3-beta have a bug, they crash with NULL attribute list.
|
||||
$class->SUPER::new($parent, -1, Wx::wxDefaultPosition, Wx::wxDefaultSize, 0, "",
|
||||
[WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, 0]) :
|
||||
$class->SUPER::new($parent);
|
||||
# Immediatelly force creation of the OpenGL context to consume the static variable s_wglContextAttribs.
|
||||
$self->GetContext();
|
||||
$self->print($print);
|
||||
$self->bb($self->print->total_bounding_box);
|
||||
|
||||
$self->_zoom(1);
|
||||
|
||||
# 2D point in model space
|
||||
$self->_camera_target(Slic3r::Pointf->new(0,0));
|
||||
|
||||
# Texture for the extrusion simulator. The texture will be allocated / reallocated on Resize.
|
||||
$self->_texture_name(0);
|
||||
$self->_texture_size(Slic3r::Point->new(0,0));
|
||||
$self->_extrusion_simulator(Slic3r::ExtrusionSimulator->new());
|
||||
$self->_simulation_mode(0);
|
||||
|
||||
EVT_PAINT($self, sub {
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
$self->Render($dc);
|
||||
});
|
||||
EVT_SIZE($self, sub { $self->dirty(1) });
|
||||
EVT_SIZE($self, sub { $self->_dirty(1) });
|
||||
EVT_IDLE($self, sub {
|
||||
return unless $self->dirty;
|
||||
return unless $self->_dirty;
|
||||
return if !$self->IsShownOnScreen;
|
||||
$self->Resize( $self->GetSizeWH );
|
||||
$self->Resize;
|
||||
$self->Refresh;
|
||||
});
|
||||
EVT_MOUSEWHEEL($self, sub {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $old_zoom = $self->_zoom;
|
||||
|
||||
# Calculate the zoom delta and apply it to the current zoom factor
|
||||
my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta();
|
||||
$zoom = max(min($zoom, 4), -4);
|
||||
$zoom /= 10;
|
||||
$self->_zoom($self->_zoom / (1-$zoom));
|
||||
$self->_zoom(1) if $self->_zoom > 1; # prevent from zooming out too much
|
||||
|
||||
{
|
||||
# In order to zoom around the mouse point we need to translate
|
||||
# the camera target. This math is almost there but not perfect yet...
|
||||
my $camera_bb_size = $self->_camera_bb->size;
|
||||
my $size = Slic3r::Pointf->new($self->GetSizeWH);
|
||||
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
|
||||
|
||||
# calculate the zooming center in pixel coordinates relative to the viewport center
|
||||
my $vec = Slic3r::Pointf->new($pos->x - $size->x/2, $pos->y - $size->y/2); #-
|
||||
|
||||
# calculate where this point will end up after applying the new zoom
|
||||
my $vec2 = $vec->clone;
|
||||
$vec2->scale($old_zoom / $self->_zoom);
|
||||
|
||||
# move the camera target by the difference of the two positions
|
||||
$self->_camera_target->translate(
|
||||
-($vec->x - $vec2->x) * $camera_bb_size->x / $size->x,
|
||||
($vec->y - $vec2->y) * $camera_bb_size->y / $size->y, #//
|
||||
);
|
||||
}
|
||||
|
||||
$self->_dirty(1);
|
||||
$self->Refresh;
|
||||
});
|
||||
EVT_MOUSE_EVENTS($self, \&mouse_event);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
|
||||
# Deallocate the OpenGL resources.
|
||||
my $context = $self->GetContext;
|
||||
if ($context and $self->texture_id) {
|
||||
$self->SetCurrent($context);
|
||||
glDeleteTextures(1, ($self->texture_id));
|
||||
$self->SetCurrent(0);
|
||||
$self->texture_id(0);
|
||||
$self->texture_size(new Slic3r::Point(0, 0));
|
||||
}
|
||||
return $self->SUPER::Destroy;
|
||||
}
|
||||
|
||||
sub mouse_event {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
return if !$self->GetParent->enabled;
|
||||
|
||||
my $pos = Slic3r::Pointf->new($e->GetPositionXY);
|
||||
if ($e->Entering && &Wx::wxMSW) {
|
||||
# wxMSW needs focus in order to catch mouse wheel events
|
||||
$self->SetFocus;
|
||||
} elsif ($e->Dragging) {
|
||||
if ($e->LeftIsDown || $e->MiddleIsDown || $e->RightIsDown) {
|
||||
# if dragging, translate view
|
||||
|
||||
if (defined $self->_drag_start_xy) {
|
||||
my $move = $self->_drag_start_xy->vector_to($pos); # in pixels
|
||||
|
||||
# get viewport and camera size in order to convert pixel to model units
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
my $camera_bb_size = $self->_camera_bb->size;
|
||||
|
||||
# compute translation in model units
|
||||
$self->_camera_target->translate(
|
||||
-$move->x * $camera_bb_size->x / $x,
|
||||
$move->y * $camera_bb_size->y / $y, # /**
|
||||
);
|
||||
|
||||
$self->_dirty(1);
|
||||
$self->Refresh;
|
||||
}
|
||||
$self->_drag_start_xy($pos);
|
||||
}
|
||||
} elsif ($e->LeftUp || $e->MiddleUp || $e->RightUp) {
|
||||
$self->_drag_start_xy(undef);
|
||||
} else {
|
||||
$e->Skip();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_z {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
@@ -150,9 +311,18 @@ sub set_z {
|
||||
}
|
||||
}
|
||||
|
||||
# reverse layers so that we draw the lowermost (i.e. current) on top
|
||||
$self->z($z);
|
||||
$self->layers([ @layers ]);
|
||||
$self->dirty(1);
|
||||
$self->layers([ reverse @layers ]);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
sub set_simulation_mode
|
||||
{
|
||||
my ($self, $mode) = @_;
|
||||
$self->_simulation_mode($mode);
|
||||
$self->_dirty(1);
|
||||
$self->Refresh;
|
||||
}
|
||||
|
||||
sub Render {
|
||||
@@ -164,28 +334,115 @@ sub Render {
|
||||
$self->SetCurrent($context);
|
||||
$self->InitGL;
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
my $bb = $self->bb;
|
||||
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
|
||||
# adjust Y
|
||||
my $new_y = $y * ($x2 - $x1) / $x;
|
||||
$y1 = ($y2 + $y1)/2 - $new_y/2;
|
||||
$y2 = $y1 + $new_y;
|
||||
} else {
|
||||
my $new_x = $x * ($y2 - $y1) / $y;
|
||||
$x1 = ($x2 + $x1)/2 - $new_x/2;
|
||||
$x2 = $x1 + $new_x;
|
||||
glClearColor(1, 1, 1, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (!$self->GetParent->enabled || !$self->layers) {
|
||||
$self->SwapBuffers;
|
||||
return;
|
||||
}
|
||||
glOrtho($x1, $x2, $y1, $y2, 0, 1);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
|
||||
if ($self->_simulation_mode and $self->_texture_name and $self->_texture_size->x() > 0 and $self->_texture_size->y() > 0) {
|
||||
$self->_simulate_extrusion();
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_REPLACE);
|
||||
glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexImage2D_c(GL_TEXTURE_2D,
|
||||
0, # level (0 normal, heighr is form mip-mapping)
|
||||
GL_RGBA, # internal format
|
||||
$self->_texture_size->x(), $self->_texture_size->y(),
|
||||
0, # border
|
||||
GL_RGBA, # format RGBA color data
|
||||
GL_UNSIGNED_BYTE, # unsigned byte data
|
||||
$self->_extrusion_simulator->image_ptr()); # ptr to texture data
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
glOrtho(0, 1, 0, 1, 0, 1);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0, 0);
|
||||
glVertex2f(0, 0);
|
||||
glTexCoord2f($x/$self->_texture_size->x(), 0);
|
||||
glVertex2f(1, 0);
|
||||
glTexCoord2f($x/$self->_texture_size->x(), $y/$self->_texture_size->y());
|
||||
glVertex2f(1, 1);
|
||||
glTexCoord2f(0, $y/$self->_texture_size->y());
|
||||
glVertex2f(0, 1);
|
||||
glEnd();
|
||||
glPopMatrix();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
# anti-alias
|
||||
if (0) {
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
|
||||
glHint(GL_POLYGON_SMOOTH_HINT, GL_DONT_CARE);
|
||||
}
|
||||
|
||||
glClearColor(1, 1, 1, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
# Tesselator triangulates polygons with holes on the fly for the rendering purposes only.
|
||||
my $tess;
|
||||
if ($self->_simulation_mode() == 0 and !(&Wx::wxMSW && $OpenGL::VERSION < 0.6704)) {
|
||||
# We can't use the GLU tesselator on MSW with older OpenGL versions
|
||||
# because of an upstream bug:
|
||||
# http://sourceforge.net/p/pogl/bugs/16/
|
||||
$tess = gluNewTess();
|
||||
gluTessCallback($tess, GLU_TESS_BEGIN, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_END, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_VERTEX, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_COMBINE, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_ERROR, 'DEFAULT');
|
||||
gluTessCallback($tess, GLU_TESS_EDGE_FLAG, 'DEFAULT');
|
||||
}
|
||||
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
my $object = $layer->object;
|
||||
|
||||
# only draw the slice for the current layer
|
||||
next unless abs($layer->print_z - $self->z) < epsilon;
|
||||
|
||||
# draw slice contour
|
||||
glLineWidth(1);
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
glPushMatrix();
|
||||
glTranslatef(@$copy, 0);
|
||||
|
||||
foreach my $slice (@{$layer->slices}) {
|
||||
glColor3f(0.95, 0.95, 0.95);
|
||||
|
||||
if ($tess) {
|
||||
gluTessBeginPolygon($tess);
|
||||
foreach my $polygon (@$slice) {
|
||||
gluTessBeginContour($tess);
|
||||
gluTessVertex_p($tess, @$_, 0) for @$polygon;
|
||||
gluTessEndContour($tess);
|
||||
}
|
||||
gluTessEndPolygon($tess);
|
||||
}
|
||||
|
||||
glColor3f(0.9, 0.9, 0.9);
|
||||
foreach my $polygon (@$slice) {
|
||||
foreach my $line (@{$polygon->lines}) {
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
my $skirt_drawn = 0;
|
||||
my $brim_drawn = 0;
|
||||
@@ -194,40 +451,47 @@ sub Render {
|
||||
my $print_z = $layer->print_z;
|
||||
|
||||
# draw brim
|
||||
if ($layer->id == 0 && !$brim_drawn) {
|
||||
if ($self->print->step_done(STEP_BRIM) && $layer->id == 0 && !$brim_drawn) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw(undef, $print_z, $_) for @{$self->print->brim};
|
||||
$brim_drawn = 1;
|
||||
}
|
||||
if (($self->print->config->skirt_height == -1 || $self->print->config->skirt_height >= $layer->id) && !$skirt_drawn) {
|
||||
if ($self->print->step_done(STEP_SKIRT)
|
||||
&& ($self->print->has_infinite_skirt() || $self->print->config->skirt_height > $layer->id)
|
||||
&& !$skirt_drawn) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw(undef, $print_z, $_) for @{$self->print->skirt};
|
||||
$skirt_drawn = 1;
|
||||
}
|
||||
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
$self->color([0.7, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for @{$layerm->perimeters};
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
$self->color([0.7, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->perimeters};
|
||||
}
|
||||
|
||||
$self->color([0, 0, 0.7]);
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
$self->color([0, 0, 0.7]);
|
||||
$self->_draw($object, $print_z, $_) for map @$_, @{$layerm->fills};
|
||||
}
|
||||
}
|
||||
|
||||
if ($layer->isa('Slic3r::Layer::Support')) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
|
||||
$self->_draw($object, $print_z, $_) for @{$layer->support_interface_fills};
|
||||
if ($object->step_done(STEP_SUPPORTMATERIAL)) {
|
||||
if ($layer->isa('Slic3r::Layer::Support')) {
|
||||
$self->color([0, 0, 0]);
|
||||
$self->_draw($object, $print_z, $_) for @{$layer->support_fills};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glFlush();
|
||||
gluDeleteTess($tess) if $tess;
|
||||
$self->SwapBuffers;
|
||||
}
|
||||
|
||||
sub _draw {
|
||||
my ($self, $object, $print_z, $path) = @_;
|
||||
|
||||
my @paths = $path->isa('Slic3r::ExtrusionLoop')
|
||||
my @paths = ($path->isa('Slic3r::ExtrusionLoop') || $path->isa('Slic3r::ExtrusionMultiPath'))
|
||||
? @$path
|
||||
: ($path);
|
||||
|
||||
@@ -249,13 +513,15 @@ sub _draw_path {
|
||||
|
||||
if (defined $object) {
|
||||
foreach my $copy (@{ $object->_shifted_copies }) {
|
||||
glPushMatrix();
|
||||
glTranslatef(@$copy, 0);
|
||||
foreach my $line (@{$path->polyline->lines}) {
|
||||
$line->translate(@$copy);
|
||||
glBegin(GL_LINES);
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
} else {
|
||||
foreach my $line (@{$path->polyline->lines}) {
|
||||
@@ -263,8 +529,42 @@ sub _draw_path {
|
||||
glVertex2f(@{$line->a});
|
||||
glVertex2f(@{$line->b});
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub _simulate_extrusion {
|
||||
my ($self) = @_;
|
||||
$self->_extrusion_simulator->reset_accumulator();
|
||||
foreach my $layer (@{$self->layers}) {
|
||||
if (abs($layer->print_z - $self->z) < epsilon) {
|
||||
my $object = $layer->object;
|
||||
my @shifts = (defined $object) ? @{$object->_shifted_copies} : (Slic3r::Point->new(0, 0));
|
||||
foreach my $layerm (@{$layer->regions}) {
|
||||
my @extrusions = ();
|
||||
if ($object->step_done(STEP_PERIMETERS)) {
|
||||
push @extrusions, @$_ for @{$layerm->perimeters};
|
||||
}
|
||||
if ($object->step_done(STEP_INFILL)) {
|
||||
push @extrusions, @$_ for @{$layerm->fills};
|
||||
}
|
||||
foreach my $extrusion_entity (@extrusions) {
|
||||
my @paths = ($extrusion_entity->isa('Slic3r::ExtrusionLoop') || $extrusion_entity->isa('Slic3r::ExtrusionMultiPath'))
|
||||
? @$extrusion_entity
|
||||
: ($extrusion_entity);
|
||||
foreach my $path (@paths) {
|
||||
print "width: ", $path->width,
|
||||
" height: ", $path->height,
|
||||
" mm3_per_mm: ", $path->mm3_per_mm,
|
||||
" height2: ", $path->mm3_per_mm / $path->height,
|
||||
"\n";
|
||||
$self->_extrusion_simulator->extrude_to_accumulator($path, $_, $self->_simulation_mode()) for @shifts;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->_extrusion_simulator->evaluate_accumulator($self->_simulation_mode());
|
||||
}
|
||||
|
||||
sub InitGL {
|
||||
@@ -272,39 +572,126 @@ sub InitGL {
|
||||
|
||||
return if $self->init;
|
||||
return unless $self->GetContext;
|
||||
|
||||
my $texture_id = 0;
|
||||
($texture_id) = glGenTextures_p(1);
|
||||
$self->_texture_name($texture_id);
|
||||
$self->init(1);
|
||||
}
|
||||
|
||||
sub GetContext {
|
||||
my ($self) = @_;
|
||||
|
||||
if (Wx::wxVERSION >= 2.009) {
|
||||
return $self->{context} ||= Wx::GLContext->new($self);
|
||||
} else {
|
||||
return $self->SUPER::GetContext;
|
||||
}
|
||||
return $self->{context} ||= Wx::GLContext->new($self);
|
||||
}
|
||||
|
||||
sub SetCurrent {
|
||||
my ($self, $context) = @_;
|
||||
|
||||
if (Wx::wxVERSION >= 2.009) {
|
||||
return $self->SUPER::SetCurrent($context);
|
||||
} else {
|
||||
return $self->SUPER::SetCurrent;
|
||||
}
|
||||
return $self->SUPER::SetCurrent($context);
|
||||
}
|
||||
|
||||
sub Resize {
|
||||
my ($self, $x, $y) = @_;
|
||||
my ($self) = @_;
|
||||
|
||||
return unless $self->GetContext;
|
||||
$self->dirty(0);
|
||||
|
||||
return unless $self->bb;
|
||||
$self->_dirty(0);
|
||||
|
||||
$self->SetCurrent($self->GetContext);
|
||||
my ($x, $y) = $self->GetSizeWH;
|
||||
|
||||
if ($self->_texture_size->x() < $x or $self->_texture_size->y() < $y) {
|
||||
# Allocate a large enough OpenGL texture with power of 2 dimensions.
|
||||
$self->_texture_size->set_x(1) if ($self->_texture_size->x() == 0);
|
||||
$self->_texture_size->set_y(1) if ($self->_texture_size->y() == 0);
|
||||
$self->_texture_size->set_x($self->_texture_size->x() * 2) while ($self->_texture_size->x() < $x);
|
||||
$self->_texture_size->set_y($self->_texture_size->y() * 2) while ($self->_texture_size->y() < $y);
|
||||
#print "screen size ", $x, "x", $y;
|
||||
#print "texture size ", $self->_texture_size->x(), "x", $self->_texture_size->y();
|
||||
# Initialize an empty texture.
|
||||
glBindTexture(GL_TEXTURE_2D, $self->_texture_name);
|
||||
if (1) {
|
||||
glTexImage2D_c(GL_TEXTURE_2D,
|
||||
0, # level (0 normal, heighr is form mip-mapping)
|
||||
GL_RGBA, # internal format
|
||||
$self->_texture_size->x(), $self->_texture_size->y(),
|
||||
0, # border
|
||||
GL_RGBA, # format RGBA color data
|
||||
GL_UNSIGNED_BYTE, # unsigned byte data
|
||||
0); # ptr to texture data
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
$self->_extrusion_simulator->set_image_size($self->_texture_size);
|
||||
}
|
||||
$self->_extrusion_simulator->set_viewport(Slic3r::Geometry::BoundingBox->new_from_points(
|
||||
[Slic3r::Point->new(0, 0), Slic3r::Point->new($x, $y)]));
|
||||
|
||||
glViewport(0, 0, $x, $y);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
|
||||
my $bb = $self->bb->clone;
|
||||
|
||||
# center bounding box around origin before scaling it
|
||||
my $bb_center = $bb->center;
|
||||
$bb->translate(@{$bb_center->negative});
|
||||
|
||||
# scale bounding box according to zoom factor
|
||||
$bb->scale($self->_zoom);
|
||||
|
||||
# reposition bounding box around original center
|
||||
$bb->translate(@{$bb_center});
|
||||
|
||||
# translate camera
|
||||
$bb->translate(@{$self->_camera_target});
|
||||
|
||||
# keep camera_bb within total bb
|
||||
# (i.e. prevent user from panning outside the bounding box)
|
||||
{
|
||||
my @translate = (0,0);
|
||||
if ($bb->x_min < $self->bb->x_min) {
|
||||
$translate[X] += $self->bb->x_min - $bb->x_min;
|
||||
}
|
||||
if ($bb->y_min < $self->bb->y_min) {
|
||||
$translate[Y] += $self->bb->y_min - $bb->y_min;
|
||||
}
|
||||
if ($bb->x_max > $self->bb->x_max) {
|
||||
$translate[X] -= $bb->x_max - $self->bb->x_max;
|
||||
}
|
||||
if ($bb->y_max > $self->bb->y_max) {
|
||||
$translate[Y] -= $bb->y_max - $self->bb->y_max;
|
||||
}
|
||||
$self->_camera_target->translate(@translate);
|
||||
$bb->translate(@translate);
|
||||
}
|
||||
|
||||
# save camera
|
||||
$self->_camera_bb($bb);
|
||||
|
||||
my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max);
|
||||
if (($x2 - $x1)/($y2 - $y1) > $x/$y) {
|
||||
# adjust Y
|
||||
my $new_y = $y * ($x2 - $x1) / $x;
|
||||
$y1 = ($y2 + $y1)/2 - $new_y/2;
|
||||
$y2 = $y1 + $new_y;
|
||||
} else {
|
||||
my $new_x = $x * ($y2 - $y1) / $y;
|
||||
$x1 = ($x2 + $x1)/2 - $new_x/2;
|
||||
$x2 = $x1 + $new_x;
|
||||
}
|
||||
glOrtho($x1, $x2, $y1, $y2, 0, 1);
|
||||
|
||||
# Set the adjusted bounding box at the extrusion simulator.
|
||||
#print "Scene bbox ", $bb->x_min, ",", $bb->y_min, " ", $bb->x_max, ",", $bb->y_max, "\n";
|
||||
#print "Setting simulator bbox ", $x1, ",", $y1, " ", $x2, ",", $y2, "\n";
|
||||
$self->_extrusion_simulator->set_bounding_box(
|
||||
Slic3r::Geometry::BoundingBox->new_from_points(
|
||||
[Slic3r::Point->new($x1, $y1), Slic3r::Point->new($x2, $y2)]));
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
}
|
||||
|
||||
# Thick line drawing is not used anywhere. Probably not tested?
|
||||
sub line {
|
||||
my (
|
||||
$x1, $y1, $x2, $y2, # coordinates of the line
|
||||
|
||||
@@ -4,43 +4,280 @@ use warnings;
|
||||
use utf8;
|
||||
|
||||
use List::Util qw();
|
||||
use Slic3r::Geometry qw();
|
||||
use Slic3r::Geometry::Clipper qw();
|
||||
use Wx qw(:misc :pen :brush :sizer :font :cursor wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw();
|
||||
use base 'Slic3r::GUI::PreviewCanvas';
|
||||
use Wx qw(:misc :pen :brush :sizer :font :cursor :keycode wxTAB_TRAVERSAL);
|
||||
#==============================================================================================================================
|
||||
#use Wx::Event qw(EVT_KEY_DOWN EVT_CHAR);
|
||||
#==============================================================================================================================
|
||||
use base qw(Slic3r::GUI::3DScene Class::Accessor);
|
||||
|
||||
#==============================================================================================================================
|
||||
#use Wx::Locale gettext => 'L';
|
||||
#
|
||||
#__PACKAGE__->mk_accessors(qw(
|
||||
# on_arrange on_rotate_object_left on_rotate_object_right on_scale_object_uniformly
|
||||
# on_remove_object on_increase_objects on_decrease_objects on_enable_action_buttons));
|
||||
#==============================================================================================================================
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $objects, $model, $config) = @_;
|
||||
my ($parent, $objects, $model, $print, $config) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent);
|
||||
|
||||
$self->{objects} = $objects;
|
||||
$self->{model} = $model;
|
||||
$self->{config} = $config;
|
||||
$self->{on_select_object} = sub {};
|
||||
$self->{on_double_click} = sub {};
|
||||
$self->{on_right_click} = sub {};
|
||||
$self->{on_instance_moved} = sub {};
|
||||
|
||||
|
||||
#==============================================================================================================================
|
||||
Slic3r::GUI::_3DScene::enable_picking($self, 1);
|
||||
Slic3r::GUI::_3DScene::enable_moving($self, 1);
|
||||
Slic3r::GUI::_3DScene::set_select_by($self, 'object');
|
||||
Slic3r::GUI::_3DScene::set_drag_by($self, 'instance');
|
||||
Slic3r::GUI::_3DScene::set_model($self, $model);
|
||||
Slic3r::GUI::_3DScene::set_print($self, $print);
|
||||
Slic3r::GUI::_3DScene::set_config($self, $config);
|
||||
# $self->enable_picking(1);
|
||||
# $self->enable_moving(1);
|
||||
# $self->select_by('object');
|
||||
# $self->drag_by('instance');
|
||||
#
|
||||
# $self->{objects} = $objects;
|
||||
# $self->{model} = $model;
|
||||
# $self->{print} = $print;
|
||||
# $self->{config} = $config;
|
||||
# $self->{on_select_object} = sub {};
|
||||
# $self->{on_instances_moved} = sub {};
|
||||
# $self->{on_wipe_tower_moved} = sub {};
|
||||
#
|
||||
# $self->{objects_volumes_idxs} = [];
|
||||
#
|
||||
# $self->on_select(sub {
|
||||
# my ($volume_idx) = @_;
|
||||
# $self->{on_select_object}->(($volume_idx == -1) ? undef : $self->volumes->[$volume_idx]->object_idx)
|
||||
# if ($self->{on_select_object});
|
||||
# });
|
||||
#
|
||||
# $self->on_move(sub {
|
||||
# my @volume_idxs = @_;
|
||||
# my %done = (); # prevent moving instances twice
|
||||
# my $object_moved;
|
||||
# my $wipe_tower_moved;
|
||||
# foreach my $volume_idx (@volume_idxs) {
|
||||
# my $volume = $self->volumes->[$volume_idx];
|
||||
# my $obj_idx = $volume->object_idx;
|
||||
# my $instance_idx = $volume->instance_idx;
|
||||
# next if $done{"${obj_idx}_${instance_idx}"};
|
||||
# $done{"${obj_idx}_${instance_idx}"} = 1;
|
||||
# if ($obj_idx < 1000) {
|
||||
# # Move a regular object.
|
||||
# my $model_object = $self->{model}->get_object($obj_idx);
|
||||
# $model_object
|
||||
# ->instances->[$instance_idx]
|
||||
# ->offset
|
||||
# ->translate($volume->origin->x, $volume->origin->y); #))
|
||||
# $model_object->invalidate_bounding_box;
|
||||
# $object_moved = 1;
|
||||
# } elsif ($obj_idx == 1000) {
|
||||
# # Move a wipe tower proxy.
|
||||
# $wipe_tower_moved = $volume->origin;
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# $self->{on_instances_moved}->()
|
||||
# if $object_moved && $self->{on_instances_moved};
|
||||
# $self->{on_wipe_tower_moved}->($wipe_tower_moved)
|
||||
# if $wipe_tower_moved && $self->{on_wipe_tower_moved};
|
||||
# });
|
||||
#
|
||||
# EVT_KEY_DOWN($self, sub {
|
||||
# my ($s, $event) = @_;
|
||||
# if ($event->HasModifiers) {
|
||||
# $event->Skip;
|
||||
# } else {
|
||||
# my $key = $event->GetKeyCode;
|
||||
# if ($key == WXK_DELETE) {
|
||||
# $self->on_remove_object->() if $self->on_remove_object;
|
||||
# } else {
|
||||
# $event->Skip;
|
||||
# }
|
||||
# }
|
||||
# });
|
||||
#
|
||||
# EVT_CHAR($self, sub {
|
||||
# my ($s, $event) = @_;
|
||||
# if ($event->HasModifiers) {
|
||||
# $event->Skip;
|
||||
# } else {
|
||||
# my $key = $event->GetKeyCode;
|
||||
# if ($key == ord('a')) {
|
||||
# $self->on_arrange->() if $self->on_arrange;
|
||||
# } elsif ($key == ord('l')) {
|
||||
# $self->on_rotate_object_left->() if $self->on_rotate_object_left;
|
||||
# } elsif ($key == ord('r')) {
|
||||
# $self->on_rotate_object_right->() if $self->on_rotate_object_right;
|
||||
# } elsif ($key == ord('s')) {
|
||||
# $self->on_scale_object_uniformly->() if $self->on_scale_object_uniformly;
|
||||
# } elsif ($key == ord('+')) {
|
||||
# $self->on_increase_objects->() if $self->on_increase_objects;
|
||||
# } elsif ($key == ord('-')) {
|
||||
# $self->on_decrease_objects->() if $self->on_decrease_objects;
|
||||
# } else {
|
||||
# $event->Skip;
|
||||
# }
|
||||
# }
|
||||
# });
|
||||
#==============================================================================================================================
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->reset_objects;
|
||||
return if $self->{model}->objects_count == 0;
|
||||
|
||||
$self->set_bounding_box($self->{model}->bounding_box);
|
||||
$self->set_bed_shape($self->{config}->bed_shape);
|
||||
|
||||
foreach my $model_object (@{$self->{model}->objects}) {
|
||||
$self->load_object($model_object, 1);
|
||||
}
|
||||
}
|
||||
#==============================================================================================================================
|
||||
#sub set_on_select_object {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->{on_select_object} = $cb;
|
||||
#}
|
||||
#
|
||||
#sub set_on_double_click {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_double_click($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_right_click {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_right_click($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_arrange {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_arrange($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_rotate_object_left {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_rotate_object_left($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_rotate_object_right {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_rotate_object_right($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_scale_object_uniformly {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_scale_object_uniformly($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_increase_objects {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_increase_objects($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_decrease_objects {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_decrease_objects($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_remove_object {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_remove_object($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_instances_moved {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->{on_instances_moved} = $cb;
|
||||
#}
|
||||
#
|
||||
#sub set_on_wipe_tower_moved {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->{on_wipe_tower_moved} = $cb;
|
||||
#}
|
||||
#
|
||||
#sub set_on_model_update {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_model_update($cb);
|
||||
#}
|
||||
#
|
||||
#sub set_on_enable_action_buttons {
|
||||
# my ($self, $cb) = @_;
|
||||
# $self->on_enable_action_buttons($cb);
|
||||
#}
|
||||
#
|
||||
#sub update_volumes_selection {
|
||||
# my ($self) = @_;
|
||||
#
|
||||
# foreach my $obj_idx (0..$#{$self->{model}->objects}) {
|
||||
# if ($self->{objects}[$obj_idx]->selected) {
|
||||
# my $volume_idxs = $self->{objects_volumes_idxs}->[$obj_idx];
|
||||
# $self->select_volume($_) for @{$volume_idxs};
|
||||
# }
|
||||
# }
|
||||
#}
|
||||
#
|
||||
#sub reload_scene {
|
||||
# my ($self, $force) = @_;
|
||||
#
|
||||
# $self->reset_objects;
|
||||
# $self->update_bed_size;
|
||||
#
|
||||
# if (! $self->IsShown && ! $force) {
|
||||
# $self->{reload_delayed} = 1;
|
||||
# return;
|
||||
# }
|
||||
#
|
||||
# $self->{reload_delayed} = 0;
|
||||
#
|
||||
# $self->{objects_volumes_idxs} = [];
|
||||
# foreach my $obj_idx (0..$#{$self->{model}->objects}) {
|
||||
# my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx);
|
||||
# push(@{$self->{objects_volumes_idxs}}, \@volume_idxs);
|
||||
# }
|
||||
#
|
||||
# $self->update_volumes_selection;
|
||||
#
|
||||
# if (defined $self->{config}->nozzle_diameter) {
|
||||
# # Should the wipe tower be visualized?
|
||||
# my $extruders_count = scalar @{ $self->{config}->nozzle_diameter };
|
||||
# # Height of a print.
|
||||
# my $height = $self->{model}->bounding_box->z_max;
|
||||
# # Show at least a slab.
|
||||
# $height = 10 if $height < 10;
|
||||
# if ($extruders_count > 1 && $self->{config}->single_extruder_multi_material && $self->{config}->wipe_tower &&
|
||||
# ! $self->{config}->complete_objects) {
|
||||
# $self->volumes->load_wipe_tower_preview(1000,
|
||||
# $self->{config}->wipe_tower_x, $self->{config}->wipe_tower_y, $self->{config}->wipe_tower_width,
|
||||
# #$self->{config}->wipe_tower_per_color_wipe# 15 * ($extruders_count - 1), # this is just a hack when the config parameter became obsolete
|
||||
# 15 * ($extruders_count - 1),
|
||||
# $self->{model}->bounding_box->z_max, $self->{config}->wipe_tower_rotation_angle, $self->UseVBOs);
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# $self->update_volumes_colors_by_extruder($self->{config});
|
||||
#
|
||||
# # checks for geometry outside the print volume to render it accordingly
|
||||
# if (scalar @{$self->volumes} > 0)
|
||||
# {
|
||||
# my $contained = $self->volumes->check_outside_state($self->{config});
|
||||
# if (!$contained) {
|
||||
# $self->set_warning_enabled(1);
|
||||
# Slic3r::GUI::_3DScene::generate_warning_texture(L("Detected object outside print volume"));
|
||||
# $self->on_enable_action_buttons->(0) if ($self->on_enable_action_buttons);
|
||||
# } else {
|
||||
# $self->set_warning_enabled(0);
|
||||
# $self->volumes->reset_outside_state();
|
||||
# Slic3r::GUI::_3DScene::reset_warning_texture();
|
||||
# $self->on_enable_action_buttons->(scalar @{$self->{model}->objects} > 0) if ($self->on_enable_action_buttons);
|
||||
# }
|
||||
# } else {
|
||||
# $self->set_warning_enabled(0);
|
||||
# Slic3r::GUI::_3DScene::reset_warning_texture();
|
||||
# }
|
||||
#}
|
||||
#
|
||||
#sub update_bed_size {
|
||||
# my ($self) = @_;
|
||||
# $self->set_bed_shape($self->{config}->bed_shape);
|
||||
#}
|
||||
#
|
||||
## Called by the Platter wxNotebook when this page is activated.
|
||||
#sub OnActivate {
|
||||
# my ($self) = @_;
|
||||
# $self->reload_scene(1) if ($self->{reload_delayed});
|
||||
#}
|
||||
#==============================================================================================================================
|
||||
|
||||
1;
|
||||
|
||||
542
lib/Slic3r/GUI/Plater/3DPreview.pm
Normal file
542
lib/Slic3r/GUI/Plater/3DPreview.pm
Normal file
@@ -0,0 +1,542 @@
|
||||
package Slic3r::GUI::Plater::3DPreview;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Print::State ':steps';
|
||||
use Wx qw(:misc :sizer :slider :statictext :keycode wxWHITE wxCB_READONLY);
|
||||
use Wx::Event qw(EVT_SLIDER EVT_KEY_DOWN EVT_CHECKBOX EVT_CHOICE EVT_CHECKLISTBOX);
|
||||
use base qw(Wx::Panel Class::Accessor);
|
||||
|
||||
use Wx::Locale gettext => 'L';
|
||||
|
||||
__PACKAGE__->mk_accessors(qw(print gcode_preview_data enabled _loaded canvas slider_low slider_high single_layer));
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, $print, $gcode_preview_data, $config) = @_;
|
||||
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition);
|
||||
$self->{config} = $config;
|
||||
$self->{number_extruders} = 1;
|
||||
# Show by feature type by default.
|
||||
$self->{preferred_color_mode} = 'feature';
|
||||
|
||||
# init GUI elements
|
||||
my $canvas = Slic3r::GUI::3DScene->new($self);
|
||||
Slic3r::GUI::_3DScene::enable_shader($canvas, 1);
|
||||
$self->canvas($canvas);
|
||||
my $slider_low = Wx::Slider->new(
|
||||
$self, -1,
|
||||
0, # default
|
||||
0, # min
|
||||
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
|
||||
# will skip drawing the slider if max <= min:
|
||||
1, # max
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxVERTICAL | wxSL_INVERSE,
|
||||
);
|
||||
$self->slider_low($slider_low);
|
||||
my $slider_high = Wx::Slider->new(
|
||||
$self, -1,
|
||||
0, # default
|
||||
0, # min
|
||||
# we set max to a bogus non-zero value because the MSW implementation of wxSlider
|
||||
# will skip drawing the slider if max <= min:
|
||||
1, # max
|
||||
wxDefaultPosition,
|
||||
wxDefaultSize,
|
||||
wxVERTICAL | wxSL_INVERSE,
|
||||
);
|
||||
$self->slider_high($slider_high);
|
||||
|
||||
my $z_label_low = $self->{z_label_low} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label_low->SetFont($Slic3r::GUI::small_font);
|
||||
my $z_label_high = $self->{z_label_high} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label_high->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
my $z_label_low_idx = $self->{z_label_low_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label_low_idx->SetFont($Slic3r::GUI::small_font);
|
||||
my $z_label_high_idx = $self->{z_label_high_idx} = Wx::StaticText->new($self, -1, "", wxDefaultPosition,
|
||||
[40,-1], wxALIGN_CENTRE_HORIZONTAL);
|
||||
$z_label_high_idx->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
$self->single_layer(0);
|
||||
my $checkbox_singlelayer = $self->{checkbox_singlelayer} = Wx::CheckBox->new($self, -1, L("1 Layer"));
|
||||
|
||||
my $label_view_type = $self->{label_view_type} = Wx::StaticText->new($self, -1, L("View"));
|
||||
|
||||
my $choice_view_type = $self->{choice_view_type} = Wx::Choice->new($self, -1);
|
||||
$choice_view_type->Append(L("Feature type"));
|
||||
$choice_view_type->Append(L("Height"));
|
||||
$choice_view_type->Append(L("Width"));
|
||||
$choice_view_type->Append(L("Speed"));
|
||||
$choice_view_type->Append(L("Volumetric flow rate"));
|
||||
$choice_view_type->Append(L("Tool"));
|
||||
$choice_view_type->SetSelection(0);
|
||||
|
||||
# the following value needs to be changed if new items are added into $choice_view_type before "Tool"
|
||||
$self->{tool_idx} = 5;
|
||||
|
||||
my $label_show_features = $self->{label_show_features} = Wx::StaticText->new($self, -1, L("Show"));
|
||||
|
||||
my $combochecklist_features = $self->{combochecklist_features} = Wx::ComboCtrl->new();
|
||||
$combochecklist_features->Create($self, -1, L("Feature types"), wxDefaultPosition, [200, -1], wxCB_READONLY);
|
||||
my $feature_text = L("Feature types");
|
||||
my $feature_items = L("Perimeter")."|"
|
||||
.L("External perimeter")."|"
|
||||
.L("Overhang perimeter")."|"
|
||||
.L("Internal infill")."|"
|
||||
.L("Solid infill")."|"
|
||||
.L("Top solid infill")."|"
|
||||
.L("Bridge infill")."|"
|
||||
.L("Gap fill")."|"
|
||||
.L("Skirt")."|"
|
||||
.L("Support material")."|"
|
||||
.L("Support material interface")."|"
|
||||
.L("Wipe tower")."|"
|
||||
.L("Custom");
|
||||
Slic3r::GUI::create_combochecklist($combochecklist_features, $feature_text, $feature_items, 1);
|
||||
|
||||
my $checkbox_travel = $self->{checkbox_travel} = Wx::CheckBox->new($self, -1, L("Travel"));
|
||||
my $checkbox_retractions = $self->{checkbox_retractions} = Wx::CheckBox->new($self, -1, L("Retractions"));
|
||||
my $checkbox_unretractions = $self->{checkbox_unretractions} = Wx::CheckBox->new($self, -1, L("Unretractions"));
|
||||
my $checkbox_shells = $self->{checkbox_shells} = Wx::CheckBox->new($self, -1, L("Shells"));
|
||||
|
||||
my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
my $vsizer_outer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$vsizer->Add($slider_low, 3, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$vsizer->Add($z_label_low_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$vsizer->Add($z_label_low, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$hsizer->Add($vsizer, 0, wxEXPAND, 0);
|
||||
$vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$vsizer->Add($slider_high, 3, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$vsizer->Add($z_label_high_idx, 0, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$vsizer->Add($z_label_high, 0, 0, 0);
|
||||
$hsizer->Add($vsizer, 0, wxEXPAND, 0);
|
||||
$vsizer_outer->Add($hsizer, 3, wxALIGN_CENTER_HORIZONTAL, 0);
|
||||
$vsizer_outer->Add($checkbox_singlelayer, 0, wxTOP | wxALIGN_CENTER_HORIZONTAL, 5);
|
||||
|
||||
my $bottom_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$bottom_sizer->Add($label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->Add($choice_view_type, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->AddSpacer(10);
|
||||
$bottom_sizer->Add($label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->Add($combochecklist_features, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->AddSpacer(20);
|
||||
$bottom_sizer->Add($checkbox_travel, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->AddSpacer(10);
|
||||
$bottom_sizer->Add($checkbox_retractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->AddSpacer(10);
|
||||
$bottom_sizer->Add($checkbox_unretractions, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
$bottom_sizer->AddSpacer(10);
|
||||
$bottom_sizer->Add($checkbox_shells, 0, wxEXPAND | wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$sizer->Add($canvas, 1, wxALL | wxEXPAND, 0);
|
||||
$sizer->Add($vsizer_outer, 0, wxTOP | wxBOTTOM | wxEXPAND, 5);
|
||||
|
||||
my $main_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$main_sizer->Add($sizer, 1, wxALL | wxEXPAND, 0);
|
||||
$main_sizer->Add($bottom_sizer, 0, wxALL | wxEXPAND, 0);
|
||||
|
||||
EVT_SLIDER($self, $slider_low, sub {
|
||||
$slider_high->SetValue($slider_low->GetValue) if $self->single_layer;
|
||||
$self->set_z_idx_low ($slider_low ->GetValue)
|
||||
});
|
||||
EVT_SLIDER($self, $slider_high, sub {
|
||||
$slider_low->SetValue($slider_high->GetValue) if $self->single_layer;
|
||||
$self->set_z_idx_high($slider_high->GetValue)
|
||||
});
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($s, $event) = @_;
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($event->HasModifiers) {
|
||||
$event->Skip;
|
||||
} else {
|
||||
if ($key == ord('U')) {
|
||||
$slider_high->SetValue($slider_high->GetValue + 1);
|
||||
$slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
|
||||
$self->set_z_idx_high($slider_high->GetValue);
|
||||
} elsif ($key == ord('D')) {
|
||||
$slider_high->SetValue($slider_high->GetValue - 1);
|
||||
$slider_low->SetValue($slider_high->GetValue) if ($event->ShiftDown());
|
||||
$self->set_z_idx_high($slider_high->GetValue);
|
||||
} elsif ($key == ord('S')) {
|
||||
$checkbox_singlelayer->SetValue(! $checkbox_singlelayer->GetValue());
|
||||
$self->single_layer($checkbox_singlelayer->GetValue());
|
||||
if ($self->single_layer) {
|
||||
$slider_low->SetValue($slider_high->GetValue);
|
||||
$self->set_z_idx_high($slider_high->GetValue);
|
||||
}
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
}
|
||||
});
|
||||
EVT_KEY_DOWN($slider_low, sub {
|
||||
my ($s, $event) = @_;
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($event->HasModifiers) {
|
||||
$event->Skip;
|
||||
} else {
|
||||
if ($key == WXK_LEFT) {
|
||||
} elsif ($key == WXK_RIGHT) {
|
||||
$slider_high->SetFocus;
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
}
|
||||
});
|
||||
EVT_KEY_DOWN($slider_high, sub {
|
||||
my ($s, $event) = @_;
|
||||
my $key = $event->GetKeyCode;
|
||||
if ($event->HasModifiers) {
|
||||
$event->Skip;
|
||||
} else {
|
||||
if ($key == WXK_LEFT) {
|
||||
$slider_low->SetFocus;
|
||||
} elsif ($key == WXK_RIGHT) {
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
}
|
||||
});
|
||||
EVT_CHECKBOX($self, $checkbox_singlelayer, sub {
|
||||
$self->single_layer($checkbox_singlelayer->GetValue());
|
||||
if ($self->single_layer) {
|
||||
$slider_low->SetValue($slider_high->GetValue);
|
||||
$self->set_z_idx_high($slider_high->GetValue);
|
||||
}
|
||||
});
|
||||
EVT_CHOICE($self, $choice_view_type, sub {
|
||||
my $selection = $choice_view_type->GetCurrentSelection();
|
||||
$self->{preferred_color_mode} = ($selection == $self->{tool_idx}) ? 'tool' : 'feature';
|
||||
$self->gcode_preview_data->set_type($selection);
|
||||
$self->reload_print;
|
||||
});
|
||||
EVT_CHECKLISTBOX($self, $combochecklist_features, sub {
|
||||
my $flags = Slic3r::GUI::combochecklist_get_flags($combochecklist_features);
|
||||
|
||||
$self->gcode_preview_data->set_extrusion_flags($flags);
|
||||
$self->refresh_print;
|
||||
});
|
||||
EVT_CHECKBOX($self, $checkbox_travel, sub {
|
||||
$self->gcode_preview_data->set_travel_visible($checkbox_travel->IsChecked());
|
||||
$self->refresh_print;
|
||||
});
|
||||
EVT_CHECKBOX($self, $checkbox_retractions, sub {
|
||||
$self->gcode_preview_data->set_retractions_visible($checkbox_retractions->IsChecked());
|
||||
$self->refresh_print;
|
||||
});
|
||||
EVT_CHECKBOX($self, $checkbox_unretractions, sub {
|
||||
$self->gcode_preview_data->set_unretractions_visible($checkbox_unretractions->IsChecked());
|
||||
$self->refresh_print;
|
||||
});
|
||||
EVT_CHECKBOX($self, $checkbox_shells, sub {
|
||||
$self->gcode_preview_data->set_shells_visible($checkbox_shells->IsChecked());
|
||||
$self->refresh_print;
|
||||
});
|
||||
|
||||
$self->SetSizer($main_sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
# init canvas
|
||||
$self->print($print);
|
||||
$self->gcode_preview_data($gcode_preview_data);
|
||||
|
||||
# sets colors for gcode preview extrusion roles
|
||||
my @extrusion_roles_colors = (
|
||||
'Perimeter' => 'FFFF66',
|
||||
'External perimeter' => 'FFA500',
|
||||
'Overhang perimeter' => '0000FF',
|
||||
'Internal infill' => 'B1302A',
|
||||
'Solid infill' => 'D732D7',
|
||||
'Top solid infill' => 'FF1A1A',
|
||||
'Bridge infill' => '9999FF',
|
||||
'Gap fill' => 'FFFFFF',
|
||||
'Skirt' => '845321',
|
||||
'Support material' => '00FF00',
|
||||
'Support material interface' => '008000',
|
||||
'Wipe tower' => 'B3E3AB',
|
||||
'Custom' => '28CC94',
|
||||
);
|
||||
$self->gcode_preview_data->set_extrusion_paths_colors(\@extrusion_roles_colors);
|
||||
|
||||
$self->show_hide_ui_elements('none');
|
||||
$self->reload_print;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reload_print {
|
||||
my ($self, $force) = @_;
|
||||
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->canvas);
|
||||
$self->_loaded(0);
|
||||
|
||||
if (! $self->IsShown && ! $force) {
|
||||
# $self->{reload_delayed} = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
$self->load_print;
|
||||
}
|
||||
|
||||
sub refresh_print {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->_loaded(0);
|
||||
|
||||
if (! $self->IsShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
$self->load_print;
|
||||
}
|
||||
|
||||
sub reset_gcode_preview_data {
|
||||
my ($self) = @_;
|
||||
$self->gcode_preview_data->reset;
|
||||
Slic3r::GUI::_3DScene::reset_legend_texture();
|
||||
}
|
||||
|
||||
sub load_print {
|
||||
my ($self) = @_;
|
||||
|
||||
return if $self->_loaded;
|
||||
|
||||
# we require that there's at least one object and the posSlice step
|
||||
# is performed on all of them (this ensures that _shifted_copies was
|
||||
# populated and we know the number of layers)
|
||||
my $n_layers = 0;
|
||||
if ($self->print->object_step_done(STEP_SLICE)) {
|
||||
my %z = (); # z => 1
|
||||
foreach my $object (@{$self->{print}->objects}) {
|
||||
foreach my $layer (@{$object->layers}, @{$object->support_layers}) {
|
||||
$z{$layer->print_z} = 1;
|
||||
}
|
||||
}
|
||||
$self->{layers_z} = [ sort { $a <=> $b } keys %z ];
|
||||
$n_layers = scalar(@{$self->{layers_z}});
|
||||
}
|
||||
|
||||
if ($n_layers == 0) {
|
||||
$self->reset_sliders;
|
||||
Slic3r::GUI::_3DScene::reset_legend_texture();
|
||||
$self->canvas->Refresh; # clears canvas
|
||||
return;
|
||||
}
|
||||
|
||||
if ($self->{preferred_color_mode} eq 'tool_or_feature') {
|
||||
# It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature.
|
||||
# Color by feature if it is a single extruder print.
|
||||
my $extruders = $self->{print}->extruders;
|
||||
my $type = (scalar(@{$extruders}) > 1) ? $self->{tool_idx} : 0;
|
||||
$self->gcode_preview_data->set_type($type);
|
||||
$self->{choice_view_type}->SetSelection($type);
|
||||
# If the ->SetSelection changed the following line, revert it to "decide yourself".
|
||||
$self->{preferred_color_mode} = 'tool_or_feature';
|
||||
}
|
||||
|
||||
# Collect colors per extruder.
|
||||
my @colors = ();
|
||||
if (! $self->gcode_preview_data->empty() || $self->gcode_preview_data->type == $self->{tool_idx}) {
|
||||
my @extruder_colors = @{$self->{config}->extruder_colour};
|
||||
my @filament_colors = @{$self->{config}->filament_colour};
|
||||
for (my $i = 0; $i <= $#extruder_colors; $i += 1) {
|
||||
my $color = $extruder_colors[$i];
|
||||
$color = $filament_colors[$i] if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
|
||||
$color = '#FFFFFF' if (! defined($color) || $color !~ m/^#[[:xdigit:]]{6}/);
|
||||
push @colors, $color;
|
||||
}
|
||||
}
|
||||
|
||||
if ($self->IsShown) {
|
||||
# used to set the sliders to the extremes of the current zs range
|
||||
$self->{force_sliders_full_range} = 0;
|
||||
|
||||
if ($self->gcode_preview_data->empty) {
|
||||
# load skirt and brim
|
||||
Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
|
||||
Slic3r::GUI::_3DScene::load_print_toolpaths($self->canvas);
|
||||
Slic3r::GUI::_3DScene::load_wipe_tower_toolpaths($self->canvas, \@colors);
|
||||
foreach my $object (@{$self->print->objects}) {
|
||||
Slic3r::GUI::_3DScene::load_print_object_toolpaths($self->canvas, $object, \@colors);
|
||||
# Show the objects in very transparent color.
|
||||
#my @volume_ids = $self->canvas->load_object($object->model_object);
|
||||
#$self->canvas->volumes->[$_]->color->[3] = 0.2 for @volume_ids;
|
||||
}
|
||||
$self->show_hide_ui_elements('simple');
|
||||
Slic3r::GUI::_3DScene::reset_legend_texture();
|
||||
} else {
|
||||
$self->{force_sliders_full_range} = (Slic3r::GUI::_3DScene::get_volumes_count($self->canvas) == 0);
|
||||
Slic3r::GUI::_3DScene::set_print($self->canvas, $self->print);
|
||||
Slic3r::GUI::_3DScene::load_gcode_preview($self->canvas, $self->gcode_preview_data, \@colors);
|
||||
$self->show_hide_ui_elements('full');
|
||||
|
||||
# recalculates zs and update sliders accordingly
|
||||
$self->{layers_z} = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 1);
|
||||
$n_layers = scalar(@{$self->{layers_z}});
|
||||
if ($n_layers == 0) {
|
||||
# all layers filtered out
|
||||
$self->reset_sliders;
|
||||
$self->canvas->Refresh; # clears canvas
|
||||
}
|
||||
}
|
||||
|
||||
$self->update_sliders($n_layers) if ($n_layers > 0);
|
||||
$self->_loaded(1);
|
||||
}
|
||||
}
|
||||
|
||||
sub reset_sliders {
|
||||
my ($self) = @_;
|
||||
$self->enabled(0);
|
||||
$self->set_z_range(0,0);
|
||||
$self->slider_low->Hide;
|
||||
$self->slider_high->Hide;
|
||||
$self->{z_label_low}->SetLabel("");
|
||||
$self->{z_label_high}->SetLabel("");
|
||||
$self->{z_label_low_idx}->SetLabel("");
|
||||
$self->{z_label_high_idx}->SetLabel("");
|
||||
}
|
||||
|
||||
sub update_sliders
|
||||
{
|
||||
my ($self, $n_layers) = @_;
|
||||
|
||||
my $z_idx_low = $self->slider_low->GetValue;
|
||||
my $z_idx_high = $self->slider_high->GetValue;
|
||||
$self->enabled(1);
|
||||
$self->slider_low->SetRange(0, $n_layers - 1);
|
||||
$self->slider_high->SetRange(0, $n_layers - 1);
|
||||
|
||||
if ($self->{force_sliders_full_range}) {
|
||||
$z_idx_low = 0;
|
||||
$z_idx_high = $n_layers - 1;
|
||||
} elsif ($z_idx_high < $n_layers && ($self->single_layer || $z_idx_high != 0)) {
|
||||
# search new indices for nearest z (size of $self->{layers_z} may change in dependence of what is shown)
|
||||
if (defined($self->{z_low})) {
|
||||
for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
|
||||
if ($self->{layers_z}[$i] <= $self->{z_low}) {
|
||||
$z_idx_low = $i;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($self->{z_high})) {
|
||||
for (my $i = scalar(@{$self->{layers_z}}) - 1; $i >= 0; $i -= 1) {
|
||||
if ($self->{layers_z}[$i] <= $self->{z_high}) {
|
||||
$z_idx_high = $i;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif ($z_idx_high >= $n_layers) {
|
||||
# Out of range. Disable 'single layer' view.
|
||||
$self->single_layer(0);
|
||||
$self->{checkbox_singlelayer}->SetValue(0);
|
||||
$z_idx_low = 0;
|
||||
$z_idx_high = $n_layers - 1;
|
||||
} else {
|
||||
$z_idx_low = 0;
|
||||
$z_idx_high = $n_layers - 1;
|
||||
}
|
||||
|
||||
$self->slider_low->SetValue($z_idx_low);
|
||||
$self->slider_high->SetValue($z_idx_high);
|
||||
$self->slider_low->Show;
|
||||
$self->slider_high->Show;
|
||||
$self->set_z_range($self->{layers_z}[$z_idx_low], $self->{layers_z}[$z_idx_high]);
|
||||
$self->Layout;
|
||||
}
|
||||
|
||||
sub set_z_range
|
||||
{
|
||||
my ($self, $z_low, $z_high) = @_;
|
||||
|
||||
return if !$self->enabled;
|
||||
$self->{z_low} = $z_low;
|
||||
$self->{z_high} = $z_high;
|
||||
$self->{z_label_low}->SetLabel(sprintf '%.2f', $z_low);
|
||||
$self->{z_label_high}->SetLabel(sprintf '%.2f', $z_high);
|
||||
|
||||
my $layers_z = Slic3r::GUI::_3DScene::get_current_print_zs($self->canvas, 0);
|
||||
for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) {
|
||||
if (($z_low - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_low + 1e-6)) {
|
||||
$self->{z_label_low_idx}->SetLabel(sprintf '%d', $i + 1);
|
||||
last;
|
||||
}
|
||||
}
|
||||
for (my $i = 0; $i < scalar(@{$layers_z}); $i += 1) {
|
||||
if (($z_high - 1e-6 < @{$layers_z}[$i]) && (@{$layers_z}[$i] < $z_high + 1e-6)) {
|
||||
$self->{z_label_high_idx}->SetLabel(sprintf '%d', $i + 1);
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::set_toolpaths_range($self->canvas, $z_low - 1e-6, $z_high + 1e-6);
|
||||
$self->canvas->Refresh if $self->IsShown;
|
||||
}
|
||||
|
||||
sub set_z_idx_low
|
||||
{
|
||||
my ($self, $idx_low) = @_;
|
||||
if ($self->enabled) {
|
||||
my $idx_high = $self->slider_high->GetValue;
|
||||
if ($idx_low >= $idx_high) {
|
||||
$idx_high = $idx_low;
|
||||
$self->slider_high->SetValue($idx_high);
|
||||
}
|
||||
$self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]);
|
||||
}
|
||||
}
|
||||
|
||||
sub set_z_idx_high
|
||||
{
|
||||
my ($self, $idx_high) = @_;
|
||||
if ($self->enabled) {
|
||||
my $idx_low = $self->slider_low->GetValue;
|
||||
if ($idx_low > $idx_high) {
|
||||
$idx_low = $idx_high;
|
||||
$self->slider_low->SetValue($idx_low);
|
||||
}
|
||||
$self->set_z_range($self->{layers_z}[$idx_low], $self->{layers_z}[$idx_high]);
|
||||
}
|
||||
}
|
||||
|
||||
sub set_number_extruders {
|
||||
my ($self, $number_extruders) = @_;
|
||||
if ($self->{number_extruders} != $number_extruders) {
|
||||
$self->{number_extruders} = $number_extruders;
|
||||
my $type = ($number_extruders > 1) ?
|
||||
$self->{tool_idx} # color by a tool number
|
||||
: 0; # color by a feature type
|
||||
$self->{choice_view_type}->SetSelection($type);
|
||||
$self->gcode_preview_data->set_type($type);
|
||||
$self->{preferred_color_mode} = ($type == $self->{tool_idx}) ? 'tool_or_feature' : 'feature';
|
||||
}
|
||||
}
|
||||
|
||||
sub show_hide_ui_elements {
|
||||
my ($self, $what) = @_;
|
||||
my $method = ($what eq 'full') ? 'Enable' : 'Disable';
|
||||
$self->{$_}->$method for qw(label_show_features combochecklist_features checkbox_travel checkbox_retractions checkbox_unretractions checkbox_shells);
|
||||
$method = ($what eq 'none') ? 'Disable' : 'Enable';
|
||||
$self->{$_}->$method for qw(label_view_type choice_view_type);
|
||||
}
|
||||
|
||||
# Called by the Platter wxNotebook when this page is activated.
|
||||
sub OnActivate {
|
||||
# my ($self) = @_;
|
||||
# $self->reload_print(1) if ($self->{reload_delayed});
|
||||
}
|
||||
|
||||
1;
|
||||
221
lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm
Normal file
221
lib/Slic3r/GUI/Plater/LambdaObjectDialog.pm
Normal file
@@ -0,0 +1,221 @@
|
||||
# Generate an anonymous or "lambda" 3D object. This gets used with the Add Generic option in Settings.
|
||||
#
|
||||
|
||||
package Slic3r::GUI::Plater::LambdaObjectDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL wxCB_READONLY wxTE_PROCESS_TAB);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON EVT_COMBOBOX EVT_TEXT);
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Lambda Object", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
# Note whether the window was already closed, so a pending update is not executed.
|
||||
$self->{already_closed} = 0;
|
||||
$self->{object_parameters} = {
|
||||
type => "box",
|
||||
dim => [1, 1, 1],
|
||||
cyl_r => 1,
|
||||
cyl_h => 1,
|
||||
sph_rho => 1.0,
|
||||
slab_h => 1.0,
|
||||
slab_z => 0.0,
|
||||
};
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
my $button_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
my $button_ok = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
my $button_cancel = $self->CreateStdDialogButtonSizer(wxCANCEL);
|
||||
$button_sizer->Add($button_ok);
|
||||
$button_sizer->Add($button_cancel);
|
||||
EVT_BUTTON($self, wxID_OK, sub {
|
||||
# validate user input
|
||||
return if !$self->CanClose;
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
EVT_BUTTON($self, wxID_CANCEL, sub {
|
||||
# validate user input
|
||||
return if !$self->CanClose;
|
||||
|
||||
$self->EndModal(wxID_CANCEL);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
my $optgroup_box;
|
||||
$optgroup_box = $self->{optgroup_box} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Cube...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id == 0 || $opt_id == 1 || $opt_id == 2) {
|
||||
if (!looks_like_number($optgroup_box->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{dim}[$opt_id] = $optgroup_box->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
my @options = ("box", "slab", "cylinder", "sphere");
|
||||
$self->{type} = Wx::ComboBox->new($self, 1, "box", wxDefaultPosition, wxDefaultSize, \@options, wxCB_READONLY);
|
||||
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 0,
|
||||
label => 'L',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 1,
|
||||
label => 'W',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_box->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 2,
|
||||
label => 'H',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_cylinder;
|
||||
$optgroup_cylinder = $self->{optgroup_cylinder} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Cylinder...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'cyl_r' || $opt_id eq 'cyl_h') {
|
||||
if (!looks_like_number($optgroup_cylinder->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_cylinder->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
|
||||
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "cyl_r",
|
||||
label => 'Radius',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_cylinder->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "cyl_h",
|
||||
label => 'Height',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_sphere;
|
||||
$optgroup_sphere = $self->{optgroup_sphere} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Sphere...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'sph_rho') {
|
||||
if (!looks_like_number($optgroup_sphere->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_sphere->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
|
||||
$optgroup_sphere->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "sph_rho",
|
||||
label => 'Rho',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
|
||||
my $optgroup_slab;
|
||||
$optgroup_slab = $self->{optgroup_slab} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Add Slab...',
|
||||
on_change => sub {
|
||||
# Do validation
|
||||
my ($opt_id) = @_;
|
||||
if ($opt_id eq 'slab_z' || $opt_id eq 'slab_h') {
|
||||
if (!looks_like_number($optgroup_slab->get_value($opt_id))) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
$self->{object_parameters}->{$opt_id} = $optgroup_slab->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
$optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "slab_h",
|
||||
label => 'H',
|
||||
type => 'f',
|
||||
default => '1',
|
||||
));
|
||||
$optgroup_slab->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => "slab_z",
|
||||
label => 'Initial Z',
|
||||
type => 'f',
|
||||
default => '0',
|
||||
));
|
||||
|
||||
|
||||
EVT_COMBOBOX($self, 1, sub{
|
||||
$self->{object_parameters}->{type} = $self->{type}->GetValue();
|
||||
$self->_update_ui;
|
||||
});
|
||||
|
||||
|
||||
$self->{sizer}->Add($self->{type}, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_box->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_cylinder->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_sphere->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($optgroup_slab->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->{sizer}->Add($button_sizer,0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
$self->_update_ui;
|
||||
|
||||
$self->SetSizer($self->{sizer});
|
||||
$self->{sizer}->Fit($self);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
|
||||
return $self;
|
||||
}
|
||||
sub CanClose {
|
||||
return 1;
|
||||
}
|
||||
sub ObjectParameter {
|
||||
my ($self) = @_;
|
||||
return $self->{object_parameters};
|
||||
}
|
||||
|
||||
sub _update_ui {
|
||||
my ($self) = @_;
|
||||
$self->{sizer}->Hide($self->{optgroup_cylinder}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_slab}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_box}->sizer);
|
||||
$self->{sizer}->Hide($self->{optgroup_sphere}->sizer);
|
||||
if ($self->{type}->GetValue eq "box") {
|
||||
$self->{sizer}->Show($self->{optgroup_box}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "cylinder") {
|
||||
$self->{sizer}->Show($self->{optgroup_cylinder}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "slab") {
|
||||
$self->{sizer}->Show($self->{optgroup_slab}->sizer);
|
||||
} elsif ($self->{type}->GetValue eq "sphere") {
|
||||
$self->{sizer}->Show($self->{optgroup_sphere}->sizer);
|
||||
}
|
||||
$self->{sizer}->Fit($self);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
}
|
||||
1;
|
||||
@@ -1,20 +1,27 @@
|
||||
# Cut an object at a Z position, keep either the top or the bottom of the object.
|
||||
# This dialog gets opened with the "Cut..." button above the platter.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectCutDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Slic3r::Geometry qw(PI X);
|
||||
use Wx qw(:dialog :id :misc :sizer wxTAB_TRAVERSAL);
|
||||
use Wx qw(wxTheApp :dialog :id :misc :sizer wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE EVT_BUTTON);
|
||||
use List::Util qw(max);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{model_object_idx} = $params{model_object_idx};
|
||||
$self->{model_object} = $params{model_object};
|
||||
$self->{new_model_objects} = [];
|
||||
# Mark whether the mesh cut is valid.
|
||||
# If not, it needs to be recalculated by _update() on wxTheApp->CallAfter() or on exit of the dialog.
|
||||
$self->{mesh_cut_valid} = 0;
|
||||
# Note whether the window was already closed, so a pending update is not executed.
|
||||
$self->{already_closed} = 0;
|
||||
|
||||
# cut options
|
||||
$self->{cut_options} = {
|
||||
@@ -22,6 +29,9 @@ sub new {
|
||||
keep_upper => 1,
|
||||
keep_lower => 1,
|
||||
rotate_lower => 1,
|
||||
# preview => 1,
|
||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
|
||||
preview => 0,
|
||||
};
|
||||
|
||||
my $optgroup;
|
||||
@@ -30,9 +40,18 @@ sub new {
|
||||
title => 'Cut',
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
|
||||
$self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
|
||||
$self->_update;
|
||||
# There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
|
||||
# genates tens of events for a single value change.
|
||||
# Only trigger the recalculation if the value changes
|
||||
# or a live preview was activated and the mesh cut is not valid yet.
|
||||
if ($self->{cut_options}{$opt_id} != $optgroup->get_value($opt_id) ||
|
||||
! $self->{mesh_cut_valid} && $self->_life_preview_active()) {
|
||||
$self->{cut_options}{$opt_id} = $optgroup->get_value($opt_id);
|
||||
$self->{mesh_cut_valid} = 0;
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_update;
|
||||
});
|
||||
}
|
||||
},
|
||||
label_width => 120,
|
||||
);
|
||||
@@ -70,6 +89,13 @@ sub new {
|
||||
tooltip => 'If enabled, the lower part will be rotated by 180° so that the flat cut surface lies on the print bed.',
|
||||
default => $self->{cut_options}{rotate_lower},
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'preview',
|
||||
label => 'Show preview',
|
||||
type => 'bool',
|
||||
tooltip => 'If enabled, object will be cut in real time.',
|
||||
default => $self->{cut_options}{preview},
|
||||
));
|
||||
{
|
||||
my $cut_button_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->{btn_cut} = Wx::Button->new($self, -1, "Perform cut", wxDefaultPosition, wxDefaultSize);
|
||||
@@ -86,12 +112,14 @@ sub new {
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas->set_bounding_box($self->{model_object}->bounding_box);
|
||||
$canvas->set_auto_bed_shape;
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
|
||||
Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas->SetMinSize($canvas->GetSize);
|
||||
Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->{config});
|
||||
Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
@@ -102,72 +130,155 @@ sub new {
|
||||
$self->SetMinSize($self->GetSize);
|
||||
$self->{sizer}->SetSizeHints($self);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
EVT_BUTTON($self, $self->{btn_cut}, sub {
|
||||
# Recalculate the cut if the preview was not active.
|
||||
$self->_perform_cut() unless $self->{mesh_cut_valid};
|
||||
|
||||
# Adjust position / orientation of the split object halves.
|
||||
if ($self->{new_model_objects}{lower}) {
|
||||
if ($self->{cut_options}{rotate_lower}) {
|
||||
$self->{new_model_objects}{lower}->rotate(PI, X);
|
||||
$self->{new_model_objects}{lower}->center_around_origin; # align to Z = 0
|
||||
}
|
||||
}
|
||||
if ($self->{new_model_objects}{upper}) {
|
||||
$self->{new_model_objects}{upper}->center_around_origin; # align to Z = 0
|
||||
}
|
||||
|
||||
# Note that the window was already closed, so a pending update will not be executed.
|
||||
$self->{already_closed} = 1;
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
$self->{canvas}->Destroy;
|
||||
$self->Destroy();
|
||||
});
|
||||
|
||||
EVT_BUTTON($self, $self->{btn_cut}, sub { $self->perform_cut });
|
||||
|
||||
|
||||
EVT_CLOSE($self, sub {
|
||||
# Note that the window was already closed, so a pending update will not be executed.
|
||||
$self->{already_closed} = 1;
|
||||
$self->EndModal(wxID_CANCEL);
|
||||
$self->{canvas}->Destroy;
|
||||
$self->Destroy();
|
||||
});
|
||||
|
||||
$self->_update;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _update {
|
||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
|
||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||
sub _mesh_slice_z_pos
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
my $optgroup = $self->{optgroup};
|
||||
|
||||
# update canvas
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->SetCuttingPlane($self->{cut_options}{z});
|
||||
$self->{canvas}->Render;
|
||||
}
|
||||
|
||||
# update controls
|
||||
my $z = $self->{cut_options}{z};
|
||||
$optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
|
||||
$optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
|
||||
$optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower});
|
||||
|
||||
# update cut button
|
||||
if (($self->{cut_options}{keep_upper} && $have_upper)
|
||||
|| ($self->{cut_options}{keep_lower} && $have_lower)) {
|
||||
$self->{btn_cut}->Enable;
|
||||
} else {
|
||||
$self->{btn_cut}->Disable;
|
||||
}
|
||||
return $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor;
|
||||
}
|
||||
|
||||
sub perform_cut {
|
||||
# Only perform live preview if just a single part of the object shall survive.
|
||||
sub _life_preview_active
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
|
||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||
my $z = $self->{cut_options}{z} / $self->{model_object}->instances->[0]->scaling_factor;
|
||||
|
||||
return $self->{cut_options}{preview} && ($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
|
||||
}
|
||||
|
||||
# Slice the mesh, keep the top / bottom part.
|
||||
sub _perform_cut
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
# Early exit. If the cut is valid, don't recalculate it.
|
||||
return if $self->{mesh_cut_valid};
|
||||
|
||||
my $z = $self->_mesh_slice_z_pos();
|
||||
|
||||
my ($new_model) = $self->{model_object}->cut($z);
|
||||
my ($upper_object, $lower_object) = @{$new_model->objects};
|
||||
$self->{new_model} = $new_model;
|
||||
$self->{new_model_objects} = [];
|
||||
$self->{new_model_objects} = {};
|
||||
if ($self->{cut_options}{keep_upper} && $upper_object->volumes_count > 0) {
|
||||
push @{$self->{new_model_objects}}, $upper_object;
|
||||
$self->{new_model_objects}{upper} = $upper_object;
|
||||
}
|
||||
if ($self->{cut_options}{keep_lower} && $lower_object->volumes_count > 0) {
|
||||
push @{$self->{new_model_objects}}, $lower_object;
|
||||
if ($self->{cut_options}{rotate_lower}) {
|
||||
$lower_object->rotate(PI, X);
|
||||
$self->{new_model_objects}{lower} = $lower_object;
|
||||
}
|
||||
|
||||
$self->{mesh_cut_valid} = 1;
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
|
||||
# Don't update if the window was already closed.
|
||||
# We are not sure whether the action planned by wxTheApp->CallAfter() may be triggered after the window is closed.
|
||||
# Probably not, but better be safe than sorry, which is espetially true on multiple platforms.
|
||||
return if $self->{already_closed};
|
||||
|
||||
# Only recalculate the cut, if the live cut preview is active.
|
||||
my $life_preview_active = $self->_life_preview_active();
|
||||
$self->_perform_cut() if $life_preview_active;
|
||||
|
||||
{
|
||||
# scale Z down to original size since we're using the transformed mesh for 3D preview
|
||||
# and cut dialog but ModelObject::cut() needs Z without any instance transformation
|
||||
my $z = $self->_mesh_slice_z_pos();
|
||||
|
||||
|
||||
# update canvas
|
||||
if ($self->{canvas}) {
|
||||
# get volumes to render
|
||||
my @objects = ();
|
||||
if ($life_preview_active) {
|
||||
push @objects, values %{$self->{new_model_objects}};
|
||||
} else {
|
||||
push @objects, $self->{model_object};
|
||||
}
|
||||
|
||||
my $z_cut = $z + $self->{model_object}->bounding_box->z_min;
|
||||
|
||||
# get section contour
|
||||
my @expolygons = ();
|
||||
foreach my $volume (@{$self->{model_object}->volumes}) {
|
||||
next if !$volume->mesh;
|
||||
next if $volume->modifier;
|
||||
my $expp = $volume->mesh->slice([ $z_cut ])->[0];
|
||||
push @expolygons, @$expp;
|
||||
}
|
||||
foreach my $expolygon (@expolygons) {
|
||||
$self->{model_object}->instances->[0]->transform_polygon($_)
|
||||
for @$expolygon;
|
||||
$expolygon->translate(map Slic3r::Geometry::scale($_), @{ $self->{model_object}->instances->[0]->offset });
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
|
||||
Slic3r::GUI::_3DScene::set_cutting_plane($self->{canvas}, $self->{cut_options}{z}, [@expolygons]);
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
$self->Close;
|
||||
# update controls
|
||||
{
|
||||
my $z = $self->{cut_options}{z};
|
||||
my $optgroup = $self->{optgroup};
|
||||
$optgroup->get_field('keep_upper')->toggle(my $have_upper = abs($z - $optgroup->get_option('z')->max) > 0.1);
|
||||
$optgroup->get_field('keep_lower')->toggle(my $have_lower = $z > 0.1);
|
||||
$optgroup->get_field('rotate_lower')->toggle($z > 0 && $self->{cut_options}{keep_lower});
|
||||
# Disabled live preview by default as it is not stable and/or the calculation takes too long for interactive usage.
|
||||
# $optgroup->get_field('preview')->toggle($self->{cut_options}{keep_upper} != $self->{cut_options}{keep_lower});
|
||||
|
||||
# update cut button
|
||||
if (($self->{cut_options}{keep_upper} && $have_upper)
|
||||
|| ($self->{cut_options}{keep_lower} && $have_lower)) {
|
||||
$self->{btn_cut}->Enable;
|
||||
} else {
|
||||
$self->{btn_cut}->Disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub NewModelObjects {
|
||||
my ($self) = @_;
|
||||
return @{ $self->{new_model_objects} };
|
||||
return values %{ $self->{new_model_objects} };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Configuration of mesh modifiers and their parameters.
|
||||
# This panel is inserted into ObjectSettingsDialog.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectPartsPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use Wx qw(:misc :sizer :treectrl :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG
|
||||
use Wx qw(:misc :sizer :treectrl :button :keycode wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG wxID_CANCEL wxMOD_CONTROL
|
||||
wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED EVT_TREE_KEY_DOWN EVT_KEY_DOWN);
|
||||
use List::Util qw(max);
|
||||
use base 'Wx::Panel';
|
||||
|
||||
use constant ICON_OBJECT => 0;
|
||||
@@ -14,22 +18,34 @@ use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
|
||||
# C++ type Slic3r::ModelObject
|
||||
my $object = $self->{model_object} = $params{model_object};
|
||||
|
||||
# Save state for sliders.
|
||||
$self->{move_options} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
$self->{last_coords} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
|
||||
# create TreeCtrl
|
||||
my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100],
|
||||
wxTR_NO_BUTTONS | wxSUNKEN_BORDER | wxTR_HAS_VARIABLE_ROW_HEIGHT
|
||||
| wxTR_SINGLE | wxTR_NO_BUTTONS);
|
||||
| wxTR_SINGLE);
|
||||
{
|
||||
$self->{tree_icons} = Wx::ImageList->new(16, 16, 1);
|
||||
$tree->AssignImageList($self->{tree_icons});
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/brick.png", wxBITMAP_TYPE_PNG)); # ICON_OBJECT
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new("$Slic3r::var/plugin.png", wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("brick.png"), wxBITMAP_TYPE_PNG)); # ICON_OBJECT
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("package.png"), wxBITMAP_TYPE_PNG)); # ICON_SOLIDMESH
|
||||
$self->{tree_icons}->Add(Wx::Bitmap->new(Slic3r::var("plugin.png"), wxBITMAP_TYPE_PNG)); # ICON_MODIFIERMESH
|
||||
|
||||
my $rootId = $tree->AddRoot("Object", ICON_OBJECT);
|
||||
$tree->SetPlData($rootId, { type => 'object' });
|
||||
@@ -38,41 +54,116 @@ sub new {
|
||||
# buttons
|
||||
$self->{btn_load_part} = Wx::Button->new($self, -1, "Load part…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_load_modifier} = Wx::Button->new($self, -1, "Load modifier…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_load_lambda_modifier} = Wx::Button->new($self, -1, "Load generic…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_delete} = Wx::Button->new($self, -1, "Delete part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
if ($Slic3r::GUI::have_button_icons) {
|
||||
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG));
|
||||
}
|
||||
$self->{btn_split} = Wx::Button->new($self, -1, "Split part", wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
|
||||
$self->{btn_move_up} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
|
||||
$self->{btn_move_down} = Wx::Button->new($self, -1, "", wxDefaultPosition, [40, -1], wxBU_LEFT);
|
||||
$self->{btn_load_part}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_load_lambda_modifier}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_add.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_delete}->SetBitmap(Wx::Bitmap->new(Slic3r::var("brick_delete.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_split}->SetBitmap(Wx::Bitmap->new(Slic3r::var("shape_ungroup.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_move_up}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_up.png"), wxBITMAP_TYPE_PNG));
|
||||
$self->{btn_move_down}->SetBitmap(Wx::Bitmap->new(Slic3r::var("bullet_arrow_down.png"), wxBITMAP_TYPE_PNG));
|
||||
|
||||
# buttons sizer
|
||||
my $buttons_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
$buttons_sizer->Add($self->{btn_load_part}, 0);
|
||||
$buttons_sizer->Add($self->{btn_load_modifier}, 0);
|
||||
$buttons_sizer->Add($self->{btn_delete}, 0);
|
||||
my $buttons_sizer = Wx::GridSizer->new(2, 3);
|
||||
$buttons_sizer->Add($self->{btn_load_part}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_load_modifier}, 0, wxEXPAND | wxBOTTOM | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_load_lambda_modifier}, 0, wxEXPAND | wxBOTTOM, 5);
|
||||
$buttons_sizer->Add($self->{btn_delete}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
$buttons_sizer->Add($self->{btn_split}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
{
|
||||
my $up_down_sizer = Wx::GridSizer->new(1, 2);
|
||||
$up_down_sizer->Add($self->{btn_move_up}, 0, wxEXPAND | wxRIGHT, 5);
|
||||
$up_down_sizer->Add($self->{btn_move_down}, 0, wxEXPAND, 5);
|
||||
$buttons_sizer->Add($up_down_sizer, 0, wxEXPAND, 5);
|
||||
}
|
||||
$self->{btn_load_part}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_load_modifier}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_load_lambda_modifier}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_delete}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_split}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_move_up}->SetFont($Slic3r::GUI::small_font);
|
||||
$self->{btn_move_down}->SetFont($Slic3r::GUI::small_font);
|
||||
|
||||
# part settings panel
|
||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; });
|
||||
$self->{settings_panel} = Slic3r::GUI::Plater::OverrideSettingsPanel->new($self, on_change => sub { $self->{part_settings_changed} = 1; $self->_update_canvas; });
|
||||
my $settings_sizer = Wx::StaticBoxSizer->new($self->{staticbox} = Wx::StaticBox->new($self, -1, "Part Settings"), wxVERTICAL);
|
||||
$settings_sizer->Add($self->{settings_panel}, 1, wxEXPAND | wxALL, 0);
|
||||
|
||||
|
||||
my $optgroup_movers;
|
||||
$optgroup_movers = $self->{optgroup_movers} = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'Move',
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
# There seems to be an issue with wxWidgets 3.0.2/3.0.3, where the slider
|
||||
# genates tens of events for a single value change.
|
||||
# Only trigger the recalculation if the value changes
|
||||
# or a live preview was activated and the mesh cut is not valid yet.
|
||||
if ($self->{move_options}{$opt_id} != $optgroup_movers->get_value($opt_id)) {
|
||||
$self->{move_options}{$opt_id} = $optgroup_movers->get_value($opt_id);
|
||||
wxTheApp->CallAfter(sub {
|
||||
$self->_update;
|
||||
});
|
||||
}
|
||||
},
|
||||
label_width => 20,
|
||||
);
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'x',
|
||||
type => 'slider',
|
||||
label => 'X',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->x)*4,
|
||||
max => $self->{model_object}->bounding_box->size->x*4,
|
||||
full_width => 1,
|
||||
));
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'y',
|
||||
type => 'slider',
|
||||
label => 'Y',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->y)*4,
|
||||
max => $self->{model_object}->bounding_box->size->y*4,
|
||||
full_width => 1,
|
||||
));
|
||||
$optgroup_movers->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'z',
|
||||
type => 'slider',
|
||||
label => 'Z',
|
||||
default => 0,
|
||||
min => -($self->{model_object}->bounding_box->size->z)*4,
|
||||
max => $self->{model_object}->bounding_box->size->z*4,
|
||||
full_width => 1,
|
||||
));
|
||||
|
||||
# left pane with tree
|
||||
my $left_sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$left_sizer->Add($tree, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($tree, 3, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($buttons_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10);
|
||||
$left_sizer->Add($settings_sizer, 1, wxEXPAND | wxALL, 0);
|
||||
$left_sizer->Add($settings_sizer, 5, wxEXPAND | wxALL, 0);
|
||||
$left_sizer->Add($optgroup_movers->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
# right pane with preview canvas
|
||||
my $canvas;
|
||||
if ($Slic3r::GUI::have_OpenGL) {
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas->set_bounding_box($self->{model_object}->bounding_box);
|
||||
$canvas->set_auto_bed_shape;
|
||||
$canvas->SetSize([500,500]);
|
||||
$canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self);
|
||||
Slic3r::GUI::_3DScene::enable_picking($canvas, 1);
|
||||
Slic3r::GUI::_3DScene::set_select_by($canvas, 'volume');
|
||||
Slic3r::GUI::_3DScene::register_on_select_object_callback($canvas, sub {
|
||||
my ($volume_idx) = @_;
|
||||
$self->reload_tree($volume_idx);
|
||||
});
|
||||
Slic3r::GUI::_3DScene::load_model_object($canvas, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::set_auto_bed_shape($canvas);
|
||||
Slic3r::GUI::_3DScene::set_axes_length($canvas, 2.0 * max(@{ Slic3r::GUI::_3DScene::get_volumes_bounding_box($canvas)->size }));
|
||||
$canvas->SetSize([500,700]);
|
||||
Slic3r::GUI::_3DScene::set_config($canvas, $self->GetParent->GetParent->GetParent->{config});
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($canvas);
|
||||
Slic3r::GUI::_3DScene::enable_force_zoom_to_bed($canvas, 1);
|
||||
}
|
||||
|
||||
$self->{sizer} = Wx::BoxSizer->new(wxHORIZONTAL);
|
||||
@@ -89,11 +180,25 @@ sub new {
|
||||
});
|
||||
EVT_TREE_SEL_CHANGED($self, $tree, sub {
|
||||
my ($self, $event) = @_;
|
||||
return if $self->{disable_tree_sel_changed_event};
|
||||
$self->selection_changed;
|
||||
});
|
||||
EVT_TREE_KEY_DOWN($self, $tree, \&on_tree_key_down);
|
||||
EVT_BUTTON($self, $self->{btn_load_part}, sub { $self->on_btn_load(0) });
|
||||
EVT_BUTTON($self, $self->{btn_load_modifier}, sub { $self->on_btn_load(1) });
|
||||
EVT_BUTTON($self, $self->{btn_load_lambda_modifier}, sub { $self->on_btn_lambda(1) });
|
||||
EVT_BUTTON($self, $self->{btn_delete}, \&on_btn_delete);
|
||||
EVT_BUTTON($self, $self->{btn_split}, \&on_btn_split);
|
||||
EVT_BUTTON($self, $self->{btn_move_up}, \&on_btn_move_up);
|
||||
EVT_BUTTON($self, $self->{btn_move_down}, \&on_btn_move_down);
|
||||
EVT_KEY_DOWN($canvas, sub {
|
||||
my ($canvas, $event) = @_;
|
||||
if ($event->GetKeyCode == WXK_DELETE) {
|
||||
$canvas->GetParent->on_btn_delete;
|
||||
} else {
|
||||
$event->Skip;
|
||||
}
|
||||
});
|
||||
|
||||
$self->reload_tree;
|
||||
|
||||
@@ -101,20 +206,31 @@ sub new {
|
||||
}
|
||||
|
||||
sub reload_tree {
|
||||
my ($self) = @_;
|
||||
my ($self, $selected_volume_idx) = @_;
|
||||
|
||||
$selected_volume_idx //= -1;
|
||||
my $object = $self->{model_object};
|
||||
my $tree = $self->{tree};
|
||||
my $rootId = $tree->GetRootItem;
|
||||
|
||||
# despite wxWidgets states that DeleteChildren "will not generate any events unlike Delete() method",
|
||||
# the MSW implementation of DeleteChildren actually calls Delete() for each item, so
|
||||
# EVT_TREE_SEL_CHANGED is being called, with bad effects (the event handler is called; this
|
||||
# subroutine is never continued; an invisible EndModal is called on the dialog causing Plater
|
||||
# to continue its logic and rescheduling the background process etc. GH #2774)
|
||||
$self->{disable_tree_sel_changed_event} = 1;
|
||||
$tree->DeleteChildren($rootId);
|
||||
$self->{disable_tree_sel_changed_event} = 0;
|
||||
|
||||
my $itemId;
|
||||
my $selectedId = $rootId;
|
||||
foreach my $volume_id (0..$#{$object->volumes}) {
|
||||
my $volume = $object->volumes->[$volume_id];
|
||||
|
||||
my $icon = $volume->modifier ? ICON_MODIFIERMESH : ICON_SOLIDMESH;
|
||||
$itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
|
||||
my $itemId = $tree->AppendItem($rootId, $volume->name || $volume_id, $icon);
|
||||
if ($volume_id == $selected_volume_idx) {
|
||||
$selectedId = $itemId;
|
||||
}
|
||||
$tree->SetPlData($itemId, {
|
||||
type => 'volume',
|
||||
volume_id => $volume_id,
|
||||
@@ -122,10 +238,13 @@ sub reload_tree {
|
||||
}
|
||||
$tree->ExpandAll;
|
||||
|
||||
# select last appended part
|
||||
# This will trigger the selection_changed() event
|
||||
Slic3r::GUI->CallAfter(sub {
|
||||
$self->{tree}->SelectItem($itemId);
|
||||
$self->{tree}->SelectItem($selectedId);
|
||||
|
||||
# SelectItem() should trigger EVT_TREE_SEL_CHANGED as per wxWidgets docs,
|
||||
# but in fact it doesn't if the given item is already selected (this happens
|
||||
# on first load)
|
||||
$self->selection_changed;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -144,43 +263,65 @@ sub selection_changed {
|
||||
|
||||
# deselect all meshes
|
||||
if ($self->{canvas}) {
|
||||
$_->{selected} = 0 for @{$self->{canvas}->volumes};
|
||||
Slic3r::GUI::_3DScene::deselect_volumes($self->{canvas});
|
||||
}
|
||||
|
||||
# disable things as if nothing is selected
|
||||
$self->{btn_delete}->Disable;
|
||||
$self->{'btn_' . $_}->Disable for (qw(delete move_up move_down split));
|
||||
$self->{settings_panel}->disable;
|
||||
$self->{settings_panel}->set_config(undef);
|
||||
|
||||
# reset move sliders
|
||||
$self->{optgroup_movers}->set_value("x", 0);
|
||||
$self->{optgroup_movers}->set_value("y", 0);
|
||||
$self->{optgroup_movers}->set_value("z", 0);
|
||||
$self->{move_options} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
$self->{last_coords} = {
|
||||
x => 0,
|
||||
y => 0,
|
||||
z => 0,
|
||||
};
|
||||
|
||||
if (my $itemData = $self->get_selection) {
|
||||
my ($config, @opt_keys);
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
# select volume in 3D preview
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->volumes->[ $itemData->{volume_id} ]{selected} = 1;
|
||||
Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
|
||||
}
|
||||
$self->{btn_delete}->Enable;
|
||||
$self->{btn_split}->Enable;
|
||||
$self->{btn_move_up}->Enable if $itemData->{volume_id} > 0;
|
||||
$self->{btn_move_down}->Enable if $itemData->{volume_id} + 1 < $self->{model_object}->volumes_count;
|
||||
|
||||
# attach volume config to settings panel
|
||||
my $volume = $self->{model_object}->volumes->[ $itemData->{volume_id} ];
|
||||
|
||||
if ($volume->modifier) {
|
||||
$self->{optgroup_movers}->enable;
|
||||
} else {
|
||||
$self->{optgroup_movers}->disable;
|
||||
}
|
||||
$config = $volume->config;
|
||||
$self->{staticbox}->SetLabel('Part Settings');
|
||||
|
||||
# get default values
|
||||
@opt_keys = @{Slic3r::Config::PrintRegion->new->get_keys};
|
||||
} elsif ($itemData->{type} eq 'object') {
|
||||
# select all object volumes in 3D preview
|
||||
if ($self->{canvas}) {
|
||||
$_->{selected} = 1 for @{$self->{canvas}->volumes};
|
||||
}
|
||||
# select nothing in 3D preview
|
||||
|
||||
# attach object config to settings panel
|
||||
$self->{optgroup_movers}->disable;
|
||||
$self->{staticbox}->SetLabel('Object Settings');
|
||||
@opt_keys = (map @{$_->get_keys}, Slic3r::Config::PrintObject->new, Slic3r::Config::PrintRegion->new);
|
||||
$config = $self->{model_object}->config;
|
||||
}
|
||||
# get default values
|
||||
my $default_config = Slic3r::Config->new_from_defaults(@opt_keys);
|
||||
my $default_config = Slic3r::Config::new_from_defaults_keys(\@opt_keys);
|
||||
|
||||
# append default extruder
|
||||
push @opt_keys, 'extruder';
|
||||
@@ -193,7 +334,7 @@ sub selection_changed {
|
||||
$self->{settings_panel}->enable;
|
||||
}
|
||||
|
||||
$self->{canvas}->Render if $self->{canvas};
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas}) if $self->{canvas};
|
||||
}
|
||||
|
||||
sub on_btn_load {
|
||||
@@ -214,7 +355,7 @@ sub on_btn_load {
|
||||
$new_volume->set_name(basename($input_file));
|
||||
|
||||
# apply the same translation we applied to the object
|
||||
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation}, 0);
|
||||
$new_volume->mesh->translate(@{$self->{model_object}->origin_translation});
|
||||
|
||||
# set a default extruder value, since user can't add it manually
|
||||
$new_volume->config->set_ifndef('extruder', 0);
|
||||
@@ -224,34 +365,134 @@ sub on_btn_load {
|
||||
}
|
||||
}
|
||||
|
||||
$self->reload_tree;
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->load_object($self->{model_object});
|
||||
$self->{canvas}->Render;
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_btn_lambda {
|
||||
my ($self, $is_modifier) = @_;
|
||||
|
||||
my $dlg = Slic3r::GUI::Plater::LambdaObjectDialog->new($self);
|
||||
if ($dlg->ShowModal() == wxID_CANCEL) {
|
||||
return;
|
||||
}
|
||||
my $params = $dlg->ObjectParameter;
|
||||
my $type = "".$params->{"type"};
|
||||
my $name = "lambda-".$params->{"type"};
|
||||
my $mesh;
|
||||
|
||||
if ($type eq "box") {
|
||||
$mesh = Slic3r::TriangleMesh::cube($params->{"dim"}[0], $params->{"dim"}[1], $params->{"dim"}[2]);
|
||||
} elsif ($type eq "cylinder") {
|
||||
$mesh = Slic3r::TriangleMesh::cylinder($params->{"cyl_r"}, $params->{"cyl_h"});
|
||||
} elsif ($type eq "sphere") {
|
||||
$mesh = Slic3r::TriangleMesh::sphere($params->{"sph_rho"});
|
||||
} elsif ($type eq "slab") {
|
||||
$mesh = Slic3r::TriangleMesh::cube($self->{model_object}->bounding_box->size->x*1.5, $self->{model_object}->bounding_box->size->y*1.5, $params->{"slab_h"});
|
||||
# box sets the base coordinate at 0,0, move to center of plate and move it up to initial_z
|
||||
$mesh->translate(-$self->{model_object}->bounding_box->size->x*1.5/2.0, -$self->{model_object}->bounding_box->size->y*1.5/2.0, $params->{"slab_z"});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
$mesh->repair;
|
||||
my $new_volume = $self->{model_object}->add_volume(mesh => $mesh);
|
||||
$new_volume->set_modifier($is_modifier);
|
||||
$new_volume->set_name($name);
|
||||
|
||||
# set a default extruder value, since user can't add it manually
|
||||
$new_volume->config->set_ifndef('extruder', 0);
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_tree_key_down {
|
||||
my ($self, $event) = @_;
|
||||
my $keycode = $event->GetKeyCode;
|
||||
# Wx >= 0.9911
|
||||
if (defined(&Wx::TreeEvent::GetKeyEvent)) {
|
||||
if ($event->GetKeyEvent->GetModifiers & wxMOD_CONTROL) {
|
||||
if ($keycode == WXK_UP) {
|
||||
$event->Skip;
|
||||
$self->on_btn_move_up;
|
||||
} elsif ($keycode == WXK_DOWN) {
|
||||
$event->Skip;
|
||||
$self->on_btn_move_down;
|
||||
}
|
||||
} elsif ($keycode == WXK_DELETE) {
|
||||
$self->on_btn_delete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_move_up {
|
||||
my ($self) = @_;
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume_id = $itemData->{volume_id};
|
||||
if ($self->{model_object}->move_volume_up($volume_id)) {
|
||||
Slic3r::GUI::_3DScene::move_volume_up($self->{canvas}, $volume_id);
|
||||
$self->{parts_changed} = 1;
|
||||
$self->reload_tree($volume_id - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_move_down {
|
||||
my ($self) = @_;
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume_id = $itemData->{volume_id};
|
||||
if ($self->{model_object}->move_volume_down($volume_id)) {
|
||||
Slic3r::GUI::_3DScene::move_volume_down($self->{canvas}, $volume_id);
|
||||
$self->{parts_changed} = 1;
|
||||
$self->reload_tree($volume_id + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub on_btn_delete {
|
||||
my ($self) = @_;
|
||||
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
|
||||
|
||||
# if user is deleting the last solid part, throw error
|
||||
if (!$volume->modifier && scalar(grep !$_->modifier, @{$self->{model_object}->volumes}) == 1) {
|
||||
Slic3r::GUI::show_error($self, "You can't delete the last solid part from this object.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$self->{model_object}->delete_volume($itemData->{volume_id});
|
||||
$self->{parts_changed} = 1;
|
||||
}
|
||||
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub on_btn_split {
|
||||
my ($self) = @_;
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
my $nozzle_dmrs = $self->GetParent->GetParent->GetParent->{config}->get('nozzle_diameter');
|
||||
$self->{parts_changed} = 1 if $volume->split(scalar(@$nozzle_dmrs)) > 1;
|
||||
}
|
||||
|
||||
$self->_parts_changed;
|
||||
}
|
||||
|
||||
sub _parts_changed {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->reload_tree;
|
||||
if ($self->{canvas}) {
|
||||
$self->{canvas}->load_object($self->{model_object});
|
||||
$self->{canvas}->Render;
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
Slic3r::GUI::_3DScene::zoom_to_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,12 +504,17 @@ sub CanClose {
|
||||
# validate options before allowing user to dismiss the dialog
|
||||
# the validate method only works on full configs so we have
|
||||
# to merge our settings with the default ones
|
||||
my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config);
|
||||
my $config = $self->GetParent->GetParent->GetParent->GetParent->GetParent->config->clone;
|
||||
eval {
|
||||
$config->apply($self->model_object->config);
|
||||
$config->validate;
|
||||
};
|
||||
return 0 if Slic3r::GUI::catch_error($self);
|
||||
return 1;
|
||||
return ! Slic3r::GUI::catch_error($self);
|
||||
}
|
||||
|
||||
sub Destroy {
|
||||
my ($self) = @_;
|
||||
$self->{canvas}->Destroy if ($self->{canvas});
|
||||
}
|
||||
|
||||
sub PartsChanged {
|
||||
@@ -281,4 +527,47 @@ sub PartSettingsChanged {
|
||||
return $self->{part_settings_changed};
|
||||
}
|
||||
|
||||
sub _update_canvas {
|
||||
my ($self) = @_;
|
||||
|
||||
if ($self->{canvas}) {
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $self->{model_object}, 0, [0]);
|
||||
|
||||
# restore selection, if any
|
||||
if (my $itemData = $self->get_selection) {
|
||||
if ($itemData->{type} eq 'volume') {
|
||||
Slic3r::GUI::_3DScene::select_volume($self->{canvas}, $itemData->{volume_id});
|
||||
}
|
||||
}
|
||||
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
}
|
||||
|
||||
sub _update {
|
||||
my ($self) = @_;
|
||||
my ($m_x, $m_y, $m_z) = ($self->{move_options}{x}, $self->{move_options}{y}, $self->{move_options}{z});
|
||||
my ($l_x, $l_y, $l_z) = ($self->{last_coords}{x}, $self->{last_coords}{y}, $self->{last_coords}{z});
|
||||
|
||||
my $itemData = $self->get_selection;
|
||||
if ($itemData && $itemData->{type} eq 'volume') {
|
||||
my $d = Slic3r::Pointf3->new($m_x - $l_x, $m_y - $l_y, $m_z - $l_z);
|
||||
my $volume = $self->{model_object}->volumes->[$itemData->{volume_id}];
|
||||
$volume->mesh->translate(@{$d});
|
||||
$self->{last_coords}{x} = $m_x;
|
||||
$self->{last_coords}{y} = $m_y;
|
||||
$self->{last_coords}{z} = $m_z;
|
||||
}
|
||||
|
||||
$self->{parts_changed} = 1;
|
||||
my @objects = ();
|
||||
push @objects, $self->{model_object};
|
||||
Slic3r::GUI::_3DScene::reset_volumes($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::load_model_object($self->{canvas}, $_, 0, [0]) for @objects;
|
||||
Slic3r::GUI::_3DScene::update_volumes_colors_by_extruder($self->{canvas});
|
||||
Slic3r::GUI::_3DScene::render($self->{canvas});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package Slic3r::GUI::Plater::ObjectPreviewDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL);
|
||||
use Wx::Event qw(EVT_CLOSE);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{model_object} = $params{model_object};
|
||||
|
||||
my $canvas = Slic3r::GUI::PreviewCanvas->new($self);
|
||||
$canvas->load_object($self->{model_object});
|
||||
$canvas->set_bounding_box($self->{model_object}->bounding_box);
|
||||
$canvas->set_auto_bed_shape;
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($canvas, 1, wxEXPAND, 0);
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
# needed to actually free memory
|
||||
EVT_CLOSE($self, sub {
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,18 +1,24 @@
|
||||
# This dialog opens up when double clicked on an object line in the list at the right side of the platter.
|
||||
# One may load additional STLs and additional modifier STLs,
|
||||
# one may change the properties of the print per each modifier mesh or a Z-span.
|
||||
|
||||
package Slic3r::GUI::Plater::ObjectSettingsDialog;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL);
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
# Called with
|
||||
# %params{object} of a Perl type Slic3r::GUI::Plater::Object
|
||||
# %params{model_object} of a C++ type Slic3r::ModelObject
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Settings for " . $params{object}->name, wxDefaultPosition, [700,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
|
||||
$self->{$_} = $params{$_} for keys %params;
|
||||
|
||||
|
||||
$self->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
|
||||
$self->{tabpanel}->AddPage($self->{parts} = Slic3r::GUI::Plater::ObjectPartsPanel->new($self->{tabpanel}, model_object => $params{model_object}), "Parts");
|
||||
$self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}), "Layers");
|
||||
@@ -26,7 +32,11 @@ sub new {
|
||||
# notify tabs
|
||||
$self->{layers}->Closing;
|
||||
|
||||
# save window size
|
||||
wxTheApp->save_window_pos($self, "object_settings");
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->{parts}->Destroy;
|
||||
$self->Destroy;
|
||||
});
|
||||
|
||||
@@ -37,6 +47,10 @@ sub new {
|
||||
$self->SetSizer($sizer);
|
||||
$self->SetMinSize($self->GetSize);
|
||||
|
||||
$self->Layout;
|
||||
|
||||
wxTheApp->restore_window_pos($self, "object_settings");
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
@@ -55,6 +69,7 @@ use base 'Wx::Panel';
|
||||
|
||||
sub model_object {
|
||||
my ($self) = @_;
|
||||
# $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
|
||||
return $self->GetParent->GetParent->{model_object};
|
||||
}
|
||||
|
||||
@@ -72,7 +87,7 @@ sub new {
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
|
||||
{
|
||||
my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object. Set layer height to zero to skip portions of the input file.",
|
||||
my $label = Wx::StaticText->new($self, -1, "You can use this section to override the default layer height for parts of this object.",
|
||||
wxDefaultPosition, [-1, 40]);
|
||||
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
$sizer->Add($label, 0, wxEXPAND | wxALL, 10);
|
||||
@@ -82,7 +97,7 @@ sub new {
|
||||
$sizer->Add($grid, 1, wxEXPAND | wxALL, 10);
|
||||
$grid->CreateGrid(0, 3);
|
||||
$grid->DisableDragRowSize;
|
||||
$grid->HideRowLabels if &Wx::wxVERSION_STRING !~ / 2\.8\./;
|
||||
$grid->HideRowLabels;
|
||||
$grid->SetColLabelValue(0, "Min Z (mm)");
|
||||
$grid->SetColLabelValue(1, "Max Z (mm)");
|
||||
$grid->SetColLabelValue(2, "Layer height (mm)");
|
||||
@@ -104,15 +119,20 @@ sub new {
|
||||
my $value = $grid->GetCellValue($event->GetRow, $event->GetCol);
|
||||
$value =~ s/,/./g;
|
||||
$value =~ s/[^0-9.]//g;
|
||||
$grid->SetCellValue($event->GetRow, $event->GetCol, $value);
|
||||
$grid->SetCellValue($event->GetRow, $event->GetCol, ($event->GetCol == 2) ? $self->_clamp_layer_height($value) : $value);
|
||||
|
||||
# if there's no empty row, let's append one
|
||||
for my $i (0 .. $grid->GetNumberRows-1) {
|
||||
for my $i (0 .. $grid->GetNumberRows) {
|
||||
if ($i == $grid->GetNumberRows) {
|
||||
# if we're here then we found no empty row
|
||||
$grid->AppendRows(1);
|
||||
last;
|
||||
}
|
||||
if (!grep $grid->GetCellValue($i, $_), 0..2) {
|
||||
return;
|
||||
# exit loop if this row is empty
|
||||
last;
|
||||
}
|
||||
}
|
||||
$grid->AppendRows(1);
|
||||
|
||||
$self->{layers_changed} = 1;
|
||||
});
|
||||
@@ -123,6 +143,35 @@ sub new {
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _clamp_layer_height
|
||||
{
|
||||
my ($self, $value) = @_;
|
||||
# $self->GetParent->GetParent is of type Slic3r::GUI::Plater::ObjectSettingsDialog
|
||||
my $config = $self->GetParent->GetParent->{config};
|
||||
if ($value =~ /^[0-9,.E]+$/) {
|
||||
# Looks like a number. Validate the layer height.
|
||||
my $nozzle_dmrs = $config->get('nozzle_diameter');
|
||||
my $min_layer_heights = $config->get('min_layer_height');
|
||||
my $max_layer_heights = $config->get('max_layer_height');
|
||||
my $min_layer_height = 1000.;
|
||||
my $max_layer_height = 0.;
|
||||
my $max_nozzle_dmr = 0.;
|
||||
for (my $i = 0; $i < int(@{$nozzle_dmrs}); $i += 1) {
|
||||
$min_layer_height = $min_layer_heights->[$i] if ($min_layer_heights->[$i] < $min_layer_height);
|
||||
$max_layer_height = $max_layer_heights->[$i] if ($max_layer_heights->[$i] > $max_layer_height);
|
||||
$max_nozzle_dmr = $nozzle_dmrs ->[$i] if ($nozzle_dmrs ->[$i] > $max_nozzle_dmr );
|
||||
}
|
||||
$min_layer_height = 0.005 if ($min_layer_height < 0.005);
|
||||
$max_layer_height = $max_nozzle_dmr * 0.75 if ($max_layer_height == 0.);
|
||||
$max_layer_height = $max_nozzle_dmr if ($max_layer_height > $max_nozzle_dmr);
|
||||
return ($value < $min_layer_height) ? $min_layer_height :
|
||||
($value > $max_layer_height) ? $max_layer_height : $value;
|
||||
} else {
|
||||
# If an invalid numeric value has been entered, use the default layer height.
|
||||
return $config->get('layer_height');
|
||||
}
|
||||
}
|
||||
|
||||
sub CanClose {
|
||||
my $self = shift;
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# Included in ObjectSettingsDialog -> ObjectPartsPanel.
|
||||
# Maintains, displays, adds and removes overrides of slicing parameters for an object and its modifier mesh.
|
||||
|
||||
package Slic3r::GUI::Plater::OverrideSettingsPanel;
|
||||
use strict;
|
||||
use warnings;
|
||||
@@ -13,12 +16,25 @@ use constant ICON_MATERIAL => 0;
|
||||
use constant ICON_SOLIDMESH => 1;
|
||||
use constant ICON_MODIFIERMESH => 2;
|
||||
|
||||
my %icons = (
|
||||
'Advanced' => 'wand.png',
|
||||
'Extruders' => 'funnel.png',
|
||||
'Extrusion Width' => 'funnel.png',
|
||||
'Infill' => 'infill.png',
|
||||
'Layers and Perimeters' => 'layers.png',
|
||||
'Skirt and brim' => 'box.png',
|
||||
'Speed' => 'time.png',
|
||||
'Speed > Acceleration' => 'time.png',
|
||||
'Support material' => 'building.png',
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my ($class, $parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
||||
# C++ class Slic3r::DynamicPrintConfig, initially empty.
|
||||
$self->{default_config} = Slic3r::Config->new;
|
||||
$self->{config} = Slic3r::Config->new;
|
||||
# On change callback.
|
||||
$self->{on_change} = $params{on_change};
|
||||
$self->{fixed_options} = {};
|
||||
|
||||
@@ -30,18 +46,33 @@ sub new {
|
||||
# option selector
|
||||
{
|
||||
# create the button
|
||||
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG),
|
||||
my $btn = $self->{btn_add} = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("add.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
|
||||
EVT_LEFT_DOWN($btn, sub {
|
||||
my $menu = Wx::Menu->new;
|
||||
# create category submenus
|
||||
my %categories = (); # category => submenu
|
||||
foreach my $opt_key (@{$self->{options}}) {
|
||||
my $id = &Wx::NewId();
|
||||
$menu->Append($id, $self->{option_labels}{$opt_key});
|
||||
EVT_MENU($menu, $id, sub {
|
||||
if (my $cat = $Slic3r::Config::Options->{$opt_key}{category}) {
|
||||
$categories{$cat} //= Wx::Menu->new;
|
||||
}
|
||||
}
|
||||
# append submenus to main menu
|
||||
my @categories = ('Layers and Perimeters', 'Infill', 'Support material', 'Speed', 'Extruders', 'Extrusion Width', 'Advanced');
|
||||
#foreach my $cat (sort keys %categories) {
|
||||
foreach my $cat (@categories) {
|
||||
wxTheApp->append_submenu($menu, $cat, "", $categories{$cat}, undef, $icons{$cat});
|
||||
}
|
||||
# append options to submenus
|
||||
foreach my $opt_key (@{$self->{options}}) {
|
||||
my $cat = $Slic3r::Config::Options->{$opt_key}{category} or next;
|
||||
my $cb = sub {
|
||||
$self->{config}->set($opt_key, $self->{default_config}->get($opt_key));
|
||||
$self->update_optgroup;
|
||||
$self->{on_change}->() if $self->{on_change};
|
||||
});
|
||||
$self->{on_change}->($opt_key) if $self->{on_change};
|
||||
};
|
||||
wxTheApp->append_menu_item($categories{$cat}, $self->{option_labels}{$opt_key},
|
||||
$Slic3r::Config::Options->{$opt_key}{tooltip}, $cb);
|
||||
}
|
||||
$self->PopupMenu($menu, $btn->GetPosition);
|
||||
$menu->Destroy;
|
||||
@@ -74,11 +105,8 @@ sub set_config {
|
||||
|
||||
sub set_opt_keys {
|
||||
my ($self, $opt_keys) = @_;
|
||||
|
||||
# sort options by category+label
|
||||
$self->{option_labels} = {
|
||||
map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } @$opt_keys
|
||||
};
|
||||
$self->{option_labels} = { map { $_ => $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label} } @$opt_keys };
|
||||
$self->{options} = [ sort { $self->{option_labels}{$a} cmp $self->{option_labels}{$b} } @$opt_keys ];
|
||||
}
|
||||
|
||||
@@ -118,7 +146,7 @@ sub update_optgroup {
|
||||
# disallow deleting fixed options
|
||||
return undef if $self->{fixed_options}{$opt_key};
|
||||
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG),
|
||||
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new(Slic3r::var("delete.png"), wxBITMAP_TYPE_PNG),
|
||||
wxDefaultPosition, wxDefaultSize, Wx::wxBORDER_NONE);
|
||||
EVT_BUTTON($self, $btn, sub {
|
||||
$self->{config}->erase($opt_key);
|
||||
@@ -133,7 +161,7 @@ sub update_optgroup {
|
||||
}
|
||||
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 0);
|
||||
}
|
||||
$self->Layout;
|
||||
$self->GetParent->Layout; # we need this for showing scrollbars
|
||||
}
|
||||
|
||||
# work around a wxMAC bug causing controls not being disabled when calling Disable() on a Window
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package Slic3r::GUI::Preferences;
|
||||
use Wx qw(:dialog :id :misc :sizer :systemsettings wxTheApp);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_TEXT_ENTER);
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, "Preferences", wxDefaultPosition, [500,200]);
|
||||
$self->{values} = {};
|
||||
|
||||
my $optgroup;
|
||||
$optgroup = Slic3r::GUI::OptionsGroup->new(
|
||||
parent => $self,
|
||||
title => 'General',
|
||||
on_change => sub {
|
||||
my ($opt_id) = @_;
|
||||
$self->{values}{$opt_id} = $optgroup->get_value($opt_id);
|
||||
},
|
||||
label_width => 100,
|
||||
);
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'mode',
|
||||
type => 'select',
|
||||
label => 'Mode',
|
||||
tooltip => 'Choose between a simpler, basic mode and an expert mode with more options and more complicated interface.',
|
||||
labels => ['Simple','Expert'],
|
||||
values => ['simple','expert'],
|
||||
default => $Slic3r::GUI::Settings->{_}{mode},
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'version_check',
|
||||
type => 'bool',
|
||||
label => 'Check for updates',
|
||||
tooltip => 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
|
||||
default => $Slic3r::GUI::Settings->{_}{version_check} // 1,
|
||||
readonly => !wxTheApp->have_version_check,
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'remember_output_path',
|
||||
type => 'bool',
|
||||
label => 'Remember output directory',
|
||||
tooltip => 'If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files.',
|
||||
default => $Slic3r::GUI::Settings->{_}{remember_output_path},
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'autocenter',
|
||||
type => 'bool',
|
||||
label => 'Auto-center parts',
|
||||
tooltip => 'If this is enabled, Slic3r will auto-center objects around the print bed center.',
|
||||
default => $Slic3r::GUI::Settings->{_}{autocenter},
|
||||
));
|
||||
$optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
|
||||
opt_id => 'background_processing',
|
||||
type => 'bool',
|
||||
label => 'Background processing',
|
||||
tooltip => 'If this is enabled, Slic3r will pre-process objects as soon as they\'re loaded in order to save time when exporting G-code.',
|
||||
default => $Slic3r::GUI::Settings->{_}{background_processing},
|
||||
readonly => !$Slic3r::have_threads,
|
||||
));
|
||||
|
||||
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$sizer->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
EVT_BUTTON($self, wxID_OK, sub { $self->_accept });
|
||||
$sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
$self->SetSizer($sizer);
|
||||
$sizer->SetSizeHints($self);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub _accept {
|
||||
my $self = shift;
|
||||
|
||||
if ($self->{values}{mode}) {
|
||||
Slic3r::GUI::warning_catcher($self)->("You need to restart Slic3r to make the changes effective.");
|
||||
}
|
||||
|
||||
$Slic3r::GUI::Settings->{_}{$_} = $self->{values}{$_} for keys %{$self->{values}};
|
||||
wxTheApp->save_settings;
|
||||
|
||||
$self->EndModal(wxID_OK);
|
||||
$self->Close; # needed on Linux
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,642 +0,0 @@
|
||||
package Slic3r::GUI::PreviewCanvas;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS);
|
||||
# must load OpenGL *before* Wx::GLCanvas
|
||||
use OpenGL qw(:glconstants :glfunctions :glufunctions);
|
||||
use base qw(Wx::GLCanvas Class::Accessor);
|
||||
use Math::Trig qw(asin);
|
||||
use List::Util qw(reduce min max first);
|
||||
use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan scale unscale);
|
||||
use Slic3r::Geometry::Clipper qw(offset_ex intersection_pl);
|
||||
use Wx::GLCanvas qw(:all);
|
||||
|
||||
__PACKAGE__->mk_accessors( qw(quat dirty init mview_init
|
||||
object_bounding_box
|
||||
volumes initpos
|
||||
sphi stheta
|
||||
cutting_plane_z
|
||||
cut_lines_vertices
|
||||
bed_triangles
|
||||
bed_grid_lines
|
||||
origin
|
||||
) );
|
||||
|
||||
use constant TRACKBALLSIZE => 0.8;
|
||||
use constant TURNTABLE_MODE => 1;
|
||||
use constant GROUND_Z => 0.02;
|
||||
use constant SELECTED_COLOR => [0,1,0,1];
|
||||
use constant COLORS => [ [1,1,0], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ];
|
||||
|
||||
# make OpenGL::Array thread-safe
|
||||
{
|
||||
no warnings 'redefine';
|
||||
*OpenGL::Array::CLONE_SKIP = sub { 1 };
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ($class, $parent) = @_;
|
||||
my $self = $class->SUPER::new($parent);
|
||||
|
||||
$self->quat((0, 0, 0, 1));
|
||||
$self->sphi(45);
|
||||
$self->stheta(-45);
|
||||
|
||||
$self->reset_objects;
|
||||
|
||||
EVT_PAINT($self, sub {
|
||||
my $dc = Wx::PaintDC->new($self);
|
||||
return if !$self->object_bounding_box;
|
||||
$self->Render($dc);
|
||||
});
|
||||
EVT_SIZE($self, sub { $self->dirty(1) });
|
||||
EVT_IDLE($self, sub {
|
||||
return unless $self->dirty;
|
||||
return if !$self->IsShownOnScreen;
|
||||
return if !$self->object_bounding_box;
|
||||
$self->Resize( $self->GetSizeWH );
|
||||
$self->Refresh;
|
||||
});
|
||||
EVT_MOUSEWHEEL($self, sub {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
my $zoom = ($e->GetWheelRotation() / $e->GetWheelDelta() / 10);
|
||||
$zoom = $zoom > 0 ? (1.0 + $zoom) : 1 / (1.0 - $zoom);
|
||||
my @pos3d = $self->mouse_to_3d($e->GetX(), $e->GetY());
|
||||
$self->ZoomTo($zoom, $pos3d[0], $pos3d[1]);
|
||||
|
||||
$self->Refresh;
|
||||
});
|
||||
EVT_MOUSE_EVENTS($self, sub {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
if ($e->Dragging() && $e->LeftIsDown()) {
|
||||
$self->handle_rotation($e);
|
||||
} elsif ($e->Dragging() && $e->RightIsDown()) {
|
||||
$self->handle_translation($e);
|
||||
} elsif ($e->LeftUp() || $e->RightUp()) {
|
||||
$self->initpos(undef);
|
||||
} else {
|
||||
$e->Skip();
|
||||
}
|
||||
});
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub reset_objects {
|
||||
my ($self) = @_;
|
||||
|
||||
$self->volumes([]);
|
||||
$self->dirty(1);
|
||||
}
|
||||
|
||||
# this method accepts a Slic3r::BoudingBox3f object
|
||||
sub set_bounding_box {
|
||||
my ($self, $bb) = @_;
|
||||
|
||||
$self->object_bounding_box($bb);
|
||||
$self->dirty(1);
|
||||
}
|
||||
|
||||
sub set_auto_bed_shape {
|
||||
my ($self, $bed_shape) = @_;
|
||||
|
||||
# draw a default square bed around object center
|
||||
my $max_size = max(@{ $self->object_bounding_box->size });
|
||||
my $center = $self->object_bounding_box->center;
|
||||
$self->set_bed_shape([
|
||||
[ $center->x - $max_size, $center->y - $max_size ], #--
|
||||
[ $center->x + $max_size, $center->y - $max_size ], #--
|
||||
[ $center->x + $max_size, $center->y + $max_size ], #++
|
||||
[ $center->x - $max_size, $center->y + $max_size ], #++
|
||||
]);
|
||||
$self->origin(Slic3r::Pointf->new(@$center[X,Y]));
|
||||
}
|
||||
|
||||
sub set_bed_shape {
|
||||
my ($self, $bed_shape) = @_;
|
||||
|
||||
# triangulate bed
|
||||
my $expolygon = Slic3r::ExPolygon->new([ map [map scale($_), @$_], @$bed_shape ]);
|
||||
my $bed_bb = $expolygon->bounding_box;
|
||||
|
||||
{
|
||||
my @points = ();
|
||||
foreach my $triangle (@{ $expolygon->triangulate }) {
|
||||
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$triangle; #))
|
||||
}
|
||||
$self->bed_triangles(OpenGL::Array->new_list(GL_FLOAT, @points));
|
||||
}
|
||||
|
||||
{
|
||||
my @lines = ();
|
||||
for (my $x = $bed_bb->x_min; $x <= $bed_bb->x_max; $x += scale 10) {
|
||||
push @lines, Slic3r::Polyline->new([$x,$bed_bb->y_min], [$x,$bed_bb->y_max]);
|
||||
}
|
||||
for (my $y = $bed_bb->y_min; $y <= $bed_bb->y_max; $y += scale 10) {
|
||||
push @lines, Slic3r::Polyline->new([$bed_bb->x_min,$y], [$bed_bb->x_max,$y]);
|
||||
}
|
||||
@lines = @{intersection_pl(\@lines, [ @$expolygon ])};
|
||||
my @points = ();
|
||||
foreach my $polyline (@lines) {
|
||||
push @points, map {+ unscale($_->x), unscale($_->y), GROUND_Z } @$polyline; #))
|
||||
}
|
||||
$self->bed_grid_lines(OpenGL::Array->new_list(GL_FLOAT, @points));
|
||||
}
|
||||
|
||||
$self->origin(Slic3r::Pointf->new(0,0));
|
||||
}
|
||||
|
||||
sub load_object {
|
||||
my ($self, $object, $all_instances) = @_;
|
||||
|
||||
my $z_min = $object->raw_bounding_box->z_min;
|
||||
|
||||
# color mesh(es) by material
|
||||
my @materials = ();
|
||||
|
||||
# sort volumes: non-modifiers first
|
||||
my @volumes = sort { ($a->modifier // 0) <=> ($b->modifier // 0) } @{$object->volumes};
|
||||
foreach my $volume (@volumes) {
|
||||
my @instances = $all_instances ? @{$object->instances} : $object->instances->[0];
|
||||
foreach my $instance (@instances) {
|
||||
my $mesh = $volume->mesh->clone;
|
||||
$instance->transform_mesh($mesh);
|
||||
|
||||
my $material_id = $volume->material_id // '_';
|
||||
my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;
|
||||
if (!defined $color_idx) {
|
||||
push @materials, $material_id;
|
||||
$color_idx = $#materials;
|
||||
}
|
||||
|
||||
my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ];
|
||||
push @$color, $volume->modifier ? 0.5 : 1;
|
||||
push @{$self->volumes}, my $v = {
|
||||
mesh => $mesh,
|
||||
color => $color,
|
||||
z_min => $z_min,
|
||||
};
|
||||
|
||||
{
|
||||
my $vertices = $mesh->vertices;
|
||||
my @verts = map @{ $vertices->[$_] }, map @$_, @{$mesh->facets};
|
||||
$v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts);
|
||||
}
|
||||
|
||||
{
|
||||
my @norms = map { @$_, @$_, @$_ } @{$mesh->normals};
|
||||
$v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub SetCuttingPlane {
|
||||
my ($self, $z) = @_;
|
||||
|
||||
$self->cutting_plane_z($z);
|
||||
|
||||
# perform cut and cache section lines
|
||||
my @verts = ();
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
my $expolygons = $volume->{mesh}->slice([ $z + $volume->{z_min} ])->[0];
|
||||
$expolygons = offset_ex([ map @$_, @$expolygons ], scale 0.1);
|
||||
|
||||
foreach my $line (map @{$_->lines}, map @$_, @$expolygons) {
|
||||
push @verts, (
|
||||
unscale($line->a->x), unscale($line->a->y), $z, #))
|
||||
unscale($line->b->x), unscale($line->b->y), $z, #))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->cut_lines_vertices(OpenGL::Array->new_list(GL_FLOAT, @verts));
|
||||
}
|
||||
|
||||
# Given an axis and angle, compute quaternion.
|
||||
sub axis_to_quat {
|
||||
my ($ax, $phi) = @_;
|
||||
|
||||
my $lena = sqrt(reduce { $a + $b } (map { $_ * $_ } @$ax));
|
||||
my @q = map { $_ * (1 / $lena) } @$ax;
|
||||
@q = map { $_ * sin($phi / 2.0) } @q;
|
||||
$q[$#q + 1] = cos($phi / 2.0);
|
||||
return @q;
|
||||
}
|
||||
|
||||
# Project a point on the virtual trackball.
|
||||
# If it is inside the sphere, map it to the sphere, if it outside map it
|
||||
# to a hyperbola.
|
||||
sub project_to_sphere {
|
||||
my ($r, $x, $y) = @_;
|
||||
|
||||
my $d = sqrt($x * $x + $y * $y);
|
||||
if ($d < $r * 0.70710678118654752440) { # Inside sphere
|
||||
return sqrt($r * $r - $d * $d);
|
||||
} else { # On hyperbola
|
||||
my $t = $r / 1.41421356237309504880;
|
||||
return $t * $t / $d;
|
||||
}
|
||||
}
|
||||
|
||||
sub cross {
|
||||
my ($v1, $v2) = @_;
|
||||
|
||||
return (@$v1[1] * @$v2[2] - @$v1[2] * @$v2[1],
|
||||
@$v1[2] * @$v2[0] - @$v1[0] * @$v2[2],
|
||||
@$v1[0] * @$v2[1] - @$v1[1] * @$v2[0]);
|
||||
}
|
||||
|
||||
# Simulate a track-ball. Project the points onto the virtual trackball,
|
||||
# then figure out the axis of rotation, which is the cross product of
|
||||
# P1 P2 and O P1 (O is the center of the ball, 0,0,0) Note: This is a
|
||||
# deformed trackball-- is a trackball in the center, but is deformed
|
||||
# into a hyperbolic sheet of rotation away from the center.
|
||||
# It is assumed that the arguments to this routine are in the range
|
||||
# (-1.0 ... 1.0).
|
||||
sub trackball {
|
||||
my ($p1x, $p1y, $p2x, $p2y) = @_;
|
||||
|
||||
if ($p1x == $p2x && $p1y == $p2y) {
|
||||
# zero rotation
|
||||
return (0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
# First, figure out z-coordinates for projection of P1 and P2 to
|
||||
# deformed sphere
|
||||
my @p1 = ($p1x, $p1y, project_to_sphere(TRACKBALLSIZE, $p1x, $p1y));
|
||||
my @p2 = ($p2x, $p2y, project_to_sphere(TRACKBALLSIZE, $p2x, $p2y));
|
||||
|
||||
# axis of rotation (cross product of P1 and P2)
|
||||
my @a = cross(\@p2, \@p1);
|
||||
|
||||
# Figure out how much to rotate around that axis.
|
||||
my @d = map { $_ * $_ } (map { $p1[$_] - $p2[$_] } 0 .. $#p1);
|
||||
my $t = sqrt(reduce { $a + $b } @d) / (2.0 * TRACKBALLSIZE);
|
||||
|
||||
# Avoid problems with out-of-control values...
|
||||
$t = 1.0 if ($t > 1.0);
|
||||
$t = -1.0 if ($t < -1.0);
|
||||
my $phi = 2.0 * asin($t);
|
||||
|
||||
return axis_to_quat(\@a, $phi);
|
||||
}
|
||||
|
||||
# Build a rotation matrix, given a quaternion rotation.
|
||||
sub quat_to_rotmatrix {
|
||||
my ($q) = @_;
|
||||
|
||||
my @m = ();
|
||||
|
||||
$m[0] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[2] * @$q[2]);
|
||||
$m[1] = 2.0 * (@$q[0] * @$q[1] - @$q[2] * @$q[3]);
|
||||
$m[2] = 2.0 * (@$q[2] * @$q[0] + @$q[1] * @$q[3]);
|
||||
$m[3] = 0.0;
|
||||
|
||||
$m[4] = 2.0 * (@$q[0] * @$q[1] + @$q[2] * @$q[3]);
|
||||
$m[5] = 1.0 - 2.0 * (@$q[2] * @$q[2] + @$q[0] * @$q[0]);
|
||||
$m[6] = 2.0 * (@$q[1] * @$q[2] - @$q[0] * @$q[3]);
|
||||
$m[7] = 0.0;
|
||||
|
||||
$m[8] = 2.0 * (@$q[2] * @$q[0] - @$q[1] * @$q[3]);
|
||||
$m[9] = 2.0 * (@$q[1] * @$q[2] + @$q[0] * @$q[3]);
|
||||
$m[10] = 1.0 - 2.0 * (@$q[1] * @$q[1] + @$q[0] * @$q[0]);
|
||||
$m[11] = 0.0;
|
||||
|
||||
$m[12] = 0.0;
|
||||
$m[13] = 0.0;
|
||||
$m[14] = 0.0;
|
||||
$m[15] = 1.0;
|
||||
|
||||
return @m;
|
||||
}
|
||||
|
||||
sub mulquats {
|
||||
my ($q1, $rq) = @_;
|
||||
|
||||
return (@$q1[3] * @$rq[0] + @$q1[0] * @$rq[3] + @$q1[1] * @$rq[2] - @$q1[2] * @$rq[1],
|
||||
@$q1[3] * @$rq[1] + @$q1[1] * @$rq[3] + @$q1[2] * @$rq[0] - @$q1[0] * @$rq[2],
|
||||
@$q1[3] * @$rq[2] + @$q1[2] * @$rq[3] + @$q1[0] * @$rq[1] - @$q1[1] * @$rq[0],
|
||||
@$q1[3] * @$rq[3] - @$q1[0] * @$rq[0] - @$q1[1] * @$rq[1] - @$q1[2] * @$rq[2])
|
||||
}
|
||||
|
||||
sub handle_rotation {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
if (not defined $self->initpos) {
|
||||
$self->initpos($e->GetPosition());
|
||||
} else {
|
||||
my $orig = $self->initpos;
|
||||
my $new = $e->GetPosition();
|
||||
my $size = $self->GetClientSize();
|
||||
if (TURNTABLE_MODE) {
|
||||
$self->sphi($self->sphi + ($new->x - $orig->x)*TRACKBALLSIZE);
|
||||
$self->stheta($self->stheta + ($new->y - $orig->y)*TRACKBALLSIZE); #-
|
||||
} else {
|
||||
my @quat = trackball($orig->x / ($size->width / 2) - 1,
|
||||
1 - $orig->y / ($size->height / 2), #/
|
||||
$new->x / ($size->width / 2) - 1,
|
||||
1 - $new->y / ($size->height / 2), #/
|
||||
);
|
||||
$self->quat(mulquats($self->quat, \@quat));
|
||||
}
|
||||
$self->initpos($new);
|
||||
$self->Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
sub handle_translation {
|
||||
my ($self, $e) = @_;
|
||||
|
||||
if (not defined $self->initpos) {
|
||||
$self->initpos($e->GetPosition());
|
||||
} else {
|
||||
my $new = $e->GetPosition();
|
||||
my $orig = $self->initpos;
|
||||
my @orig3d = $self->mouse_to_3d($orig->x, $orig->y); #)()
|
||||
my @new3d = $self->mouse_to_3d($new->x, $new->y); #)()
|
||||
glTranslatef($new3d[0] - $orig3d[0], $new3d[1] - $orig3d[1], 0);
|
||||
$self->initpos($new);
|
||||
$self->Refresh;
|
||||
}
|
||||
}
|
||||
|
||||
sub mouse_to_3d {
|
||||
my ($self, $x, $y) = @_;
|
||||
|
||||
my @viewport = glGetIntegerv_p(GL_VIEWPORT); # 4 items
|
||||
my @mview = glGetDoublev_p(GL_MODELVIEW_MATRIX); # 16 items
|
||||
my @proj = glGetDoublev_p(GL_PROJECTION_MATRIX); # 16 items
|
||||
|
||||
my @projected = gluUnProject_p($x, $viewport[3] - $y, 1.0, @mview, @proj, @viewport);
|
||||
return @projected;
|
||||
}
|
||||
|
||||
sub ZoomTo {
|
||||
my ($self, $factor, $tox, $toy) = @_;
|
||||
|
||||
return if !$self->init;
|
||||
glTranslatef($tox, $toy, 0);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
$self->Zoom($factor);
|
||||
glTranslatef(-$tox, -$toy, 0);
|
||||
}
|
||||
|
||||
sub Zoom {
|
||||
my ($self, $factor) = @_;
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glScalef($factor, $factor, 1);
|
||||
}
|
||||
|
||||
sub GetContext {
|
||||
my ($self) = @_;
|
||||
|
||||
if (Wx::wxVERSION >= 2.009) {
|
||||
return $self->{context} ||= Wx::GLContext->new($self);
|
||||
} else {
|
||||
return $self->SUPER::GetContext;
|
||||
}
|
||||
}
|
||||
|
||||
sub SetCurrent {
|
||||
my ($self, $context) = @_;
|
||||
|
||||
if (Wx::wxVERSION >= 2.009) {
|
||||
return $self->SUPER::SetCurrent($context);
|
||||
} else {
|
||||
return $self->SUPER::SetCurrent;
|
||||
}
|
||||
}
|
||||
|
||||
sub ResetModelView {
|
||||
my ($self, $factor) = @_;
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
my $win_size = $self->GetClientSize();
|
||||
my $ratio = $factor * min($win_size->width, $win_size->height) / (2 * max(@{ $self->object_bounding_box->size }));
|
||||
glScalef($ratio, $ratio, 1);
|
||||
}
|
||||
|
||||
sub Resize {
|
||||
my ($self, $x, $y) = @_;
|
||||
|
||||
return unless $self->GetContext;
|
||||
$self->dirty(0);
|
||||
|
||||
$self->SetCurrent($self->GetContext);
|
||||
glViewport(0, 0, $x, $y);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(
|
||||
-$x/2, $x/2, -$y/2, $y/2,
|
||||
-200, 10 * max(@{ $self->object_bounding_box->size }),
|
||||
);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
unless ($self->mview_init) {
|
||||
$self->mview_init(1);
|
||||
$self->ResetModelView(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
sub InitGL {
|
||||
my $self = shift;
|
||||
|
||||
return if $self->init;
|
||||
return unless $self->GetContext;
|
||||
$self->init(1);
|
||||
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glColor3f(1, 0, 0);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glClearDepth(1.0);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
# ambient lighting
|
||||
glLightModelfv_p(GL_LIGHT_MODEL_AMBIENT, 0.1, 0.1, 0.1, 1);
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_LIGHT0);
|
||||
glEnable(GL_LIGHT1);
|
||||
glLightfv_p(GL_LIGHT0, GL_POSITION, 0.5, 0.5, 1, 0);
|
||||
glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
|
||||
glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.8, 0.8, 0.8, 1);
|
||||
glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 0.5, 0);
|
||||
glLightfv_p(GL_LIGHT1, GL_SPECULAR, 0.5, 0.5, 0.5, 1);
|
||||
glLightfv_p(GL_LIGHT1, GL_DIFFUSE, 1, 1, 1, 1);
|
||||
|
||||
# Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
|
||||
glShadeModel(GL_SMOOTH);
|
||||
|
||||
glMaterialfv_p(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, 0.5, 0.3, 0.3, 1);
|
||||
glMaterialfv_p(GL_FRONT_AND_BACK, GL_SPECULAR, 1, 1, 1, 1);
|
||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 50);
|
||||
glMaterialfv_p(GL_FRONT_AND_BACK, GL_EMISSION, 0.1, 0, 0, 0.9);
|
||||
|
||||
# A handy trick -- have surface material mirror the color.
|
||||
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
|
||||
glEnable(GL_COLOR_MATERIAL);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
}
|
||||
|
||||
sub Render {
|
||||
my ($self, $dc) = @_;
|
||||
|
||||
# prevent calling SetCurrent() when window is not shown yet
|
||||
return unless $self->IsShownOnScreen;
|
||||
return unless my $context = $self->GetContext;
|
||||
$self->SetCurrent($context);
|
||||
$self->InitGL;
|
||||
|
||||
glClearColor(1, 1, 1, 1);
|
||||
glClearDepth(1);
|
||||
glDepthFunc(GL_LESS);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
|
||||
my $bb = $self->object_bounding_box;
|
||||
my $object_size = $bb->size;
|
||||
glTranslatef(0, 0, -max(@$object_size[0..1]));
|
||||
my @rotmat = quat_to_rotmatrix($self->quat);
|
||||
glMultMatrixd_p(@rotmat[0..15]);
|
||||
glRotatef($self->stheta, 1, 0, 0);
|
||||
glRotatef($self->sphi, 0, 0, 1);
|
||||
|
||||
# center everything around 0,0 since that's where we're looking at (glOrtho())
|
||||
my $center = $bb->center;
|
||||
glTranslatef(-$center->x, -$center->y, 0); #,,
|
||||
|
||||
# draw objects
|
||||
$self->draw_mesh;
|
||||
|
||||
# draw ground and axes
|
||||
glDisable(GL_LIGHTING);
|
||||
my $z0 = 0;
|
||||
|
||||
{
|
||||
# draw ground
|
||||
my $ground_z = GROUND_Z;
|
||||
if ($self->bed_triangles) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glColor4f(0.5, 0.5, 0.5, 0.3);
|
||||
glNormal3d(0,0,1);
|
||||
glVertexPointer_p(3, $self->bed_triangles);
|
||||
glDrawArrays(GL_TRIANGLES, 0, $self->bed_triangles->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
# draw grid
|
||||
glTranslatef(0, 0, 0.02);
|
||||
glLineWidth(3);
|
||||
glColor3f(1.0, 1.0, 1.0);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glVertexPointer_p(3, $self->bed_grid_lines);
|
||||
glDrawArrays(GL_LINES, 0, $self->bed_grid_lines->elements / 3);
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
}
|
||||
|
||||
{
|
||||
# draw axes
|
||||
$ground_z += 0.02;
|
||||
my $origin = $self->origin;
|
||||
my $axis_len = 2 * max(@{ $object_size });
|
||||
glLineWidth(2);
|
||||
glBegin(GL_LINES);
|
||||
# draw line for x axis
|
||||
glColor3f(1, 0, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x + $axis_len, $origin->y, $ground_z); #,,
|
||||
# draw line for y axis
|
||||
glColor3f(0, 1, 0);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f($origin->x, $origin->y + $axis_len, $ground_z); #++
|
||||
# draw line for Z axis
|
||||
glColor3f(0, 0, 1);
|
||||
glVertex3f(@$origin, $ground_z);
|
||||
glVertex3f(@$origin, $ground_z+$axis_len);
|
||||
glEnd();
|
||||
}
|
||||
|
||||
# draw cutting plane
|
||||
if (defined $self->cutting_plane_z) {
|
||||
my $plane_z = $z0 + $self->cutting_plane_z;
|
||||
glDisable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBegin(GL_QUADS);
|
||||
glColor4f(0.8, 0.8, 0.8, 0.5);
|
||||
glVertex3f($bb->x_min-20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_min-20, $plane_z);
|
||||
glVertex3f($bb->x_max+20, $bb->y_max+20, $plane_z);
|
||||
glVertex3f($bb->x_min-20, $bb->y_max+20, $plane_z);
|
||||
glEnd();
|
||||
glEnable(GL_CULL_FACE);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
}
|
||||
|
||||
glEnable(GL_LIGHTING);
|
||||
|
||||
glPopMatrix();
|
||||
glFlush();
|
||||
|
||||
$self->SwapBuffers();
|
||||
}
|
||||
|
||||
sub draw_mesh {
|
||||
my $self = shift;
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
|
||||
foreach my $volume (@{$self->volumes}) {
|
||||
glTranslatef(0, 0, -$volume->{z_min});
|
||||
|
||||
glVertexPointer_p(3, $volume->{verts});
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
glNormalPointer_p($volume->{norms});
|
||||
if ($volume->{selected}) {
|
||||
glColor4f(@{ &SELECTED_COLOR });
|
||||
} else {
|
||||
glColor4f(@{ $volume->{color} });
|
||||
}
|
||||
glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3);
|
||||
|
||||
glTranslatef(0, 0, +$volume->{z_min});
|
||||
}
|
||||
glDisableClientState(GL_NORMAL_ARRAY);
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_CULL_FACE);
|
||||
|
||||
if (defined $self->cutting_plane_z) {
|
||||
glLineWidth(2);
|
||||
glColor3f(0, 0, 0);
|
||||
glVertexPointer_p(3, $self->cut_lines_vertices);
|
||||
glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3);
|
||||
}
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,3 +1,5 @@
|
||||
# Status bar at the bottom of the main screen.
|
||||
|
||||
package Slic3r::GUI::ProgressStatusBar;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
package Slic3r::GUI::SimpleTab;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use List::Util qw(first);
|
||||
use Wx qw(:bookctrl :dialog :keycode :icon :id :misc :panel :sizer :window :systemsettings);
|
||||
use Wx::Event qw(EVT_BUTTON EVT_CHOICE EVT_KEY_DOWN);
|
||||
use base 'Wx::ScrolledWindow';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my ($parent, %params) = @_;
|
||||
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||
|
||||
$self->SetScrollbars(1, 1, 1, 1);
|
||||
|
||||
$self->{config} = Slic3r::Config->new;
|
||||
$self->{optgroups} = [];
|
||||
|
||||
$self->{vsizer} = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->SetSizer($self->{vsizer});
|
||||
$self->build;
|
||||
|
||||
{
|
||||
my $label = Wx::StaticText->new($self, -1, "Want more options? Switch to the Expert Mode.", wxDefaultPosition, wxDefaultSize);
|
||||
$label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
|
||||
$self->{vsizer}->Add($label, 0, wxEXPAND | wxALL, 10);
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init_config_options {
|
||||
my ($self, @opt_keys) = @_;
|
||||
$self->{config}->apply(Slic3r::Config->new_from_defaults(@opt_keys));
|
||||
}
|
||||
|
||||
sub new_optgroup {
|
||||
my ($self, $title, %params) = @_;
|
||||
|
||||
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
|
||||
parent => $self,
|
||||
title => $title,
|
||||
config => $self->{config},
|
||||
label_width => $params{label_width} // 200,
|
||||
on_change => sub { $self->_on_value_change(@_) },
|
||||
);
|
||||
|
||||
push @{$self->{optgroups}}, $optgroup;
|
||||
$self->{vsizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
return $optgroup;
|
||||
}
|
||||
|
||||
sub load_config_file {
|
||||
my $self = shift;
|
||||
my ($file) = @_;
|
||||
|
||||
my $config = Slic3r::Config->load($file);
|
||||
$self->load_config($config);
|
||||
}
|
||||
|
||||
sub load_config {
|
||||
my $self = shift;
|
||||
my ($config) = @_;
|
||||
|
||||
foreach my $opt_key (@{$self->{config}->get_keys}) {
|
||||
next unless $config->has($opt_key);
|
||||
$self->{config}->set($opt_key, $config->get($opt_key));
|
||||
}
|
||||
$_->reload_config for @{$self->{optgroups}};
|
||||
}
|
||||
|
||||
sub load_presets {}
|
||||
|
||||
sub is_dirty { 0 }
|
||||
sub config { $_[0]->{config}->clone }
|
||||
|
||||
sub on_value_change {
|
||||
my ($self, $cb) = @_;
|
||||
$self->{on_value_change} = $cb;
|
||||
}
|
||||
|
||||
sub on_presets_changed {}
|
||||
|
||||
# propagate event to the parent
|
||||
sub _on_value_change {
|
||||
my $self = shift;
|
||||
$self->{on_value_change}->(@_) if $self->{on_value_change};
|
||||
}
|
||||
|
||||
package Slic3r::GUI::SimpleTab::Print;
|
||||
use base 'Slic3r::GUI::SimpleTab';
|
||||
|
||||
use Wx qw(:sizer);
|
||||
|
||||
sub name { 'print' }
|
||||
sub title { 'Print Settings' }
|
||||
|
||||
sub build {
|
||||
my $self = shift;
|
||||
|
||||
$self->init_config_options(qw(
|
||||
layer_height perimeters top_solid_layers bottom_solid_layers
|
||||
fill_density fill_pattern support_material support_material_spacing raft_layers
|
||||
perimeter_speed infill_speed travel_speed
|
||||
brim_width
|
||||
complete_objects extruder_clearance_radius extruder_clearance_height
|
||||
));
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('General');
|
||||
$optgroup->append_single_option_line('layer_height');
|
||||
$optgroup->append_single_option_line('perimeters');
|
||||
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Solid layers',
|
||||
);
|
||||
$line->append_option($optgroup->get_option('top_solid_layers'));
|
||||
$line->append_option($optgroup->get_option('bottom_solid_layers'));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Infill');
|
||||
$optgroup->append_single_option_line('fill_density');
|
||||
$optgroup->append_single_option_line('fill_pattern');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Support material');
|
||||
$optgroup->append_single_option_line('support_material');
|
||||
$optgroup->append_single_option_line('support_material_spacing');
|
||||
$optgroup->append_single_option_line('raft_layers');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Speed');
|
||||
$optgroup->append_single_option_line('perimeter_speed');
|
||||
$optgroup->append_single_option_line('infill_speed');
|
||||
$optgroup->append_single_option_line('travel_speed');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Brim');
|
||||
$optgroup->append_single_option_line('brim_width');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Sequential printing');
|
||||
$optgroup->append_single_option_line('complete_objects');
|
||||
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Extruder clearance (mm)',
|
||||
);
|
||||
$line->append_option($optgroup->get_option('extruder_clearance_radius'));
|
||||
$line->append_option($optgroup->get_option('extruder_clearance_height'));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
}
|
||||
|
||||
package Slic3r::GUI::SimpleTab::Filament;
|
||||
use base 'Slic3r::GUI::SimpleTab';
|
||||
|
||||
sub name { 'filament' }
|
||||
sub title { 'Filament Settings' }
|
||||
|
||||
sub build {
|
||||
my $self = shift;
|
||||
|
||||
$self->init_config_options(qw(
|
||||
filament_diameter extrusion_multiplier
|
||||
temperature first_layer_temperature bed_temperature first_layer_bed_temperature
|
||||
));
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Filament');
|
||||
$optgroup->append_single_option_line('filament_diameter', 0);
|
||||
$optgroup->append_single_option_line('extrusion_multiplier', 0);
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Temperature (°C)');
|
||||
|
||||
{
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Extruder',
|
||||
);
|
||||
$line->append_option($optgroup->get_option('first_layer_temperature', 0));
|
||||
$line->append_option($optgroup->get_option('temperature', 0));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
|
||||
{
|
||||
my $line = Slic3r::GUI::OptionsGroup::Line->new(
|
||||
label => 'Bed',
|
||||
);
|
||||
$line->append_option($optgroup->get_option('first_layer_bed_temperature'));
|
||||
$line->append_option($optgroup->get_option('bed_temperature'));
|
||||
$optgroup->append_line($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package Slic3r::GUI::SimpleTab::Printer;
|
||||
use base 'Slic3r::GUI::SimpleTab';
|
||||
|
||||
sub name { 'printer' }
|
||||
sub title { 'Printer Settings' }
|
||||
|
||||
sub build {
|
||||
my $self = shift;
|
||||
|
||||
$self->init_config_options(qw(
|
||||
z_offset
|
||||
gcode_flavor
|
||||
nozzle_diameter
|
||||
retract_length retract_lift
|
||||
start_gcode
|
||||
end_gcode
|
||||
));
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Size and coordinates');
|
||||
# TODO: add bed_shape
|
||||
$optgroup->append_single_option_line('z_offset');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Firmware');
|
||||
$optgroup->append_single_option_line('gcode_flavor');
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Extruder');
|
||||
$optgroup->append_single_option_line('nozzle_diameter', 0);
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Retraction');
|
||||
$optgroup->append_single_option_line('retract_length', 0);
|
||||
$optgroup->append_single_option_line('retract_lift', 0);
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('Start G-code',
|
||||
label_width => 0,
|
||||
);
|
||||
my $option = $optgroup->get_option('start_gcode');
|
||||
$option->full_width(1);
|
||||
$option->height(150);
|
||||
$optgroup->append_single_option_line($option);
|
||||
}
|
||||
|
||||
{
|
||||
my $optgroup = $self->new_optgroup('End G-code',
|
||||
label_width => 0,
|
||||
);
|
||||
my $option = $optgroup->get_option('end_gcode');
|
||||
$option->full_width(1);
|
||||
$option->height(150);
|
||||
$optgroup->append_single_option_line($option);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
70
lib/Slic3r/GUI/SystemInfo.pm
Normal file
70
lib/Slic3r/GUI/SystemInfo.pm
Normal file
@@ -0,0 +1,70 @@
|
||||
package Slic3r::GUI::SystemInfo;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Wx qw(:font :html :misc :dialog :sizer :systemsettings :frame :id wxTheClipboard);
|
||||
use Wx::Event qw(EVT_HTML_LINK_CLICKED EVT_LEFT_DOWN EVT_BUTTON);
|
||||
use Wx::Html;
|
||||
use base 'Wx::Dialog';
|
||||
|
||||
sub new {
|
||||
my ($class, %params) = @_;
|
||||
my $self = $class->SUPER::new($params{parent}, -1, 'Slic3r Prusa Edition - System Information', wxDefaultPosition, [600, 340],
|
||||
wxDEFAULT_DIALOG_STYLE | wxMAXIMIZE_BOX | wxRESIZE_BORDER);
|
||||
$self->{text_info} = $params{text_info};
|
||||
|
||||
$self->SetBackgroundColour(Wx::wxWHITE);
|
||||
my $vsizer = Wx::BoxSizer->new(wxVERTICAL);
|
||||
$self->SetSizer($vsizer);
|
||||
|
||||
# text
|
||||
my $text =
|
||||
'<html>' .
|
||||
'<body bgcolor="#ffffff" link="#808080">' .
|
||||
($params{slic3r_info} // '') .
|
||||
($params{copyright_info} // '') .
|
||||
($params{system_info} // '') .
|
||||
($params{opengl_info} // '') .
|
||||
'</body>' .
|
||||
'</html>';
|
||||
my $html = $self->{html} = Wx::HtmlWindow->new($self, -1, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
my $font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
my $size = &Wx::wxMSW ? 8 : 10;
|
||||
$html->SetFonts($font->GetFaceName, $font->GetFaceName, [$size * 1.5, $size * 1.4, $size * 1.3, $size, $size, $size, $size]);
|
||||
$html->SetBorders(10);
|
||||
$html->SetPage($text);
|
||||
$vsizer->Add($html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
|
||||
EVT_HTML_LINK_CLICKED($self, $html, \&link_clicked);
|
||||
|
||||
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
|
||||
my $btn_copy_to_clipboard = Wx::Button->new($self, -1, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize);
|
||||
$buttons->Insert(0, $btn_copy_to_clipboard, 0, wxLEFT, 5);
|
||||
EVT_BUTTON($self, $btn_copy_to_clipboard, \©_to_clipboard);
|
||||
$self->SetEscapeId(wxID_CLOSE);
|
||||
EVT_BUTTON($self, wxID_CLOSE, sub {
|
||||
$self->EndModal(wxID_CLOSE);
|
||||
$self->Close;
|
||||
});
|
||||
# $vsizer->Add($buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
$vsizer->Add($buttons, 0, wxEXPAND | wxALL, 5);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub link_clicked {
|
||||
my ($self, $event) = @_;
|
||||
|
||||
Wx::LaunchDefaultBrowser($event->GetLinkInfo->GetHref);
|
||||
$event->Skip(0);
|
||||
}
|
||||
|
||||
sub copy_to_clipboard {
|
||||
my ($self, $event) = @_;
|
||||
my $data = $self->{text_info};
|
||||
wxTheClipboard->Open;
|
||||
wxTheClipboard->SetData(Wx::TextDataObject->new($data));
|
||||
wxTheClipboard->Close;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,41 +4,40 @@ use warnings;
|
||||
|
||||
require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(
|
||||
PI X Y Z A B X1 Y1 X2 Y2 Z1 Z2 MIN MAX epsilon slope
|
||||
line_point_belongs_to_segment points_coincide distance_between_points
|
||||
normalize tan move_points_3D
|
||||
point_in_polygon point_in_segment segment_in_segment
|
||||
polyline_lines polygon_lines
|
||||
point_along_segment polygon_segment_having_point polygon_has_subsegment
|
||||
deg2rad rad2deg
|
||||
rotate_points move_points
|
||||
dot perp polygon_points_visibility
|
||||
line_intersection bounding_box bounding_box_intersect
|
||||
angle3points
|
||||
chained_path chained_path_from collinear scale unscale
|
||||
rad2deg_dir bounding_box_center line_intersects_any douglas_peucker
|
||||
polyline_remove_short_segments normal triangle_normal polygon_is_convex
|
||||
scaled_epsilon bounding_box_3D size_3D size_2D
|
||||
convex_hull directions_parallel directions_parallel_within
|
||||
);
|
||||
|
||||
# Exported by this module. The last section starting with convex_hull is exported by Geometry.xsp
|
||||
our @EXPORT_OK = qw(
|
||||
PI epsilon
|
||||
|
||||
angle3points
|
||||
collinear
|
||||
dot
|
||||
line_intersection
|
||||
normalize
|
||||
point_in_segment
|
||||
polyline_lines
|
||||
polygon_is_convex
|
||||
polygon_segment_having_point
|
||||
scale
|
||||
unscale
|
||||
scaled_epsilon
|
||||
size_2D
|
||||
|
||||
X Y Z
|
||||
convex_hull
|
||||
chained_path_from
|
||||
deg2rad
|
||||
rad2deg
|
||||
rad2deg_dir
|
||||
);
|
||||
|
||||
use constant PI => 4 * atan2(1, 1);
|
||||
use constant A => 0;
|
||||
use constant B => 1;
|
||||
use constant X => 0;
|
||||
use constant Y => 1;
|
||||
use constant Z => 2;
|
||||
use constant X1 => 0;
|
||||
use constant Y1 => 1;
|
||||
use constant X2 => 2;
|
||||
use constant Y2 => 3;
|
||||
use constant Z1 => 4;
|
||||
use constant Z2 => 5;
|
||||
use constant MIN => 0;
|
||||
use constant MAX => 1;
|
||||
our $parallel_degrees_limit = abs(deg2rad(0.1));
|
||||
|
||||
sub epsilon () { 1E-4 }
|
||||
sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
|
||||
@@ -46,81 +45,7 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR }
|
||||
sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR }
|
||||
sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR }
|
||||
|
||||
sub tan {
|
||||
my ($angle) = @_;
|
||||
return (sin $angle) / (cos $angle);
|
||||
}
|
||||
|
||||
sub slope {
|
||||
my ($line) = @_;
|
||||
return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical
|
||||
return ($line->[B][Y] - $line->[A][Y]) / ($line->[B][X] - $line->[A][X]);
|
||||
}
|
||||
|
||||
# this subroutine checks whether a given point may belong to a given
|
||||
# segment given the hypothesis that it belongs to the line containing
|
||||
# the segment
|
||||
sub line_point_belongs_to_segment {
|
||||
my ($point, $segment) = @_;
|
||||
|
||||
#printf " checking whether %f,%f may belong to segment %f,%f - %f,%f\n",
|
||||
# @$point, map @$_, @$segment;
|
||||
|
||||
my @segment_extents = (
|
||||
[ sort { $a <=> $b } map $_->[X], @$segment ],
|
||||
[ sort { $a <=> $b } map $_->[Y], @$segment ],
|
||||
);
|
||||
|
||||
return 0 if $point->[X] < ($segment_extents[X][0] - epsilon) || $point->[X] > ($segment_extents[X][1] + epsilon);
|
||||
return 0 if $point->[Y] < ($segment_extents[Y][0] - epsilon) || $point->[Y] > ($segment_extents[Y][1] + epsilon);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub points_coincide {
|
||||
my ($p1, $p2) = @_;
|
||||
return 1 if abs($p2->[X] - $p1->[X]) < epsilon && abs($p2->[Y] - $p1->[Y]) < epsilon;
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub distance_between_points {
|
||||
my ($p1, $p2) = @_;
|
||||
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
|
||||
}
|
||||
|
||||
# this will check whether a point is in a polygon regardless of polygon orientation
|
||||
sub point_in_polygon {
|
||||
my ($point, $polygon) = @_;
|
||||
|
||||
my ($x, $y) = @$point;
|
||||
my $n = @$polygon;
|
||||
my @x = map $_->[X], @$polygon;
|
||||
my @y = map $_->[Y], @$polygon;
|
||||
|
||||
# Derived from the comp.graphics.algorithms FAQ,
|
||||
# courtesy of Wm. Randolph Franklin
|
||||
my ($i, $j);
|
||||
my $side = 0; # 0 = outside; 1 = inside
|
||||
for ($i = 0, $j = $n - 1; $i < $n; $j = $i++) {
|
||||
if (
|
||||
# If the y is between the (y-) borders...
|
||||
($y[$i] <= $y && $y < $y[$j]) || ($y[$j] <= $y && $y < $y[$i])
|
||||
and
|
||||
# ...the (x,y) to infinity line crosses the edge
|
||||
# from the ith point to the jth point...
|
||||
($x < ($x[$j] - $x[$i]) * ($y - $y[$i]) / ($y[$j] - $y[$i]) + $x[$i])
|
||||
) {
|
||||
$side = not $side; # Jump the fence
|
||||
}
|
||||
}
|
||||
|
||||
# if point is not in polygon, let's check whether it belongs to the contour
|
||||
if (!$side && 0) {
|
||||
return 1 if polygon_segment_having_point($polygon, $point);
|
||||
}
|
||||
|
||||
return $side;
|
||||
}
|
||||
|
||||
# used by geometry.t, polygon_segment_having_point
|
||||
sub point_in_segment {
|
||||
my ($point, $line) = @_;
|
||||
|
||||
@@ -144,41 +69,15 @@ sub point_in_segment {
|
||||
return abs($y3 - $y) < epsilon ? 1 : 0;
|
||||
}
|
||||
|
||||
sub segment_in_segment {
|
||||
my ($needle, $haystack) = @_;
|
||||
|
||||
# a segment is contained in another segment if its endpoints are contained
|
||||
return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack);
|
||||
}
|
||||
|
||||
# used by geometry.t
|
||||
sub polyline_lines {
|
||||
my ($polyline) = @_;
|
||||
my @points = @$polyline;
|
||||
return map Slic3r::Line->new(@points[$_, $_+1]), 0 .. $#points-1;
|
||||
}
|
||||
|
||||
sub polygon_lines {
|
||||
my ($polygon) = @_;
|
||||
return polyline_lines([ @$polygon, $polygon->[0] ]);
|
||||
}
|
||||
|
||||
# given a segment $p1-$p2, get the point at $distance from $p1 along segment
|
||||
sub point_along_segment {
|
||||
my ($p1, $p2, $distance) = @_;
|
||||
|
||||
my $point = [ @$p1 ];
|
||||
|
||||
my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) );
|
||||
for (X, Y) {
|
||||
if ($p1->[$_] != $p2->[$_]) {
|
||||
$point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length;
|
||||
}
|
||||
}
|
||||
|
||||
return Slic3r::Point->new(@$point);
|
||||
}
|
||||
|
||||
# given a $polygon, return the (first) segment having $point
|
||||
# used by geometry.t
|
||||
sub polygon_segment_having_point {
|
||||
my ($polygon, $point) = @_;
|
||||
|
||||
@@ -188,15 +87,6 @@ sub polygon_segment_having_point {
|
||||
return undef;
|
||||
}
|
||||
|
||||
# return true if the given segment is contained in any edge of the polygon
|
||||
sub polygon_has_subsegment {
|
||||
my ($polygon, $segment) = @_;
|
||||
foreach my $line (polygon_lines($polygon)) {
|
||||
return 1 if segment_in_segment($segment, $line);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# polygon must be simple (non complex) and ccw
|
||||
sub polygon_is_convex {
|
||||
my ($points) = @_;
|
||||
@@ -207,70 +97,6 @@ sub polygon_is_convex {
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub deg2rad {
|
||||
my ($degrees) = @_;
|
||||
return PI() * $degrees / 180;
|
||||
}
|
||||
|
||||
sub rad2deg {
|
||||
my ($rad) = @_;
|
||||
return $rad / PI() * 180;
|
||||
}
|
||||
|
||||
sub rad2deg_dir {
|
||||
my ($rad) = @_;
|
||||
$rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2);
|
||||
$rad += PI if $rad < 0;
|
||||
return rad2deg($rad);
|
||||
}
|
||||
|
||||
sub rotate_points {
|
||||
my ($radians, $center, @points) = @_;
|
||||
$center //= [0,0];
|
||||
return map {
|
||||
[
|
||||
$center->[X] + cos($radians) * ($_->[X] - $center->[X]) - sin($radians) * ($_->[Y] - $center->[Y]),
|
||||
$center->[Y] + cos($radians) * ($_->[Y] - $center->[Y]) + sin($radians) * ($_->[X] - $center->[X]),
|
||||
]
|
||||
} @points;
|
||||
}
|
||||
|
||||
sub move_points {
|
||||
my ($shift, @points) = @_;
|
||||
return map {
|
||||
my @p = @$_;
|
||||
Slic3r::Point->new($shift->[X] + $p[X], $shift->[Y] + $p[Y]);
|
||||
} @points;
|
||||
}
|
||||
|
||||
sub move_points_3D {
|
||||
my ($shift, @points) = @_;
|
||||
return map [
|
||||
$shift->[X] + $_->[X],
|
||||
$shift->[Y] + $_->[Y],
|
||||
$shift->[Z] + $_->[Z],
|
||||
], @points;
|
||||
}
|
||||
|
||||
sub normal {
|
||||
my ($line1, $line2) = @_;
|
||||
|
||||
return [
|
||||
($line1->[Y] * $line2->[Z]) - ($line1->[Z] * $line2->[Y]),
|
||||
-($line2->[Z] * $line1->[X]) + ($line2->[X] * $line1->[Z]),
|
||||
($line1->[X] * $line2->[Y]) - ($line1->[Y] * $line2->[X]),
|
||||
];
|
||||
}
|
||||
|
||||
sub triangle_normal {
|
||||
my ($v1, $v2, $v3) = @_;
|
||||
|
||||
my $u = [ map +($v2->[$_] - $v1->[$_]), (X,Y,Z) ];
|
||||
my $v = [ map +($v3->[$_] - $v1->[$_]), (X,Y,Z) ];
|
||||
|
||||
return normal($u, $v);
|
||||
}
|
||||
|
||||
sub normalize {
|
||||
my ($line) = @_;
|
||||
|
||||
@@ -280,38 +106,12 @@ sub normalize {
|
||||
}
|
||||
|
||||
# 2D dot product
|
||||
# used by 3DScene.pm
|
||||
sub dot {
|
||||
my ($u, $v) = @_;
|
||||
return $u->[X] * $v->[X] + $u->[Y] * $v->[Y];
|
||||
}
|
||||
|
||||
# 2D perp product
|
||||
sub perp {
|
||||
my ($u, $v) = @_;
|
||||
return $u->[X] * $v->[Y] - $u->[Y] * $v->[X];
|
||||
}
|
||||
|
||||
sub polygon_points_visibility {
|
||||
my ($polygon, $p1, $p2) = @_;
|
||||
|
||||
my $our_line = [ $p1, $p2 ];
|
||||
foreach my $line (polygon_lines($polygon)) {
|
||||
my $intersection = line_intersection($our_line, $line, 1) // next;
|
||||
next if grep points_coincide($intersection, $_), $p1, $p2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub line_intersects_any {
|
||||
my ($line, $lines) = @_;
|
||||
for (@$lines) {
|
||||
return 1 if line_intersection($line, $_, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub line_intersection {
|
||||
my ($line1, $line2, $require_crossing) = @_;
|
||||
$require_crossing ||= 0;
|
||||
@@ -409,43 +209,6 @@ sub _line_intersection {
|
||||
return [Slic3r::Point->new($x, $y), $h10 >= 0 && $h10 <= 1 && $h32 >= 0 && $h32 <= 1];
|
||||
}
|
||||
|
||||
# http://paulbourke.net/geometry/lineline2d/
|
||||
sub _line_intersection2 {
|
||||
my ($line1, $line2) = @_;
|
||||
|
||||
my $denom = ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[B][X] - $line1->[A][X])
|
||||
- ($line2->[B][X] - $line2->[A][X]) * ($line1->[B][Y] - $line1->[A][Y]);
|
||||
my $numerA = ($line2->[B][X] - $line2->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
|
||||
- ($line2->[B][Y] - $line2->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
|
||||
my $numerB = ($line1->[B][X] - $line1->[A][X]) * ($line1->[A][Y] - $line2->[A][Y])
|
||||
- ($line1->[B][Y] - $line1->[A][Y]) * ($line1->[A][X] - $line2->[A][X]);
|
||||
|
||||
# are the lines coincident?
|
||||
if (abs($numerA) < epsilon && abs($numerB) < epsilon && abs($denom) < epsilon) {
|
||||
return Slic3r::Point->new(
|
||||
($line1->[A][X] + $line1->[B][X]) / 2,
|
||||
($line1->[A][Y] + $line1->[B][Y]) / 2,
|
||||
);
|
||||
}
|
||||
|
||||
# are the lines parallel?
|
||||
if (abs($denom) < epsilon) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
# is the intersection along the segments?
|
||||
my $muA = $numerA / $denom;
|
||||
my $muB = $numerB / $denom;
|
||||
if ($muA < 0 || $muA > 1 || $muB < 0 || $muB > 1) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
return Slic3r::Point->new(
|
||||
$line1->[A][X] + $muA * ($line1->[B][X] - $line1->[A][X]),
|
||||
$line1->[A][Y] + $muA * ($line1->[B][Y] - $line1->[A][Y]),
|
||||
);
|
||||
}
|
||||
|
||||
# 2D
|
||||
sub bounding_box {
|
||||
my ($points) = @_;
|
||||
@@ -463,14 +226,6 @@ sub bounding_box {
|
||||
return @bb[X1,Y1,X2,Y2];
|
||||
}
|
||||
|
||||
sub bounding_box_center {
|
||||
my ($bounding_box) = @_;
|
||||
return Slic3r::Point->new(
|
||||
($bounding_box->[X2] + $bounding_box->[X1]) / 2,
|
||||
($bounding_box->[Y2] + $bounding_box->[Y1]) / 2,
|
||||
);
|
||||
}
|
||||
|
||||
sub size_2D {
|
||||
my @bounding_box = bounding_box(@_);
|
||||
return (
|
||||
@@ -481,7 +236,7 @@ sub size_2D {
|
||||
|
||||
# bounding_box_intersect($d, @a, @b)
|
||||
# Return true if the given bounding boxes @a and @b intersect
|
||||
# in $d dimensions. Used by line_intersection().
|
||||
# in $d dimensions. Used by sub collinear.
|
||||
sub bounding_box_intersect {
|
||||
my ( $d, @bb ) = @_; # Number of dimensions and box coordinates.
|
||||
my @aa = splice( @bb, 0, 2 * $d ); # The first box.
|
||||
@@ -497,27 +252,6 @@ sub bounding_box_intersect {
|
||||
return 1;
|
||||
}
|
||||
|
||||
# 3D
|
||||
sub bounding_box_3D {
|
||||
my ($points) = @_;
|
||||
|
||||
my @extents = (map [undef, undef], X,Y,Z);
|
||||
foreach my $point (@$points) {
|
||||
for (X,Y,Z) {
|
||||
$extents[$_][MIN] = $point->[$_] if !defined $extents[$_][MIN] || $point->[$_] < $extents[$_][MIN];
|
||||
$extents[$_][MAX] = $point->[$_] if !defined $extents[$_][MAX] || $point->[$_] > $extents[$_][MAX];
|
||||
}
|
||||
}
|
||||
return @extents;
|
||||
}
|
||||
|
||||
sub size_3D {
|
||||
my ($points) = @_;
|
||||
|
||||
my @extents = bounding_box_3D($points);
|
||||
return map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z);
|
||||
}
|
||||
|
||||
# this assumes a CCW rotation from $p2 to $p3 around $p1
|
||||
sub angle3points {
|
||||
my ($p1, $p2, $p3) = @_;
|
||||
@@ -530,224 +264,4 @@ sub angle3points {
|
||||
return $angle <= 0 ? $angle + 2*PI() : $angle;
|
||||
}
|
||||
|
||||
sub polyline_remove_short_segments {
|
||||
my ($points, $min_length, $isPolygon) = @_;
|
||||
for (my $i = $isPolygon ? 0 : 1; $i < $#$points; $i++) {
|
||||
if (distance_between_points($points->[$i-1], $points->[$i]) < $min_length) {
|
||||
# we can remove $points->[$i]
|
||||
splice @$points, $i, 1;
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub douglas_peucker {
|
||||
my ($points, $tolerance) = @_;
|
||||
no warnings "recursion";
|
||||
|
||||
my $results = [];
|
||||
my $dmax = 0;
|
||||
my $index = 0;
|
||||
for my $i (1..$#$points) {
|
||||
my $d = $points->[$i]->distance_to(Slic3r::Line->new($points->[0], $points->[-1]));
|
||||
if ($d > $dmax) {
|
||||
$index = $i;
|
||||
$dmax = $d;
|
||||
}
|
||||
}
|
||||
if ($dmax >= $tolerance) {
|
||||
my $dp1 = douglas_peucker([ @$points[0..$index] ], $tolerance);
|
||||
$results = [
|
||||
@$dp1[0..($#$dp1-1)],
|
||||
@{douglas_peucker([ @$points[$index..$#$points] ], $tolerance)},
|
||||
];
|
||||
} else {
|
||||
$results = [ $points->[0], $points->[-1] ];
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
sub douglas_peucker2 {
|
||||
my ($points, $tolerance) = @_;
|
||||
|
||||
my $anchor = 0;
|
||||
my $floater = $#$points;
|
||||
my @stack = ();
|
||||
my %keep = ();
|
||||
|
||||
push @stack, [$anchor, $floater];
|
||||
while (@stack) {
|
||||
($anchor, $floater) = @{pop @stack};
|
||||
|
||||
# initialize line segment
|
||||
my ($anchor_x, $anchor_y, $seg_len);
|
||||
if (grep $points->[$floater][$_] != $points->[$anchor][$_], X, Y) {
|
||||
$anchor_x = $points->[$floater][X] - $points->[$anchor][X];
|
||||
$anchor_y = $points->[$floater][Y] - $points->[$anchor][Y];
|
||||
$seg_len = sqrt(($anchor_x ** 2) + ($anchor_y ** 2));
|
||||
# get the unit vector
|
||||
$anchor_x /= $seg_len;
|
||||
$anchor_y /= $seg_len;
|
||||
} else {
|
||||
$anchor_x = $anchor_y = $seg_len = 0;
|
||||
}
|
||||
|
||||
# inner loop:
|
||||
my $max_dist = 0;
|
||||
my $farthest = $anchor + 1;
|
||||
for my $i (($anchor + 1) .. $floater) {
|
||||
my $dist_to_seg = 0;
|
||||
# compare to anchor
|
||||
my $vecX = $points->[$i][X] - $points->[$anchor][X];
|
||||
my $vecY = $points->[$i][Y] - $points->[$anchor][Y];
|
||||
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
|
||||
# dot product:
|
||||
my $proj = $vecX * $anchor_x + $vecY * $anchor_y;
|
||||
if ($proj < 0) {
|
||||
$dist_to_seg = $seg_len;
|
||||
} else {
|
||||
# compare to floater
|
||||
$vecX = $points->[$i][X] - $points->[$floater][X];
|
||||
$vecY = $points->[$i][Y] - $points->[$floater][Y];
|
||||
$seg_len = sqrt(($vecX ** 2) + ($vecY ** 2));
|
||||
# dot product:
|
||||
$proj = $vecX * (-$anchor_x) + $vecY * (-$anchor_y);
|
||||
if ($proj < 0) {
|
||||
$dist_to_seg = $seg_len
|
||||
} else { # calculate perpendicular distance to line (pythagorean theorem):
|
||||
$dist_to_seg = sqrt(abs(($seg_len ** 2) - ($proj ** 2)));
|
||||
}
|
||||
if ($max_dist < $dist_to_seg) {
|
||||
$max_dist = $dist_to_seg;
|
||||
$farthest = $i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($max_dist <= $tolerance) { # use line segment
|
||||
$keep{$_} = 1 for $anchor, $floater;
|
||||
} else {
|
||||
push @stack, [$anchor, $farthest];
|
||||
push @stack, [$farthest, $floater];
|
||||
}
|
||||
}
|
||||
|
||||
return [ map $points->[$_], sort keys %keep ];
|
||||
}
|
||||
|
||||
sub arrange {
|
||||
my ($total_parts, $partx, $party, $dist, $bb) = @_;
|
||||
|
||||
my $linint = sub {
|
||||
my ($value, $oldmin, $oldmax, $newmin, $newmax) = @_;
|
||||
return ($value - $oldmin) * ($newmax - $newmin) / ($oldmax - $oldmin) + $newmin;
|
||||
};
|
||||
|
||||
# use actual part size (the largest) plus separation distance (half on each side) in spacing algorithm
|
||||
$partx += $dist;
|
||||
$party += $dist;
|
||||
|
||||
my ($areax, $areay);
|
||||
if (defined $bb) {
|
||||
my $size = $bb->size;
|
||||
($areax, $areay) = @$size[X,Y];
|
||||
} else {
|
||||
# bogus area size, large enough not to trigger the error below
|
||||
$areax = $partx * $total_parts;
|
||||
$areay = $party * $total_parts;
|
||||
}
|
||||
|
||||
# this is how many cells we have available into which to put parts
|
||||
my $cellw = int(($areax + $dist) / $partx);
|
||||
my $cellh = int(($areay + $dist) / $party);
|
||||
|
||||
die "$total_parts parts won't fit in your print area!\n" if $total_parts > ($cellw * $cellh);
|
||||
|
||||
# width and height of space used by cells
|
||||
my $w = $cellw * $partx;
|
||||
my $h = $cellh * $party;
|
||||
|
||||
# left and right border positions of space used by cells
|
||||
my $l = ($areax - $w) / 2;
|
||||
my $r = $l + $w;
|
||||
|
||||
# top and bottom border positions
|
||||
my $t = ($areay - $h) / 2;
|
||||
my $b = $t + $h;
|
||||
|
||||
# list of cells, sorted by distance from center
|
||||
my @cellsorder;
|
||||
|
||||
# work out distance for all cells, sort into list
|
||||
for my $i (0..$cellw-1) {
|
||||
for my $j (0..$cellh-1) {
|
||||
my $cx = $linint->($i + 0.5, 0, $cellw, $l, $r);
|
||||
my $cy = $linint->($j + 0.5, 0, $cellh, $t, $b);
|
||||
|
||||
my $xd = abs(($areax / 2) - $cx);
|
||||
my $yd = abs(($areay / 2) - $cy);
|
||||
|
||||
my $c = {
|
||||
location => [$cx, $cy],
|
||||
index => [$i, $j],
|
||||
distance => $xd * $xd + $yd * $yd - abs(($cellw / 2) - ($i + 0.5)),
|
||||
};
|
||||
|
||||
BINARYINSERTIONSORT: {
|
||||
my $index = $c->{distance};
|
||||
my $low = 0;
|
||||
my $high = @cellsorder;
|
||||
while ($low < $high) {
|
||||
my $mid = ($low + (($high - $low) / 2)) | 0;
|
||||
my $midval = $cellsorder[$mid]->[0];
|
||||
|
||||
if ($midval < $index) {
|
||||
$low = $mid + 1;
|
||||
} elsif ($midval > $index) {
|
||||
$high = $mid;
|
||||
} else {
|
||||
splice @cellsorder, $mid, 0, [$index, $c];
|
||||
last BINARYINSERTIONSORT;
|
||||
}
|
||||
}
|
||||
splice @cellsorder, $low, 0, [$index, $c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# the extents of cells actually used by objects
|
||||
my ($lx, $ty, $rx, $by) = (0, 0, 0, 0);
|
||||
|
||||
# now find cells actually used by objects, map out the extents so we can position correctly
|
||||
for my $i (1..$total_parts) {
|
||||
my $c = $cellsorder[$i - 1];
|
||||
my $cx = $c->[1]->{index}->[0];
|
||||
my $cy = $c->[1]->{index}->[1];
|
||||
if ($i == 1) {
|
||||
$lx = $rx = $cx;
|
||||
$ty = $by = $cy;
|
||||
} else {
|
||||
$rx = $cx if $cx > $rx;
|
||||
$lx = $cx if $cx < $lx;
|
||||
$by = $cy if $cy > $by;
|
||||
$ty = $cy if $cy < $ty;
|
||||
}
|
||||
}
|
||||
# now we actually place objects into cells, positioned such that the left and bottom borders are at 0
|
||||
my @positions = ();
|
||||
for (1..$total_parts) {
|
||||
my $c = shift @cellsorder;
|
||||
my $cx = $c->[1]->{index}->[0] - $lx;
|
||||
my $cy = $c->[1]->{index}->[1] - $ty;
|
||||
|
||||
push @positions, [$cx * $partx, $cy * $party];
|
||||
}
|
||||
|
||||
if (defined $bb) {
|
||||
$_->[X] += $bb->x_min for @positions;
|
||||
$_->[Y] += $bb->y_min for @positions;
|
||||
}
|
||||
return @positions;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -4,10 +4,11 @@ use warnings;
|
||||
|
||||
require Exporter;
|
||||
our @ISA = qw(Exporter);
|
||||
our @EXPORT_OK = qw(offset offset_ex
|
||||
diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER
|
||||
JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex
|
||||
intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE
|
||||
union_pt_chained diff_ppl intersection_ppl);
|
||||
our @EXPORT_OK = qw(
|
||||
offset offset2
|
||||
offset_ex offset2_ex
|
||||
diff_ex diff union_ex intersection_ex
|
||||
JT_ROUND JT_MITER JT_SQUARE
|
||||
intersection intersection_pl diff_pl union);
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# Extends the C++ class Slic3r::Layer.
|
||||
|
||||
package Slic3r::Layer;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(scale chained_path);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex intersection_ex);
|
||||
|
||||
# the following two were previously generated by Moo
|
||||
sub print {
|
||||
my $self = shift;
|
||||
@@ -17,12 +15,6 @@ sub config {
|
||||
return $self->object->config;
|
||||
}
|
||||
|
||||
# the purpose of this method is to be overridden for ::Support layers
|
||||
sub islands {
|
||||
my $self = shift;
|
||||
return $self->slices;
|
||||
}
|
||||
|
||||
sub region {
|
||||
my $self = shift;
|
||||
my ($region_id) = @_;
|
||||
@@ -39,94 +31,7 @@ sub regions {
|
||||
return [ map $self->get_region($_), 0..($self->region_count-1) ];
|
||||
}
|
||||
|
||||
sub merge_slices {
|
||||
my ($self) = @_;
|
||||
$_->merge_slices for @{$self->regions};
|
||||
}
|
||||
|
||||
sub make_perimeters {
|
||||
my $self = shift;
|
||||
Slic3r::debugf "Making perimeters for layer %d\n", $self->id;
|
||||
|
||||
# keep track of regions whose perimeters we have already generated
|
||||
my %done = (); # region_id => 1
|
||||
|
||||
for my $region_id (0..$#{$self->regions}) {
|
||||
next if $done{$region_id};
|
||||
my $layerm = $self->regions->[$region_id];
|
||||
$done{$region_id} = 1;
|
||||
|
||||
# find compatible regions
|
||||
my @layerms = ($layerm);
|
||||
for my $i (($region_id+1)..$#{$self->regions}) {
|
||||
my $config = $self->regions->[$i]->config;
|
||||
my $layerm_config = $layerm->config;
|
||||
|
||||
if ($config->perimeter_extruder == $layerm_config->perimeter_extruder
|
||||
&& $config->perimeters == $layerm_config->perimeters
|
||||
&& $config->perimeter_speed == $layerm_config->perimeter_speed
|
||||
&& $config->gap_fill_speed == $layerm_config->gap_fill_speed
|
||||
&& $config->overhangs == $layerm_config->overhangs
|
||||
&& $config->perimeter_extrusion_width == $layerm_config->perimeter_extrusion_width
|
||||
&& $config->thin_walls == $layerm_config->thin_walls
|
||||
&& $config->external_perimeters_first == $layerm_config->external_perimeters_first) {
|
||||
push @layerms, $self->regions->[$i];
|
||||
$done{$i} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (@layerms == 1) { # optimization
|
||||
$layerm->fill_surfaces->clear;
|
||||
$layerm->make_perimeters($layerm->slices, $layerm->fill_surfaces);
|
||||
} else {
|
||||
# group slices (surfaces) according to number of extra perimeters
|
||||
my %slices = (); # extra_perimeters => [ surface, surface... ]
|
||||
foreach my $surface (map @{$_->slices}, @layerms) {
|
||||
my $extra = $surface->extra_perimeters;
|
||||
$slices{$extra} ||= [];
|
||||
push @{$slices{$extra}}, $surface;
|
||||
}
|
||||
|
||||
# merge the surfaces assigned to each group
|
||||
my $new_slices = Slic3r::Surface::Collection->new;
|
||||
foreach my $surfaces (values %slices) {
|
||||
$new_slices->append(Slic3r::Surface->new(
|
||||
surface_type => $surfaces->[0]->surface_type,
|
||||
extra_perimeters => $surfaces->[0]->extra_perimeters,
|
||||
expolygon => $_,
|
||||
)) for @{union_ex([ map $_->p, @$surfaces ], 1)};
|
||||
}
|
||||
|
||||
# make perimeters
|
||||
my $fill_surfaces = Slic3r::Surface::Collection->new;
|
||||
$layerm->make_perimeters($new_slices, $fill_surfaces);
|
||||
|
||||
# assign fill_surfaces to each layer
|
||||
if ($fill_surfaces->count > 0) {
|
||||
foreach my $lm (@layerms) {
|
||||
my $expolygons = intersection_ex(
|
||||
[ map $_->p, @$fill_surfaces ],
|
||||
[ map $_->p, @{$lm->slices} ],
|
||||
);
|
||||
$lm->fill_surfaces->clear;
|
||||
$lm->fill_surfaces->append(Slic3r::Surface->new(
|
||||
surface_type => $fill_surfaces->[0]->surface_type,
|
||||
extra_perimeters => $fill_surfaces->[0]->extra_perimeters,
|
||||
expolygon => $_,
|
||||
)) for @$expolygons;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package Slic3r::Layer::Support;
|
||||
|
||||
our @ISA = qw(Slic3r::Layer);
|
||||
|
||||
sub islands {
|
||||
my $self = shift;
|
||||
return [ @{$self->slices}, @{$self->support_islands} ];
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -1,277 +0,0 @@
|
||||
package Slic3r::Layer::BridgeDetector;
|
||||
use Moo;
|
||||
|
||||
use List::Util qw(first sum max min);
|
||||
use Slic3r::Geometry qw(PI unscale scaled_epsilon rad2deg epsilon directions_parallel_within);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl intersection_ex union offset diff_pl union_ex
|
||||
intersection_ppl);
|
||||
|
||||
has 'expolygon' => (is => 'ro', required => 1);
|
||||
has 'lower_slices' => (is => 'rw', required => 1); # ExPolygons or ExPolygonCollection
|
||||
has 'extrusion_width' => (is => 'rw', required => 1); # scaled
|
||||
has 'resolution' => (is => 'rw', default => sub { PI/36 });
|
||||
|
||||
has '_edges' => (is => 'rw'); # Polylines representing the supporting edges
|
||||
has '_anchors' => (is => 'rw'); # ExPolygons
|
||||
has 'angle' => (is => 'rw');
|
||||
|
||||
sub BUILD {
|
||||
my ($self) = @_;
|
||||
|
||||
# outset our bridge by an arbitrary amout; we'll use this outer margin
|
||||
# for detecting anchors
|
||||
my $grown = $self->expolygon->offset(+$self->extrusion_width);
|
||||
|
||||
# detect what edges lie on lower slices
|
||||
$self->_edges(my $edges = []);
|
||||
foreach my $lower (@{$self->lower_slices}) {
|
||||
# turn bridge contour and holes into polylines and then clip them
|
||||
# with each lower slice's contour
|
||||
push @$edges, @{intersection_ppl($grown, [ $lower->contour ])};
|
||||
}
|
||||
Slic3r::debugf " bridge has %d support(s)\n", scalar(@$edges);
|
||||
|
||||
# detect anchors as intersection between our bridge expolygon and the lower slices
|
||||
$self->_anchors(intersection_ex(
|
||||
$grown,
|
||||
[ map @$_, @{$self->lower_slices} ],
|
||||
1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges
|
||||
));
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output("bridge.svg",
|
||||
expolygons => [ $self->expolygon ],
|
||||
red_expolygons => $self->lower_slices,
|
||||
polylines => $self->_edges,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub detect_angle {
|
||||
my ($self) = @_;
|
||||
|
||||
return undef if !@{$self->_edges};
|
||||
|
||||
my @edges = @{$self->_edges};
|
||||
my $anchors = $self->_anchors;
|
||||
|
||||
if (!@$anchors) {
|
||||
$self->angle(undef);
|
||||
return undef;
|
||||
}
|
||||
|
||||
# Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
# we'll use this one to clip our test lines and be sure that their endpoints
|
||||
# are inside the anchors and not on their contours leading to false negatives.
|
||||
my $clip_area = $self->expolygon->offset_ex(+$self->extrusion_width/2);
|
||||
|
||||
# we'll now try several directions using a rudimentary visibility check:
|
||||
# bridge in several directions and then sum the length of lines having both
|
||||
# endpoints within anchors
|
||||
|
||||
# we test angles according to configured resolution
|
||||
my @angles = map { $_*$self->resolution } 0..(PI/$self->resolution);
|
||||
|
||||
# we also test angles of each bridge contour
|
||||
push @angles, map $_->direction, map @{$_->lines}, @{$self->expolygon};
|
||||
|
||||
# we also test angles of each open supporting edge
|
||||
# (this finds the optimal angle for C-shaped supports)
|
||||
push @angles,
|
||||
map Slic3r::Line->new($_->first_point, $_->last_point)->direction,
|
||||
grep { !$_->first_point->coincides_with($_->last_point) }
|
||||
@edges;
|
||||
|
||||
# remove duplicates
|
||||
my $min_resolution = PI/180; # 1 degree
|
||||
# proceed in reverse order so that when we compare first value with last one (-1)
|
||||
# we remove the greatest one (PI) in case they are parallel (PI, 0)
|
||||
@angles = reverse sort @angles;
|
||||
for (my $i = 0; $i <= $#angles; ++$i) {
|
||||
if (directions_parallel_within($angles[$i], $angles[$i-1], $min_resolution)) {
|
||||
splice @angles, $i, 1;
|
||||
--$i;
|
||||
}
|
||||
}
|
||||
|
||||
my %directions_coverage = (); # angle => score
|
||||
my %directions_avg_length = (); # angle => score
|
||||
my $line_increment = $self->extrusion_width;
|
||||
my %unique_angles = map { $_ => 1 } @angles;
|
||||
for my $angle (@angles) {
|
||||
my $my_clip_area = [ map $_->clone, @$clip_area ];
|
||||
my $my_anchors = [ map $_->clone, @$anchors ];
|
||||
|
||||
# rotate everything - the center point doesn't matter
|
||||
$_->rotate(-$angle, [0,0]) for @$my_clip_area, @$my_anchors;
|
||||
|
||||
# generate lines in this direction
|
||||
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$my_anchors ]);
|
||||
|
||||
my @lines = ();
|
||||
for (my $y = $bounding_box->y_min; $y <= $bounding_box->y_max; $y+= $line_increment) {
|
||||
push @lines, Slic3r::Polyline->new(
|
||||
[$bounding_box->x_min, $y],
|
||||
[$bounding_box->x_max, $y],
|
||||
);
|
||||
}
|
||||
|
||||
my @clipped_lines = map Slic3r::Line->new(@$_), @{ intersection_pl(\@lines, [ map @$_, @$my_clip_area ]) };
|
||||
|
||||
# remove any line not having both endpoints within anchors
|
||||
@clipped_lines = grep {
|
||||
my $line = $_;
|
||||
(first { $_->contains_point($line->a) } @$my_anchors)
|
||||
&& (first { $_->contains_point($line->b) } @$my_anchors);
|
||||
} @clipped_lines;
|
||||
|
||||
my @lengths = map $_->length, @clipped_lines;
|
||||
|
||||
# sum length of bridged lines
|
||||
$directions_coverage{$angle} = sum(@lengths) // 0;
|
||||
|
||||
### The following produces more correct results in some cases and more broken in others.
|
||||
### TODO: investigate, as it looks more reliable than line clipping.
|
||||
###$directions_coverage{$angle} = sum(map $_->area, @{$self->coverage($angle)}) // 0;
|
||||
|
||||
# max length of bridged lines
|
||||
$directions_avg_length{$angle} = @lengths ? (max(@lengths)) : -1;
|
||||
}
|
||||
|
||||
# if no direction produced coverage, then there's no bridge direction
|
||||
return undef if !defined first { $_ > 0 } values %directions_coverage;
|
||||
|
||||
# the best direction is the one causing most lines to be bridged (thus most coverage)
|
||||
# and shortest max line length
|
||||
my @sorted_directions = sort {
|
||||
my $cmp;
|
||||
my $coverage_diff = $directions_coverage{$a} - $directions_coverage{$b};
|
||||
if (abs($coverage_diff) < $self->extrusion_width) {
|
||||
$cmp = $directions_avg_length{$b} <=> $directions_avg_length{$a};
|
||||
} else {
|
||||
$cmp = ($coverage_diff > 0) ? 1 : -1;
|
||||
}
|
||||
$cmp;
|
||||
} keys %directions_coverage;
|
||||
|
||||
$self->angle($sorted_directions[-1]);
|
||||
|
||||
if ($self->angle >= PI) {
|
||||
$self->angle($self->angle - PI);
|
||||
}
|
||||
|
||||
Slic3r::debugf " Optimal infill angle is %d degrees\n", rad2deg($self->angle);
|
||||
|
||||
return $self->angle;
|
||||
}
|
||||
|
||||
sub coverage {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
if (!defined $angle) {
|
||||
return [] if !defined($angle = $self->angle);
|
||||
}
|
||||
|
||||
# Clone our expolygon and rotate it so that we work with vertical lines.
|
||||
my $expolygon = $self->expolygon->clone;
|
||||
$expolygon->rotate(PI/2 - $angle, [0,0]);
|
||||
|
||||
# Outset the bridge expolygon by half the amount we used for detecting anchors;
|
||||
# we'll use this one to generate our trapezoids and be sure that their vertices
|
||||
# are inside the anchors and not on their contours leading to false negatives.
|
||||
my $grown = $expolygon->offset_ex(+$self->extrusion_width/2);
|
||||
|
||||
# Compute trapezoids according to a vertical orientation
|
||||
my $trapezoids = [ map @{$_->get_trapezoids2(PI/2)}, @$grown ];
|
||||
|
||||
# get anchors and rotate them too
|
||||
my $anchors = [ map $_->clone, @{$self->_anchors} ];
|
||||
$_->rotate(PI/2 - $angle, [0,0]) for @$anchors;
|
||||
|
||||
my @covered = (); # polygons
|
||||
foreach my $trapezoid (@$trapezoids) {
|
||||
my @polylines = map $_->as_polyline, @{$trapezoid->lines};
|
||||
my @supported = @{intersection_pl(\@polylines, [map @$_, @$anchors])};
|
||||
|
||||
# not nice, we need a more robust non-numeric check
|
||||
@supported = grep $_->length >= $self->extrusion_width, @supported;
|
||||
|
||||
if (@supported >= 2) {
|
||||
push @covered, $trapezoid;
|
||||
}
|
||||
}
|
||||
|
||||
# merge trapezoids and rotate them back
|
||||
my $coverage = union(\@covered);
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @$coverage;
|
||||
|
||||
# intersect trapezoids with actual bridge area to remove extra margins
|
||||
$coverage = intersection_ex($coverage, [ @{$self->expolygon} ]);
|
||||
|
||||
if (0) {
|
||||
my @lines = map @{$_->lines}, @$trapezoids;
|
||||
$_->rotate(-(PI/2 - $angle), [0,0]) for @lines;
|
||||
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"coverage_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchors,
|
||||
red_expolygons => $coverage,
|
||||
lines => \@lines,
|
||||
);
|
||||
}
|
||||
|
||||
return $coverage;
|
||||
}
|
||||
|
||||
# this method returns the bridge edges (as polylines) that are not supported
|
||||
# but would allow the entire bridge area to be bridged with detected angle
|
||||
# if supported too
|
||||
sub unsupported_edges {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
if (!defined $angle) {
|
||||
return [] if !defined($angle = $self->angle);
|
||||
}
|
||||
|
||||
# get bridge edges (both contour and holes)
|
||||
my @bridge_edges = map $_->split_at_first_point, @{$self->expolygon};
|
||||
$_->[0]->translate(1,0) for @bridge_edges; # workaround for Clipper bug, see comments in Slic3r::Polygon::clip_as_polyline()
|
||||
|
||||
# get unsupported edges
|
||||
my $grown_lower = offset([ map @$_, @{$self->lower_slices} ], +$self->extrusion_width);
|
||||
my $unsupported = diff_pl(
|
||||
\@bridge_edges,
|
||||
$grown_lower,
|
||||
);
|
||||
|
||||
# split into individual segments and filter out edges parallel to the bridging angle
|
||||
# TODO: angle tolerance should probably be based on segment length and flow width,
|
||||
# so that we build supports whenever there's a chance that at least one or two bridge
|
||||
# extrusions would be anchored within such length (i.e. a slightly non-parallel bridging
|
||||
# direction might still benefit from anchors if long enough)
|
||||
my $angle_tolerance = PI/180*5;
|
||||
@$unsupported = map $_->as_polyline,
|
||||
grep !directions_parallel_within($_->direction, $angle, $angle_tolerance),
|
||||
map @{$_->lines},
|
||||
@$unsupported;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"unsupported_" . rad2deg($angle) . ".svg",
|
||||
expolygons => [$self->expolygon],
|
||||
green_expolygons => $self->_anchors,
|
||||
red_expolygons => union_ex($grown_lower),
|
||||
no_arrows => 1,
|
||||
polylines => \@bridge_edges,
|
||||
red_polylines => $unsupported,
|
||||
);
|
||||
}
|
||||
|
||||
return $unsupported;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,556 +0,0 @@
|
||||
package Slic3r::Layer::Region;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(sum first);
|
||||
use Slic3r::ExtrusionLoop ':roles';
|
||||
use Slic3r::ExtrusionPath ':roles';
|
||||
use Slic3r::Flow ':roles';
|
||||
use Slic3r::Geometry qw(PI A B scale unscale chained_path points_coincide);
|
||||
use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex
|
||||
offset offset_ex offset2 offset2_ex union_pt diff intersection
|
||||
union diff intersection_ppl diff_ppl);
|
||||
use Slic3r::Surface ':types';
|
||||
|
||||
|
||||
# TODO: lazy
|
||||
sub infill_area_threshold {
|
||||
my $self = shift;
|
||||
return $self->flow(FLOW_ROLE_SOLID_INFILL)->scaled_spacing ** 2;
|
||||
}
|
||||
|
||||
sub id { return $_[0]->layer->id; }
|
||||
sub slice_z { return $_[0]->layer->slice_z; }
|
||||
sub print_z { return $_[0]->layer->print_z; }
|
||||
sub height { return $_[0]->layer->height; }
|
||||
sub object { return $_[0]->layer->object; }
|
||||
sub print { return $_[0]->layer->print; }
|
||||
|
||||
sub config { return $_[0]->region->config; }
|
||||
|
||||
sub merge_slices {
|
||||
my ($self) = @_;
|
||||
|
||||
my $expolygons = union_ex([ map $_->p, @{$self->slices} ]);
|
||||
$self->slices->clear;
|
||||
$self->slices->append(Slic3r::Surface->new(
|
||||
expolygon => $_,
|
||||
surface_type => S_TYPE_INTERNAL,
|
||||
)) for @$expolygons;
|
||||
}
|
||||
|
||||
sub make_perimeters {
|
||||
my ($self, $slices, $fill_surfaces) = @_;
|
||||
|
||||
# other perimeters
|
||||
my $perimeter_flow = $self->flow(FLOW_ROLE_PERIMETER);
|
||||
my $mm3_per_mm = $perimeter_flow->mm3_per_mm;
|
||||
my $pwidth = $perimeter_flow->scaled_width;
|
||||
my $pspacing = $perimeter_flow->scaled_spacing;
|
||||
|
||||
# external perimeters
|
||||
my $ext_perimeter_flow = $self->flow(FLOW_ROLE_EXTERNAL_PERIMETER);
|
||||
my $ext_mm3_per_mm = $ext_perimeter_flow->mm3_per_mm;
|
||||
my $ext_pwidth = $ext_perimeter_flow->scaled_width;
|
||||
my $ext_pspacing = scale($ext_perimeter_flow->spacing_to($perimeter_flow));
|
||||
|
||||
# overhang perimeters
|
||||
my $overhang_flow = $self->region->flow(FLOW_ROLE_PERIMETER, -1, 1, 0, -1, $self->layer->object);
|
||||
my $mm3_per_mm_overhang = $overhang_flow->mm3_per_mm;
|
||||
|
||||
# solid infill
|
||||
my $solid_infill_flow = $self->flow(FLOW_ROLE_SOLID_INFILL);
|
||||
my $ispacing = $solid_infill_flow->scaled_spacing;
|
||||
my $gap_area_threshold = $pwidth ** 2;
|
||||
|
||||
# Calculate the minimum required spacing between two adjacent traces.
|
||||
# This should be equal to the nominal flow spacing but we experiment
|
||||
# with some tolerance in order to avoid triggering medial axis when
|
||||
# some squishing might work. Loops are still spaced by the entire
|
||||
# flow spacing; this only applies to collapsing parts.
|
||||
my $min_spacing = $pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
my $ext_min_spacing = $ext_pspacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
$self->perimeters->clear;
|
||||
$self->thin_fills->clear;
|
||||
|
||||
my @contours = (); # array of Polygons with ccw orientation
|
||||
my @holes = (); # array of Polygons with cw orientation
|
||||
my @thin_walls = (); # array of ExPolygons
|
||||
|
||||
# we need to process each island separately because we might have different
|
||||
# extra perimeters for each one
|
||||
foreach my $surface (@$slices) {
|
||||
# detect how many perimeters must be generated for this island
|
||||
my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0);
|
||||
|
||||
my @last = @{$surface->expolygon};
|
||||
my @gaps = (); # array of ExPolygons
|
||||
if ($loop_number > 0) {
|
||||
# we loop one time more than needed in order to find gaps after the last perimeter was applied
|
||||
for my $i (1 .. ($loop_number+1)) { # outer loop is 1
|
||||
my @offsets = ();
|
||||
if ($i == 1) {
|
||||
# the minimum thickness of a single loop is:
|
||||
# ext_width/2 + ext_spacing/2 + spacing/2 + width/2
|
||||
if ($self->config->thin_walls) {
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-(0.5*$ext_pwidth + 0.5*$ext_min_spacing - 1),
|
||||
+(0.5*$ext_min_spacing - 1),
|
||||
)};
|
||||
} else {
|
||||
@offsets = @{offset(
|
||||
\@last,
|
||||
-0.5*$ext_pwidth,
|
||||
)};
|
||||
}
|
||||
|
||||
# look for thin walls
|
||||
if ($self->config->thin_walls) {
|
||||
my $diff = diff_ex(
|
||||
\@last,
|
||||
offset(\@offsets, +0.5*$ext_pwidth),
|
||||
1, # medial axis requires non-overlapping geometry
|
||||
);
|
||||
push @thin_walls, @$diff;
|
||||
}
|
||||
} else {
|
||||
my $distance = ($i == 2) ? $ext_pspacing : $pspacing;
|
||||
|
||||
if ($self->config->thin_walls) {
|
||||
@offsets = @{offset2(
|
||||
\@last,
|
||||
-($distance + 0.5*$min_spacing - 1),
|
||||
+(0.5*$min_spacing - 1),
|
||||
)};
|
||||
} else {
|
||||
@offsets = @{offset(
|
||||
\@last,
|
||||
-$distance,
|
||||
)};
|
||||
}
|
||||
|
||||
# look for gaps
|
||||
if ($self->region->config->gap_fill_speed > 0 && $self->config->fill_density > 0) {
|
||||
# not using safety offset here would "detect" very narrow gaps
|
||||
# (but still long enough to escape the area threshold) that gap fill
|
||||
# won't be able to fill but we'd still remove from infill area
|
||||
my $diff = diff_ex(
|
||||
offset(\@last, -0.5*$pspacing),
|
||||
offset(\@offsets, +0.5*$pspacing + 10), # safety offset
|
||||
);
|
||||
push @gaps, grep abs($_->area) >= $gap_area_threshold, @$diff;
|
||||
}
|
||||
}
|
||||
|
||||
last if !@offsets;
|
||||
last if $i > $loop_number; # we were only looking for gaps this time
|
||||
|
||||
# clone polygons because these ExPolygons will go out of scope very soon
|
||||
@last = @offsets;
|
||||
foreach my $polygon (@offsets) {
|
||||
if ($polygon->is_counter_clockwise) {
|
||||
push @contours, $polygon;
|
||||
} else {
|
||||
push @holes, $polygon;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# fill gaps
|
||||
if (@gaps) {
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"gaps.svg",
|
||||
expolygons => \@gaps,
|
||||
);
|
||||
}
|
||||
|
||||
# where $pwidth < thickness < 2*$pspacing, infill with width = 1.5*$pwidth
|
||||
# where 0.5*$pwidth < thickness < $pwidth, infill with width = 0.5*$pwidth
|
||||
my @gap_sizes = (
|
||||
[ $pwidth, 2*$pspacing, unscale 1.5*$pwidth ],
|
||||
[ 0.5*$pwidth, $pwidth, unscale 0.5*$pwidth ],
|
||||
);
|
||||
foreach my $gap_size (@gap_sizes) {
|
||||
my @gap_fill = $self->_fill_gaps(@$gap_size, \@gaps);
|
||||
$self->thin_fills->append(@gap_fill);
|
||||
|
||||
# Make sure we don't infill narrow parts that are already gap-filled
|
||||
# (we only consider this surface's gaps to reduce the diff() complexity).
|
||||
# Growing actual extrusions ensures that gaps not filled by medial axis
|
||||
# are not subtracted from fill surfaces (they might be too short gaps
|
||||
# that medial axis skips but infill might join with other infill regions
|
||||
# and use zigzag).
|
||||
my $w = $gap_size->[2];
|
||||
my @filled = map {
|
||||
@{($_->isa('Slic3r::ExtrusionLoop') ? $_->polygon->split_at_first_point : $_->polyline)
|
||||
->grow(scale $w/2)};
|
||||
} @gap_fill;
|
||||
@last = @{diff(\@last, \@filled)};
|
||||
}
|
||||
}
|
||||
|
||||
# create one more offset to be used as boundary for fill
|
||||
# we offset by half the perimeter spacing (to get to the actual infill boundary)
|
||||
# and then we offset back and forth by half the infill spacing to only consider the
|
||||
# non-collapsing regions
|
||||
my $min_perimeter_infill_spacing = $ispacing * (1 - &Slic3r::INSET_OVERLAP_TOLERANCE);
|
||||
$fill_surfaces->append(
|
||||
map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), # use a bogus surface type
|
||||
@{offset2_ex(
|
||||
[ map @{$_->simplify_p(&Slic3r::SCALED_RESOLUTION)}, @{union_ex(\@last)} ],
|
||||
-($pspacing/2 + $min_perimeter_infill_spacing/2),
|
||||
+$min_perimeter_infill_spacing/2,
|
||||
)}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
# process thin walls by collapsing slices to single passes
|
||||
my @thin_wall_polylines = ();
|
||||
if (@thin_walls) {
|
||||
# the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
|
||||
# (actually, something larger than that still may exist due to mitering or other causes)
|
||||
my $min_width = $pwidth / 4;
|
||||
@thin_walls = @{offset2_ex([ map @$_, @thin_walls ], -$min_width/2, +$min_width/2)};
|
||||
|
||||
# the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
|
||||
@thin_wall_polylines = map @{$_->medial_axis($pwidth + $pspacing, $min_width)}, @thin_walls;
|
||||
Slic3r::debugf " %d thin walls detected\n", scalar(@thin_wall_polylines) if $Slic3r::debug;
|
||||
|
||||
if (0) {
|
||||
require "Slic3r/SVG.pm";
|
||||
Slic3r::SVG::output(
|
||||
"medial_axis.svg",
|
||||
no_arrows => 1,
|
||||
expolygons => \@thin_walls,
|
||||
green_polylines => [ map $_->polygon->split_at_first_point, @{$self->perimeters} ],
|
||||
red_polylines => \@thin_wall_polylines,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# find nesting hierarchies separately for contours and holes
|
||||
my $contours_pt = union_pt(\@contours);
|
||||
my $holes_pt = union_pt(\@holes);
|
||||
|
||||
# prepare grown lower layer slices for overhang detection
|
||||
my $lower_slices = Slic3r::ExPolygon::Collection->new;
|
||||
if ($self->layer->lower_layer && $self->region->config->overhangs) {
|
||||
# We consider overhang any part where the entire nozzle diameter is not supported by the
|
||||
# lower layer, so we take lower slices and offset them by half the nozzle diameter used
|
||||
# in the current layer
|
||||
my $nozzle_diameter = $self->layer->print->config->get_at('nozzle_diameter', $self->region->config->perimeter_extruder-1);
|
||||
$lower_slices->append(
|
||||
@{offset_ex([ map @$_, @{$self->layer->lower_layer->slices} ], scale +$nozzle_diameter/2)},
|
||||
);
|
||||
}
|
||||
my $lower_slices_p = $lower_slices->polygons;
|
||||
|
||||
# prepare a coderef for traversing the PolyTree object
|
||||
# external contours are root items of $contours_pt
|
||||
# internal contours are the ones next to external
|
||||
my $traverse;
|
||||
$traverse = sub {
|
||||
my ($polynodes, $depth, $is_contour) = @_;
|
||||
|
||||
# convert all polynodes to ExtrusionLoop objects
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new;
|
||||
my @children = ();
|
||||
foreach my $polynode (@$polynodes) {
|
||||
my $polygon = ($polynode->{outer} // $polynode->{hole})->clone;
|
||||
|
||||
my $role = EXTR_ROLE_PERIMETER;
|
||||
my $loop_role = EXTRL_ROLE_DEFAULT;
|
||||
|
||||
my $root_level = $depth == 0;
|
||||
my $no_children = !@{ $polynode->{children} };
|
||||
my $is_external = $is_contour ? $root_level : $no_children;
|
||||
my $is_internal = $is_contour ? $no_children : $root_level;
|
||||
if ($is_external) {
|
||||
# external perimeters are root level in case of contours
|
||||
# and items with no children in case of holes
|
||||
$role = EXTR_ROLE_EXTERNAL_PERIMETER;
|
||||
$loop_role = EXTRL_ROLE_EXTERNAL_PERIMETER;
|
||||
} elsif ($is_contour && $is_internal) {
|
||||
# internal perimeters are root level in case of holes
|
||||
# and items with no children in case of contours
|
||||
$loop_role = EXTRL_ROLE_CONTOUR_INTERNAL_PERIMETER;
|
||||
}
|
||||
|
||||
# detect overhanging/bridging perimeters
|
||||
my @paths = ();
|
||||
if ($self->region->config->overhangs && $lower_slices->count > 0) {
|
||||
# get non-overhang paths by intersecting this loop with the grown lower slices
|
||||
foreach my $polyline (@{ intersection_ppl([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => $role,
|
||||
mm3_per_mm => ($is_external ? $ext_mm3_per_mm : $mm3_per_mm),
|
||||
width => ($is_external ? $ext_perimeter_flow->width : $perimeter_flow->width),
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
|
||||
# get overhang paths by checking what parts of this loop fall
|
||||
# outside the grown lower slices (thus where the distance between
|
||||
# the loop centerline and original lower slices is >= half nozzle diameter
|
||||
foreach my $polyline (@{ diff_ppl([ $polygon ], $lower_slices_p) }) {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_OVERHANG_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm_overhang,
|
||||
width => $overhang_flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
|
||||
# reapply the nearest point search for starting point
|
||||
# (clone because the collection gets DESTROY'ed)
|
||||
# We allow polyline reversal because Clipper may have randomly
|
||||
# reversed polylines during clipping.
|
||||
my $collection = Slic3r::ExtrusionPath::Collection->new(@paths);
|
||||
@paths = map $_->clone, @{$collection->chained_path(0)};
|
||||
} else {
|
||||
push @paths, Slic3r::ExtrusionPath->new(
|
||||
polyline => $polygon->split_at_first_point,
|
||||
role => $role,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
}
|
||||
my $loop = Slic3r::ExtrusionLoop->new_from_paths(@paths);
|
||||
$loop->role($loop_role);
|
||||
|
||||
# return ccw contours and cw holes
|
||||
# GCode.pm will convert all of them to ccw, but it needs to know
|
||||
# what the holes are in order to compute the correct inwards move
|
||||
# We do this on the final Loop object instead of the polygon because
|
||||
# overhang clipping might have reversed its order since Clipper does
|
||||
# not preserve polyline orientation.
|
||||
if ($is_contour) {
|
||||
$loop->make_counter_clockwise;
|
||||
} else {
|
||||
$loop->make_clockwise;
|
||||
}
|
||||
$collection->append($loop);
|
||||
|
||||
# save the children
|
||||
push @children, $polynode->{children};
|
||||
}
|
||||
|
||||
# if we're handling the top-level contours, add thin walls as candidates too
|
||||
# in order to include them in the nearest-neighbor search
|
||||
if ($is_contour && $depth == 0) {
|
||||
foreach my $polyline (@thin_wall_polylines) {
|
||||
$collection->append(Slic3r::ExtrusionPath->new(
|
||||
polyline => $polyline,
|
||||
role => EXTR_ROLE_EXTERNAL_PERIMETER,
|
||||
mm3_per_mm => $mm3_per_mm,
|
||||
width => $perimeter_flow->width,
|
||||
height => $self->height,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
# use a nearest neighbor search to order these children
|
||||
# TODO: supply second argument to chained_path() too?
|
||||
# Optimization: since islands are going to be sorted by slice anyway in the
|
||||
# G-code export process, we skip chained_path here
|
||||
my ($sorted_collection, @orig_indices);
|
||||
if ($is_contour && $depth == 0) {
|
||||
$sorted_collection = $collection;
|
||||
@orig_indices = (0..$#$sorted_collection);
|
||||
} else {
|
||||
$sorted_collection = $collection->chained_path_indices(0);
|
||||
@orig_indices = @{$sorted_collection->orig_indices};
|
||||
}
|
||||
|
||||
my @loops = ();
|
||||
foreach my $loop (@$sorted_collection) {
|
||||
my $orig_index = shift @orig_indices;
|
||||
|
||||
if ($loop->isa('Slic3r::ExtrusionPath')) {
|
||||
push @loops, $loop->clone;
|
||||
} else {
|
||||
# if this is an external contour find all holes belonging to this contour(s)
|
||||
# and prepend them
|
||||
if ($is_contour && $depth == 0) {
|
||||
# $loop is the outermost loop of an island
|
||||
my @holes = ();
|
||||
for (my $i = 0; $i <= $#$holes_pt; $i++) {
|
||||
if ($loop->polygon->contains_point($holes_pt->[$i]{outer}->first_point)) {
|
||||
push @holes, splice @$holes_pt, $i, 1; # remove from candidates to reduce complexity
|
||||
$i--;
|
||||
}
|
||||
}
|
||||
|
||||
# order holes efficiently
|
||||
@holes = @holes[@{chained_path([ map {($_->{outer} // $_->{hole})->first_point} @holes ])}];
|
||||
|
||||
push @loops, reverse map $traverse->([$_], 0, 0), @holes;
|
||||
}
|
||||
|
||||
# traverse children and prepend them to this loop
|
||||
push @loops, $traverse->($children[$orig_index], $depth+1, $is_contour);
|
||||
push @loops, $loop->clone;
|
||||
}
|
||||
}
|
||||
return @loops;
|
||||
};
|
||||
|
||||
# order loops from inner to outer (in terms of object slices)
|
||||
my @loops = $traverse->($contours_pt, 0, 1);
|
||||
|
||||
# if brim will be printed, reverse the order of perimeters so that
|
||||
# we continue inwards after having finished the brim
|
||||
# TODO: add test for perimeter order
|
||||
@loops = reverse @loops
|
||||
if $self->region->config->external_perimeters_first
|
||||
|| ($self->layer->id == 0 && $self->print->config->brim_width > 0);
|
||||
|
||||
# append perimeters
|
||||
$self->perimeters->append(@loops);
|
||||
}
|
||||
|
||||
sub _fill_gaps {
|
||||
my ($self, $min, $max, $w, $gaps) = @_;
|
||||
|
||||
my $this = diff_ex(
|
||||
offset2([ map @$_, @$gaps ], -$min/2, +$min/2),
|
||||
offset2([ map @$_, @$gaps ], -$max/2, +$max/2),
|
||||
1,
|
||||
);
|
||||
|
||||
my $flow = $self->flow(FLOW_ROLE_SOLID_INFILL, 0, $w);
|
||||
my %path_args = (
|
||||
role => EXTR_ROLE_GAPFILL,
|
||||
mm3_per_mm => $flow->mm3_per_mm,
|
||||
width => $flow->width,
|
||||
height => $self->height,
|
||||
);
|
||||
my @polylines = map @{$_->medial_axis($max, $min/2)}, @$this;
|
||||
|
||||
Slic3r::debugf " %d gaps filled with extrusion width = %s\n", scalar @$this, $w
|
||||
if @$this;
|
||||
|
||||
for my $i (0..$#polylines) {
|
||||
if ($polylines[$i]->isa('Slic3r::Polygon')) {
|
||||
my $loop = Slic3r::ExtrusionLoop->new;
|
||||
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i]->split_at_first_point, %path_args));
|
||||
$polylines[$i] = $loop;
|
||||
} elsif ($polylines[$i]->is_valid && $polylines[$i]->first_point->coincides_with($polylines[$i]->last_point)) {
|
||||
# since medial_axis() now returns only Polyline objects, detect loops here
|
||||
my $loop = Slic3r::ExtrusionLoop->new;
|
||||
$loop->append(Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args));
|
||||
$polylines[$i] = $loop;
|
||||
} else {
|
||||
$polylines[$i] = Slic3r::ExtrusionPath->new(polyline => $polylines[$i], %path_args);
|
||||
}
|
||||
}
|
||||
return @polylines;
|
||||
}
|
||||
|
||||
sub prepare_fill_surfaces {
|
||||
my $self = shift;
|
||||
|
||||
# Note: in order to make the psPrepareInfill step idempotent, we should never
|
||||
# alter fill_surfaces boundaries on which our idempotency relies since that's
|
||||
# the only meaningful information returned by psPerimeters.
|
||||
|
||||
# if no solid layers are requested, turn top/bottom surfaces to internal
|
||||
if ($self->config->top_solid_layers == 0) {
|
||||
$_->surface_type(S_TYPE_INTERNAL) for @{$self->fill_surfaces->filter_by_type(S_TYPE_TOP)};
|
||||
}
|
||||
if ($self->config->bottom_solid_layers == 0) {
|
||||
$_->surface_type(S_TYPE_INTERNAL)
|
||||
for @{$self->fill_surfaces->filter_by_type(S_TYPE_BOTTOM)}, @{$self->fill_surfaces->filter_by_type(S_TYPE_BOTTOMBRIDGE)};
|
||||
}
|
||||
|
||||
# turn too small internal regions into solid regions according to the user setting
|
||||
if ($self->config->fill_density > 0) {
|
||||
my $min_area = scale scale $self->config->solid_infill_below_area; # scaling an area requires two calls!
|
||||
$_->surface_type(S_TYPE_INTERNALSOLID)
|
||||
for grep { $_->area <= $min_area } @{$self->fill_surfaces->filter_by_type(S_TYPE_INTERNAL)};
|
||||
}
|
||||
}
|
||||
|
||||
sub process_external_surfaces {
|
||||
my ($self, $lower_layer) = @_;
|
||||
|
||||
my @surfaces = @{$self->fill_surfaces};
|
||||
my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN;
|
||||
|
||||
my @bottom = ();
|
||||
foreach my $surface (grep $_->is_bottom, @surfaces) {
|
||||
my $grown = $surface->expolygon->offset_ex(+$margin);
|
||||
|
||||
# detect bridge direction before merging grown surfaces otherwise adjacent bridges
|
||||
# would get merged into a single one while they need different directions
|
||||
# also, supply the original expolygon instead of the grown one, because in case
|
||||
# of very thin (but still working) anchors, the grown expolygon would go beyond them
|
||||
my $angle;
|
||||
if ($lower_layer) {
|
||||
my $bridge_detector = Slic3r::Layer::BridgeDetector->new(
|
||||
expolygon => $surface->expolygon,
|
||||
lower_slices => $lower_layer->slices,
|
||||
extrusion_width => $self->flow(FLOW_ROLE_INFILL, $self->height, 1)->scaled_width,
|
||||
);
|
||||
Slic3r::debugf "Processing bridge at layer %d:\n", $self->id;
|
||||
$angle = $bridge_detector->detect_angle;
|
||||
|
||||
if (defined $angle && $self->object->config->support_material) {
|
||||
$self->bridged->append(@{ $bridge_detector->coverage($angle) });
|
||||
$self->unsupported_bridge_edges->append(@{ $bridge_detector->unsupported_edges });
|
||||
}
|
||||
}
|
||||
|
||||
push @bottom, map $surface->clone(expolygon => $_, bridge_angle => $angle), @$grown;
|
||||
}
|
||||
|
||||
my @top = ();
|
||||
foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @surfaces) {
|
||||
# give priority to bottom surfaces
|
||||
my $grown = diff_ex(
|
||||
$surface->expolygon->offset(+$margin),
|
||||
[ map $_->p, @bottom ],
|
||||
);
|
||||
push @top, map $surface->clone(expolygon => $_), @$grown;
|
||||
}
|
||||
|
||||
# if we're slicing with no infill, we can't extend external surfaces
|
||||
# over non-existent infill
|
||||
my @fill_boundaries = $self->config->fill_density > 0
|
||||
? @surfaces
|
||||
: grep $_->surface_type != S_TYPE_INTERNAL, @surfaces;
|
||||
|
||||
# intersect the grown surfaces with the actual fill boundaries
|
||||
my @new_surfaces = ();
|
||||
foreach my $group (@{Slic3r::Surface::Collection->new(@top, @bottom)->group}) {
|
||||
push @new_surfaces,
|
||||
map $group->[0]->clone(expolygon => $_),
|
||||
@{intersection_ex(
|
||||
[ map $_->p, @$group ],
|
||||
[ map $_->p, @fill_boundaries ],
|
||||
1, # to ensure adjacent expolygons are unified
|
||||
)};
|
||||
}
|
||||
|
||||
# subtract the new top surfaces from the other non-top surfaces and re-add them
|
||||
my @other = grep $_->surface_type != S_TYPE_TOP && !$_->is_bottom, @surfaces;
|
||||
foreach my $group (@{Slic3r::Surface::Collection->new(@other)->group}) {
|
||||
push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex(
|
||||
[ map $_->p, @$group ],
|
||||
[ map $_->p, @new_surfaces ],
|
||||
)};
|
||||
}
|
||||
$self->fill_surfaces->clear;
|
||||
$self->fill_surfaces->append(@new_surfaces);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -5,8 +5,6 @@ use warnings;
|
||||
# a line is a two-points line
|
||||
use parent 'Slic3r::Polyline';
|
||||
|
||||
use Slic3r::Geometry qw(A B X Y);
|
||||
|
||||
sub intersection {
|
||||
my $self = shift;
|
||||
my ($line, $require_crossing) = @_;
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
# extends C++ class Slic3r::Model
|
||||
package Slic3r::Model;
|
||||
|
||||
use List::Util qw(first max);
|
||||
use Slic3r::Geometry qw(X Y Z move_points);
|
||||
|
||||
sub read_from_file {
|
||||
my $class = shift;
|
||||
my ($input_file) = @_;
|
||||
|
||||
my $model = $input_file =~ /\.stl$/i ? Slic3r::Format::STL->read_file($input_file)
|
||||
: $input_file =~ /\.obj$/i ? Slic3r::Format::OBJ->read_file($input_file)
|
||||
: $input_file =~ /\.amf(\.xml)?$/i ? Slic3r::Format::AMF->read_file($input_file)
|
||||
: die "Input file must have .stl, .obj or .amf(.xml) extension\n";
|
||||
|
||||
$_->set_input_file($input_file) for @{$model->objects};
|
||||
return $model;
|
||||
}
|
||||
use List::Util qw(first max any);
|
||||
|
||||
sub merge {
|
||||
my $class = shift;
|
||||
@@ -64,158 +51,7 @@ sub set_material {
|
||||
return $material;
|
||||
}
|
||||
|
||||
sub duplicate_objects_grid {
|
||||
my ($self, $grid, $distance) = @_;
|
||||
|
||||
die "Grid duplication is not supported with multiple objects\n"
|
||||
if @{$self->objects} > 1;
|
||||
|
||||
my $object = $self->objects->[0];
|
||||
$object->clear_instances;
|
||||
|
||||
my $size = $object->bounding_box->size;
|
||||
for my $x_copy (1..$grid->[X]) {
|
||||
for my $y_copy (1..$grid->[Y]) {
|
||||
$object->add_instance(
|
||||
offset => Slic3r::Pointf->new(
|
||||
($size->[X] + $distance) * ($x_copy-1),
|
||||
($size->[Y] + $distance) * ($y_copy-1),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# this will append more instances to each object
|
||||
# and then automatically rearrange everything
|
||||
sub duplicate_objects {
|
||||
my ($self, $copies_num, $distance, $bb) = @_;
|
||||
|
||||
foreach my $object (@{$self->objects}) {
|
||||
my @instances = @{$object->instances};
|
||||
foreach my $instance (@instances) {
|
||||
$object->add_instance($instance) for 2..$copies_num;
|
||||
}
|
||||
}
|
||||
|
||||
$self->arrange_objects($distance, $bb);
|
||||
}
|
||||
|
||||
# arrange objects preserving their instance count
|
||||
# but altering their instance positions
|
||||
sub arrange_objects {
|
||||
my ($self, $distance, $bb) = @_;
|
||||
|
||||
# get the (transformed) size of each instance so that we take
|
||||
# into account their different transformations when packing
|
||||
my @instance_sizes = ();
|
||||
foreach my $object (@{$self->objects}) {
|
||||
push @instance_sizes, map $object->instance_bounding_box($_)->size, 0..$#{$object->instances};
|
||||
}
|
||||
|
||||
my @positions = $self->_arrange(\@instance_sizes, $distance, $bb);
|
||||
|
||||
foreach my $object (@{$self->objects}) {
|
||||
$_->set_offset(Slic3r::Pointf->new(@{shift @positions})) for @{$object->instances};
|
||||
$object->update_bounding_box;
|
||||
}
|
||||
}
|
||||
|
||||
# duplicate the entire model preserving instance relative positions
|
||||
sub duplicate {
|
||||
my ($self, $copies_num, $distance, $bb) = @_;
|
||||
|
||||
my $model_size = $self->bounding_box->size;
|
||||
my @positions = $self->_arrange([ map $model_size, 2..$copies_num ], $distance, $bb);
|
||||
|
||||
# note that this will leave the object count unaltered
|
||||
|
||||
foreach my $object (@{$self->objects}) {
|
||||
my @instances = @{$object->instances}; # store separately to avoid recursion from add_instance() below
|
||||
foreach my $instance (@instances) {
|
||||
foreach my $pos (@positions) {
|
||||
$object->add_instance(
|
||||
offset => Slic3r::Pointf->new($instance->offset->[X] + $pos->[X], $instance->offset->[Y] + $pos->[Y]),
|
||||
rotation => $instance->rotation,
|
||||
scaling_factor => $instance->scaling_factor,
|
||||
);
|
||||
}
|
||||
}
|
||||
$object->update_bounding_box;
|
||||
}
|
||||
}
|
||||
|
||||
sub _arrange {
|
||||
my ($self, $sizes, $distance, $bb) = @_;
|
||||
|
||||
# we supply unscaled data to arrange()
|
||||
return Slic3r::Geometry::arrange(
|
||||
scalar(@$sizes), # number of parts
|
||||
max(map $_->x, @$sizes), # cell width
|
||||
max(map $_->y, @$sizes), # cell height ,
|
||||
$distance, # distance between cells
|
||||
$bb, # bounding box of the area to fill (can be undef)
|
||||
);
|
||||
}
|
||||
|
||||
# this method splits objects into multiple distinct objects by walking their meshes
|
||||
sub split_meshes {
|
||||
my $self = shift;
|
||||
|
||||
my @objects = @{$self->objects};
|
||||
@{$self->objects} = ();
|
||||
|
||||
foreach my $object (@objects) {
|
||||
if (@{$object->volumes} > 1) {
|
||||
# We can't split meshes if there's more than one material, because
|
||||
# we can't group the resulting meshes by object afterwards
|
||||
$self->_add_object($object);
|
||||
next;
|
||||
}
|
||||
|
||||
my $volume = $object->volumes->[0];
|
||||
foreach my $mesh (@{$volume->mesh->split}) {
|
||||
my $new_object = $self->add_object(
|
||||
input_file => $object->input_file,
|
||||
config => $object->config->clone,
|
||||
layer_height_ranges => $object->layer_height_ranges, # TODO: this needs to be cloned
|
||||
origin_translation => $object->origin_translation,
|
||||
);
|
||||
$new_object->add_volume(
|
||||
mesh => $mesh,
|
||||
name => $volume->name,
|
||||
material_id => $volume->material_id,
|
||||
config => $volume->config,
|
||||
);
|
||||
|
||||
# add one instance per original instance
|
||||
$new_object->add_instance(
|
||||
offset => Slic3r::Pointf->new(@{$_->offset}),
|
||||
rotation => $_->rotation,
|
||||
scaling_factor => $_->scaling_factor,
|
||||
) for @{ $object->instances // [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub print_info {
|
||||
my $self = shift;
|
||||
$_->print_info for @{$self->objects};
|
||||
}
|
||||
|
||||
sub get_material_name {
|
||||
my $self = shift;
|
||||
my ($material_id) = @_;
|
||||
|
||||
my $name;
|
||||
if ($self->has_material($material_id)) {
|
||||
$name //= $self->get_material($material_id)
|
||||
->attributes->{$_} for qw(Name name);
|
||||
}
|
||||
$name //= $material_id;
|
||||
return $name;
|
||||
}
|
||||
|
||||
# Extends C++ class Slic3r::ModelMaterial
|
||||
package Slic3r::Model::Material;
|
||||
|
||||
sub apply {
|
||||
@@ -223,11 +59,10 @@ sub apply {
|
||||
$self->set_attribute($_, $attributes{$_}) for keys %$attributes;
|
||||
}
|
||||
|
||||
# Extends C++ class Slic3r::ModelObject
|
||||
package Slic3r::Model::Object;
|
||||
|
||||
use File::Basename qw(basename);
|
||||
use List::Util qw(first sum);
|
||||
use Slic3r::Geometry qw(X Y Z rad2deg);
|
||||
|
||||
sub add_volume {
|
||||
my $self = shift;
|
||||
@@ -277,7 +112,6 @@ sub add_volume {
|
||||
|
||||
sub add_instance {
|
||||
my $self = shift;
|
||||
my %params = @_;
|
||||
|
||||
if (@_ == 1) {
|
||||
# we have a Model::Instance
|
||||
@@ -299,35 +133,6 @@ sub add_instance {
|
||||
}
|
||||
}
|
||||
|
||||
sub rotate {
|
||||
my ($self, $angle, $axis) = @_;
|
||||
|
||||
# we accept angle in radians but mesh currently uses degrees
|
||||
$angle = rad2deg($angle);
|
||||
|
||||
if ($axis == X) {
|
||||
$_->mesh->rotate_x($angle) for @{$self->volumes};
|
||||
} elsif ($axis == Y) {
|
||||
$_->mesh->rotate_y($angle) for @{$self->volumes};
|
||||
} elsif ($axis == Z) {
|
||||
$_->mesh->rotate_z($angle) for @{$self->volumes};
|
||||
}
|
||||
$self->invalidate_bounding_box;
|
||||
}
|
||||
|
||||
sub flip {
|
||||
my ($self, $axis) = @_;
|
||||
|
||||
if ($axis == X) {
|
||||
$_->mesh->flip_x for @{$self->volumes};
|
||||
} elsif ($axis == Y) {
|
||||
$_->mesh->flip_y for @{$self->volumes};
|
||||
} elsif ($axis == Z) {
|
||||
$_->mesh->flip_z for @{$self->volumes};
|
||||
}
|
||||
$self->invalidate_bounding_box;
|
||||
}
|
||||
|
||||
sub mesh_stats {
|
||||
my $self = shift;
|
||||
|
||||
@@ -335,29 +140,4 @@ sub mesh_stats {
|
||||
return $self->volumes->[0]->mesh->stats;
|
||||
}
|
||||
|
||||
sub print_info {
|
||||
my $self = shift;
|
||||
|
||||
printf "Info about %s:\n", basename($self->input_file);
|
||||
printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->raw_mesh->bounding_box->size};
|
||||
if (my $stats = $self->mesh_stats) {
|
||||
printf " number of facets: %d\n", $stats->{number_of_facets};
|
||||
printf " number of shells: %d\n", $stats->{number_of_parts};
|
||||
printf " volume: %.3f\n", $stats->{volume};
|
||||
if ($self->needed_repair) {
|
||||
printf " needed repair: yes\n";
|
||||
printf " degenerate facets: %d\n", $stats->{degenerate_facets};
|
||||
printf " edges fixed: %d\n", $stats->{edges_fixed};
|
||||
printf " facets removed: %d\n", $stats->{facets_removed};
|
||||
printf " facets added: %d\n", $stats->{facets_added};
|
||||
printf " facets reversed: %d\n", $stats->{facets_reversed};
|
||||
printf " backwards edges: %d\n", $stats->{backwards_edges};
|
||||
} else {
|
||||
printf " needed repair: no\n";
|
||||
}
|
||||
} else {
|
||||
printf " number of facets: %d\n", scalar(map @{$_->facets}, grep !$_->modifier, @{$self->volumes});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -7,4 +7,27 @@ sub new_scale {
|
||||
return $class->new(map Slic3r::Geometry::scale($_), @_);
|
||||
}
|
||||
|
||||
sub dump_perl {
|
||||
my $self = shift;
|
||||
return sprintf "[%s,%s]", @$self;
|
||||
}
|
||||
|
||||
package Slic3r::Pointf;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new_unscale {
|
||||
my $class = shift;
|
||||
return $class->new(map Slic3r::Geometry::unscale($_), @_);
|
||||
}
|
||||
|
||||
package Slic3r::Pointf3;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub new_unscale {
|
||||
my $class = shift;
|
||||
return $class->new(map Slic3r::Geometry::unscale($_), @_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
@@ -5,91 +5,9 @@ use warnings;
|
||||
# a polygon is a closed polyline.
|
||||
use parent 'Slic3r::Polyline';
|
||||
|
||||
use Slic3r::Geometry qw(
|
||||
polygon_segment_having_point
|
||||
PI X1 X2 Y1 Y2 epsilon scaled_epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(intersection_pl);
|
||||
|
||||
sub wkt {
|
||||
my $self = shift;
|
||||
return sprintf "POLYGON((%s))", join ',', map "$_->[0] $_->[1]", @$self;
|
||||
}
|
||||
|
||||
sub dump_perl {
|
||||
my $self = shift;
|
||||
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
|
||||
}
|
||||
|
||||
sub grow {
|
||||
my $self = shift;
|
||||
return $self->split_at_first_point->grow(@_);
|
||||
}
|
||||
|
||||
# this method subdivides the polygon segments to that no one of them
|
||||
# is longer than the length provided
|
||||
sub subdivide {
|
||||
my $self = shift;
|
||||
my ($max_length) = @_;
|
||||
|
||||
my @points = @$self;
|
||||
push @points, $points[0]; # append first point as this is a polygon
|
||||
my @new_points = shift @points;
|
||||
while (@points) {
|
||||
while ($new_points[-1]->distance_to($points[0]) > $max_length) {
|
||||
push @new_points, map Slic3r::Point->new(@$_),
|
||||
Slic3r::Geometry::point_along_segment($new_points[-1], $points[0], $max_length);
|
||||
}
|
||||
push @new_points, shift @points;
|
||||
}
|
||||
pop @new_points; # remove last point as it coincides with first one
|
||||
return Slic3r::Polygon->new(@new_points);
|
||||
}
|
||||
|
||||
sub concave_points {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
$angle //= PI;
|
||||
|
||||
# input angle threshold is checked on the internal side of the polygon
|
||||
# but angle3points measures CCW angle, so we calculate the complementary angle
|
||||
my $ccw_angle = 2*PI-$angle;
|
||||
|
||||
my @concave = ();
|
||||
my @points = @$self;
|
||||
my @points_pp = @{$self->pp};
|
||||
|
||||
for my $i (-1 .. ($#points-1)) {
|
||||
# angle is measured in ccw orientation
|
||||
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
|
||||
if ($vertex_angle <= $ccw_angle) {
|
||||
push @concave, $points[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return [@concave];
|
||||
}
|
||||
|
||||
sub convex_points {
|
||||
my ($self, $angle) = @_;
|
||||
|
||||
$angle //= PI;
|
||||
|
||||
# input angle threshold is checked on the internal side of the polygon
|
||||
# but angle3points measures CCW angle, so we calculate the complementary angle
|
||||
my $ccw_angle = 2*PI-$angle;
|
||||
|
||||
my @convex = ();
|
||||
my @points = @$self;
|
||||
my @points_pp = @{$self->pp};
|
||||
|
||||
for my $i (-1 .. ($#points-1)) {
|
||||
# angle is measured in ccw orientation
|
||||
my $vertex_angle = Slic3r::Geometry::angle3points(@points_pp[$i, $i-1, $i+1]);
|
||||
if ($vertex_angle >= $ccw_angle) {
|
||||
push @convex, $points[$i];
|
||||
}
|
||||
}
|
||||
return [@convex];
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -2,9 +2,7 @@ package Slic3r::Polyline;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(first);
|
||||
use Slic3r::Geometry qw(X Y PI epsilon);
|
||||
use Slic3r::Geometry::Clipper qw(JT_SQUARE);
|
||||
use Slic3r::Geometry qw(X Y);
|
||||
|
||||
sub new_scale {
|
||||
my $class = shift;
|
||||
@@ -12,29 +10,9 @@ sub new_scale {
|
||||
return $class->new(map [ Slic3r::Geometry::scale($_->[X]), Slic3r::Geometry::scale($_->[Y]) ], @points);
|
||||
}
|
||||
|
||||
sub wkt {
|
||||
sub dump_perl {
|
||||
my $self = shift;
|
||||
return sprintf "LINESTRING((%s))", join ',', map "$_->[0] $_->[1]", @$self;
|
||||
}
|
||||
|
||||
sub bounding_box {
|
||||
my $self = shift;
|
||||
return Slic3r::Geometry::BoundingBox->new_from_points([ @$self ]);
|
||||
}
|
||||
|
||||
sub size {
|
||||
my $self = shift;
|
||||
return [ Slic3r::Geometry::size_2D($self) ];
|
||||
}
|
||||
|
||||
sub is_straight {
|
||||
my ($self) = @_;
|
||||
|
||||
# Check that each segment's direction is equal to the line connecting
|
||||
# first point and last point. (Checking each line against the previous
|
||||
# one would have caused the error to accumulate.)
|
||||
my $dir = Slic3r::Line->new($self->first_point, $self->last_point)->direction;
|
||||
return !defined first { !$_->parallel_to($dir) } @{$self->lines};
|
||||
return sprintf "[%s]", join ',', map "[$_->[0],$_->[1]]", @$self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user