├── .gitignore ├── .idea ├── .gitignore ├── misc.xml ├── modules.xml └── vcs.xml ├── Cargo.toml ├── GladiusGUI.iml ├── LICENSE ├── README.md ├── assets └── Screenshot.png ├── langs └── en_US.json ├── logo └── logo.svg ├── slicer ├── gladius_slicer └── gladius_slicer.exe └── src ├── main.rs ├── model.rs ├── object.rs └── shaders.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "GladiusGUI" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | egui = "0.17" 10 | 11 | # Backend-agnostic interface for writing apps using egui 12 | epi = "0.17.0" 13 | # Winit integration with egui 14 | egui_winit_platform = "0.13.0" 15 | # logging 16 | env_logger = { version = "0.9", default-features = false, features = ["termcolor", "atty"] } 17 | # Linear algebra library 18 | glam = "0.20.0" 19 | 20 | # Windowing 21 | winit = "0.26" 22 | 23 | native-dialog = "0.5.5" 24 | 25 | egui_glium = "0.17.0" 26 | glium = "0.31.0" 27 | 28 | nom_stl = "0.2.2" 29 | 30 | gladius_shared = { git = "https://github.com/GladiusSlicer/GladiusSlicer", branch = "Dev" } 31 | 32 | serde_json = "1.0.74" 33 | bincode = "1.3.3" 34 | serde = { version = "1.0", features = ["derive"] } 35 | num_cpus = "1.13.1" 36 | 37 | itertools = "0.10.3" 38 | geo = { version = "0.18.0", features = ["use-serde"] } 39 | 40 | json-gettext = "4.0.0" -------------------------------------------------------------------------------- /GladiusGUI.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GladiusGUI 2 | A Graphical Interface for the Gladius Slicer. The Core Engine can be found [here](https://github.com/GladiusSlicer/GladiusSlicer). 3 | ![Screenshot](/assets/Screenshot.png) 4 | 5 | Rust, LGPL. Copyright 2022 Lucas Ince/mrhatman 6 | 7 | **Questions?** Please use the github discussion page. 8 | 9 | **Want to contribute?** Open a PR. See contributing document for more information. 10 | 11 | Gladius GUI is currently in an alpha state and very little is stable so assume any new release will cause breaking bugs. 12 | -------------------------------------------------------------------------------- /assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GladiusSlicer/GladiusGUI/c67a65fdbb126978c32409d78ffd3a919cbb6276/assets/Screenshot.png -------------------------------------------------------------------------------- /langs/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "viewer": "2D Viewer", 3 | "setup_bar_heading" : "Setup Print", 4 | "choose_model_button": "Choose Model", 5 | "model_path": "Model Path: ", 6 | "border": "Border", 7 | "slice": "Slice", 8 | "remove": "Remove", 9 | "copy": "Copy", 10 | "center": "Center", 11 | "plastic_volume_msg": "This print will use {} cm^3 of plastic", 12 | "plastic_weight_msg": "This print will use {} grams of plastic", 13 | "print_time_msg": "This print will take {0} hours and {1} minutes", 14 | "settings_path": "Settings Path: " 15 | 16 | } -------------------------------------------------------------------------------- /logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /slicer/gladius_slicer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GladiusSlicer/GladiusGUI/c67a65fdbb126978c32409d78ffd3a919cbb6276/slicer/gladius_slicer -------------------------------------------------------------------------------- /slicer/gladius_slicer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GladiusSlicer/GladiusGUI/c67a65fdbb126978c32409d78ffd3a919cbb6276/slicer/gladius_slicer.exe -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release 2 | mod object; 3 | mod shaders; 4 | mod model; 5 | 6 | use crate::object::{load, DisplayVertex, Object}; 7 | use crate::shaders::*; 8 | use crate::model::*; 9 | 10 | use native_dialog::FileDialog; 11 | 12 | use egui::plot::{Corner, Legend, Line, Plot, Value, Values}; 13 | use egui::{ 14 | Color32, FontDefinitions, FontFamily, InnerResponse, Pos2, Sense, Stroke, TextStyle, 15 | }; 16 | use gladius_shared::error::SlicerErrors; 17 | use gladius_shared::messages::Message; 18 | use glam::{Vec2, Vec3}; 19 | use glium::{glutin, Surface, uniform}; 20 | use itertools::Itertools; 21 | use std::fs::File; 22 | use std::io::{BufRead, BufReader, Write}; 23 | use std::process::{Command, Stdio}; 24 | use std::sync::{Arc, RwLock}; 25 | use json_gettext::JSONGetText; 26 | use winit::event::{DeviceEvent, ElementState, MouseScrollDelta, WindowEvent}; 27 | #[macro_use] extern crate json_gettext; 28 | fn vertex(pos: [f32; 3]) -> DisplayVertex { 29 | DisplayVertex { 30 | position: (pos[0], pos[1], pos[2]), 31 | } 32 | } 33 | 34 | #[derive(Clone, Debug)] 35 | enum Errors { 36 | SlicerCommunicationIssue, 37 | SlicerApplicationIssue, 38 | SlicerError(SlicerErrors), 39 | } 40 | 41 | impl Errors { 42 | ///Return the error code and pretty error message 43 | pub fn get_code_and_message(&self) -> (u32, String) { 44 | match self { 45 | Errors::SlicerApplicationIssue => { 46 | (0x8000, format!("Slicing Application could not be found.")) 47 | } 48 | Errors::SlicerCommunicationIssue => ( 49 | 0x8001, 50 | format!("Error found in communication between GUI and slicer application."), 51 | ), 52 | Errors::SlicerError(e) => e.get_code_and_message(), 53 | } 54 | } 55 | } 56 | 57 | 58 | fn create_build_area( 59 | display: &glium::Display, 60 | build_x: f32, 61 | build_y: f32, 62 | build_z: f32, 63 | ) -> (glium::VertexBuffer, glium::IndexBuffer) { 64 | let mut vertex_positions: Vec = vec![ 65 | // far side (0.0, 0.0, 1.0) 66 | vertex([0.0, 0.0, 0.0]), 67 | vertex([0.0, build_y, 0.0]), 68 | vertex([build_x, 0.0, 0.0]), 69 | vertex([build_x, build_y, 0.0]), 70 | vertex([0.0, 0.0, build_z]), 71 | vertex([0.0, build_y, build_z]), 72 | vertex([build_x, 0.0, build_z]), 73 | vertex([build_x, build_y, build_z]), 74 | ]; 75 | 76 | let mut index_data: Vec = vec![ 77 | 0, 1, 0, 2, 1, 3, 2, 3, 4, 5, 4, 6, 5, 7, 6, 7, 0, 4, 1, 5, 2, 6, 3, 7, 78 | ]; 79 | 80 | let step_size = 10.0; 81 | (0..(build_x / step_size) as u32) 82 | .into_iter() 83 | .map(|index| index as f32 * step_size) 84 | .for_each(|x| { 85 | let index_pos = vertex_positions.len() as u32; 86 | vertex_positions.push(vertex([x, 0.0, 0.0])); 87 | vertex_positions.push(vertex([x, build_y, 0.0])); 88 | index_data.push(index_pos); 89 | index_data.push(index_pos + 1); 90 | }); 91 | 92 | (0..(build_y / step_size) as u32) 93 | .into_iter() 94 | .map(|index| index as f32 * step_size) 95 | .for_each(|y| { 96 | let index_pos = vertex_positions.len() as u32; 97 | vertex_positions.push(vertex([0.0, y, 0.0])); 98 | vertex_positions.push(vertex([build_x, y, 0.0])); 99 | index_data.push(index_pos); 100 | index_data.push(index_pos + 1); 101 | }); 102 | 103 | let positions = glium::VertexBuffer::new(display, &vertex_positions).unwrap(); 104 | let indices = 105 | glium::IndexBuffer::new(display, glium::index::PrimitiveType::LinesList, &index_data) 106 | .unwrap(); 107 | (positions, indices) 108 | } 109 | 110 | fn create_display(event_loop: &glutin::event_loop::EventLoop<()>) -> glium::Display { 111 | let window_builder = glutin::window::WindowBuilder::new() 112 | .with_resizable(true) 113 | .with_maximized(true) 114 | .with_title("Gladius"); 115 | 116 | let context_builder = glutin::ContextBuilder::new() 117 | .with_depth_buffer(24) 118 | .with_multisampling(16) 119 | .with_srgb(true) 120 | .with_vsync(true); 121 | 122 | glium::Display::new(window_builder, context_builder, event_loop).unwrap() 123 | } 124 | 125 | fn main() { 126 | 127 | let ctx = static_json_gettext_build!( 128 | "en_US"; 129 | "en_US" => "langs/en_US.json", 130 | ).unwrap(); 131 | 132 | let lang = "en_US"; 133 | 134 | let event_loop = glutin::event_loop::EventLoop::with_user_event(); 135 | let display = create_display(&event_loop); 136 | 137 | let mut egui_glium = egui_glium::EguiGlium::new(&display); 138 | 139 | 140 | let mut left_mouse_button_state = ElementState::Released; 141 | let mut on_render_screen = false; 142 | let mut in_window = false; 143 | let mut index = 0; 144 | let mut layers = 0; 145 | 146 | 147 | let mut viewer_open = false; 148 | 149 | let build_x = 250.0; 150 | let build_y = 210.0; 151 | let build_z = 210.0; 152 | 153 | let model_program = 154 | glium::Program::from_source(&display, VERTEX_SHADER_SRC, FRAGMENT_SHADER_SRC, None) 155 | .unwrap(); 156 | let line_program = glium::Program::from_source( 157 | &display, 158 | LINE_VERTEX_SHADER_SRC, 159 | LINE_FRAGMENT_SHADER_SRC, 160 | None, 161 | ) 162 | .unwrap(); 163 | 164 | let mut gui_data = GUIData::new(Vec2::new(400.0, 400.0),Vec3::new(build_x,build_y,build_z)); 165 | 166 | 167 | 168 | let mut plot_window_resp = None; 169 | let mut window_rec = None; 170 | let mut window_clicked = false; 171 | 172 | let r = gui_data.check_refresh_and_clear(); 173 | 174 | let (line_positions, line_indices) = create_build_area(&display, build_x, build_y, build_z); 175 | event_loop.run(move |event, _, control_flow| { 176 | let mut redraw = || { 177 | let mut quit = false; 178 | 179 | 180 | let needs_repaint = egui_glium.run(&display, |egui_ctx| { 181 | 182 | gui_data.update_colors(); 183 | 184 | plot_window_resp = None; 185 | window_clicked = false; 186 | let resp = egui::SidePanel::left("my_side_panel").show(&egui_ctx, |ui| { 187 | ui.heading(&get_translated_string(&ctx, lang, "setup_bar_heading")); 188 | ui.horizontal(|ui| { 189 | ui.label(&get_translated_string(&ctx, lang, "model_path")); 190 | if ui.button(&get_translated_string(&ctx, lang, "choose_model_button")).clicked() { 191 | gui_data.load_model( &display); 192 | } 193 | }); 194 | ui.horizontal(|ui| { 195 | ui.label(&get_translated_string(&ctx, lang, "settings_path")); 196 | let mut short = gui_data.get_settings_path().clone(); 197 | if short.len() > 13{ 198 | short.truncate(10); 199 | short += "..."; 200 | } 201 | ui.label(short); 202 | }); 203 | ui.horizontal(|ui| { 204 | if ui.button("Choose settings").clicked() { 205 | gui_data.load_settings_file(); 206 | } 207 | }); 208 | ui.group(|ui| { 209 | 210 | gui_data.get_objects().iter().enumerate() 211 | .for_each(|(i,mut obj)| { 212 | ui.horizontal(|ui| { 213 | ui.label(obj.name.to_string()); 214 | }); 215 | 216 | let mut changed = false; 217 | 218 | /*ui.horizontal(|ui| { 219 | changed |= ui.add(egui::DragValue::new(&mut obj.get_mut_location().x) 220 | .speed(1.0) 221 | .clamp_range(f64::NEG_INFINITY..=f64::INFINITY) 222 | .prefix("x: ")) 223 | .changed(); 224 | changed |= ui.add(egui::DragValue::new(&mut obj.get_mut_location().y) 225 | .speed(1.0) 226 | .clamp_range(f64::NEG_INFINITY..=f64::INFINITY) 227 | .prefix("y: ")) 228 | .changed(); 229 | changed |= ui.add(egui::DragValue::new(&mut obj.get_mut_scale().x) 230 | .speed(0.01) 231 | .clamp_range(0.0..=f64::INFINITY) 232 | .prefix("scale: ")) 233 | .changed(); 234 | 235 | obj.get_mut_scale().y = obj.get_scale().x; 236 | obj.get_mut_scale().z = obj.get_scale().x; 237 | }); 238 | 239 | ui.horizontal(|ui| { 240 | if ui.button(&get_translated_string(&ctx, lang,"remove")).clicked() { 241 | remove = Some(i); 242 | }; 243 | if ui.button(&get_translated_string(&ctx, lang, "copy")).clicked(){ 244 | copy = Some(i) 245 | } 246 | if ui.button(&get_translated_string(&ctx, lang, "center")).clicked(){ 247 | obj.get_mut_location().x = build_x /2.0; 248 | obj.get_mut_location().y = build_y /2.0; 249 | changed = true; 250 | } 251 | 252 | });*/ 253 | 254 | if changed{ 255 | //obj.revalidate_cache(); 256 | //*gcode.write().unwrap() = None; 257 | //*calc_vals.write().unwrap() = None; 258 | } 259 | }); 260 | }); 261 | 262 | ui.horizontal(|ui| { 263 | 264 | 265 | ui.style_mut().spacing.button_padding = egui::Vec2::new(50., 20.); 266 | ui.style_mut().override_text_style = Some(TextStyle::Heading); 267 | 268 | 269 | ui.centered_and_justified(|ui| { 270 | let mut fonts = FontDefinitions::default(); 271 | 272 | // Large button text: 273 | 274 | //ui.ctx().set_fonts(fonts); 275 | 276 | 277 | if ui.add_enabled( gui_data.can_slice(), egui::Button::new(&get_translated_string(&ctx, lang, "slice"))).clicked() { 278 | index = 0; 279 | layers = 0; 280 | viewer_open = true; 281 | 282 | gui_data.start_slice(); 283 | } 284 | }); 285 | }); 286 | 287 | if let Some(cv) = gui_data.get_calculated_values(){ 288 | ui.horizontal(|ui| { 289 | ui.label(get_translated_string_argument(&ctx,lang,"plastic_volume_msg",format!("{:.0}",cv.plastic_volume))); 290 | }); 291 | ui.horizontal(|ui| { 292 | ui.label(get_translated_string_argument(&ctx,lang,"plastic_weight_msg",format!("{:.0}",cv.plastic_weight))); 293 | 294 | }); 295 | ui.horizontal(|ui| { 296 | let (hour,min,_,_) = cv.get_hours_minutes_seconds_fract_time(); 297 | ui.label(get_translated_string_arguments(&ctx,lang,"print_time_msg",&[hour.to_string(), min.to_string()])); 298 | }); 299 | }; 300 | 301 | for err in gui_data.get_errors(){ 302 | 303 | let (code, message) = err.get_code_and_message(); 304 | ui.horizontal(|ui| { 305 | ui.label(format!("Error {:#X}",code)); 306 | }); 307 | ui.horizontal(|ui| { 308 | ui.label(message.to_string()); 309 | }); 310 | }; 311 | 312 | if gui_data.is_command_running(){ 313 | ui.horizontal(|ui| { 314 | ui.heading("Running"); 315 | }); 316 | ui.horizontal(|ui| { 317 | ui.label(format!("Status {}",gui_data.get_command_state())); 318 | }); 319 | } 320 | 321 | if let Some(str) = gui_data.get_gcode() { 322 | ui.horizontal(|ui| { 323 | ui.style_mut().spacing.button_padding = egui::Vec2::new(50., 20.); 324 | //ui.style_mut().body_text_style = TextStyle::Heading; 325 | ui.style_mut().override_text_style = Some(TextStyle::Heading); 326 | 327 | 328 | ui.centered_and_justified(|ui| { 329 | if ui.button("Save").clicked() { 330 | let path = FileDialog::new() 331 | .add_filter("gcode", &["gcode"]) 332 | .show_save_single_file() 333 | .unwrap(); 334 | 335 | let path = match path { 336 | Some(path) => path, 337 | None => return, 338 | }; 339 | 340 | let mut file = File::create(path).unwrap(); 341 | file.write_all(str.as_bytes()).unwrap(); 342 | } 343 | }); 344 | }); 345 | } 346 | if let Some(cmds) = gui_data.get_commands() { 347 | plot_window_resp = egui::Window::new(&get_translated_string(&ctx, lang, "viewer")) 348 | .open(&mut viewer_open) 349 | .default_size(egui::Vec2::new(400.0, 400.0)) 350 | .show(&egui_ctx, |ui| { 351 | 352 | 353 | let line = Line::new(Values::from_values(vec![Value{x:0.0,y: 0.0},Value{x:0.0,y: build_y as f64 },Value{x:build_x as f64 ,y: build_y as f64 },Value{x:build_x as f64,y: 0.0 },Value{x:0.0,y: 0.0},Value{x:0.0,y: build_y as f64 }])).width(5.0); 354 | 355 | 356 | let mut layer_height = 0.0; 357 | 358 | ui.style_mut().spacing.slider_width = ui.available_width() - 100.0; 359 | let drag_resp = ui.add(egui::Slider::new(&mut index, 1..=layers-1) 360 | .prefix("x: ")); 361 | 362 | 363 | let plot = Plot::new("items_demo") 364 | .legend(Legend::default().position(Corner::RightBottom)) 365 | .show_x(false) 366 | .show_y(false) 367 | .data_aspect(1.0); 368 | 369 | 370 | let resp = plot.show(ui, |plot_ui| { 371 | plot_ui.line(line.name(&get_translated_string(&ctx, lang, "Border"))); 372 | 373 | let p1 = plot_ui.screen_from_plot(Value{x:0.0,y:0.0}); 374 | let p2 = plot_ui.screen_from_plot(Value{x:0.0,y:1.0}); 375 | 376 | 377 | let pixels_per_plot_unit = (p2-p1).length(); 378 | 379 | 380 | let mut moves : Vec<_>= (&cmds 381 | .iter() 382 | .group_by(|cmd|{ 383 | if let gladius_shared::types::Command::LayerChange { z, index: _index } = cmd{ 384 | let r = *z == layer_height; 385 | layer_height = *z; 386 | r 387 | }else{ 388 | true 389 | } 390 | })) 391 | .into_iter() 392 | .filter_map(|(change,layer)|{ 393 | if !change{ 394 | None 395 | }else{ 396 | let lines :Vec<_> = layer.into_iter().filter_map(|cmd|{ 397 | 398 | if let gladius_shared::types::Command::MoveAndExtrude{start,end,width,..} = cmd{ 399 | Some(Line::new(Values::from_values(vec![Value{x:start.x,y:start.y},Value{x:end.x,y: end.y }])) 400 | .stroke(Stroke{width: *width as f32 * pixels_per_plot_unit* 0.95,color: Color32::BLUE})) 401 | 402 | }else{ 403 | None 404 | } 405 | }).collect(); 406 | 407 | Some(lines) 408 | } 409 | }) 410 | .collect(); 411 | 412 | layers = moves.len(); 413 | 414 | for move_line in moves.remove(index){ 415 | plot_ui.line(move_line.name("Move")); 416 | } 417 | }); 418 | 419 | drag_resp.union(resp.response) 420 | }); 421 | } 422 | 423 | 424 | 425 | }); 426 | 427 | window_rec = plot_window_resp.as_ref().map(|r| r.response.rect); 428 | 429 | let full_resp = match plot_window_resp.take(){ 430 | Some(mut window_resp) => { 431 | 432 | resp.response.union(window_resp.response.interact(Sense::click_and_drag()).union(window_resp.inner.take().unwrap())) 433 | }, 434 | None => { 435 | resp.response 436 | } 437 | }; 438 | 439 | on_render_screen = !egui_ctx.wants_pointer_input(); 440 | }); 441 | 442 | *control_flow = if quit { 443 | glutin::event_loop::ControlFlow::Exit 444 | } else if needs_repaint { 445 | 446 | gui_data.check_refresh_and_clear(); 447 | display.gl_window().window().request_redraw(); 448 | 449 | glutin::event_loop::ControlFlow::Poll 450 | } else if gui_data.is_command_running(){ 451 | //If command is running keep refreshing 452 | glutin::event_loop::ControlFlow::Poll 453 | }else{ 454 | glutin::event_loop::ControlFlow::Wait 455 | }; 456 | 457 | 458 | { 459 | use glium::Surface as _; 460 | let mut target = display.draw(); 461 | 462 | let color = egui::Rgba::from_rgb(0.0, 0.0, 0.0); 463 | target.clear_color_and_depth((color[0], color[1], color[2], color[3]),1.0); 464 | 465 | // draw things behind egui here 466 | 467 | let (view ,perspective ) = gui_data.get_camera_view_and_proj_matrix(); 468 | 469 | let line_model = glam::Mat4::from_translation(Vec3::new(0.0,0.0,0.0)).to_cols_array_2d(); 470 | 471 | let params = glium::DrawParameters { 472 | depth: glium::Depth { 473 | test: glium::draw_parameters::DepthTest::IfLess, 474 | write: true, 475 | .. Default::default() 476 | }, 477 | //backface_culling: glium::draw_parameters::BackfaceCullingMode::CullClockwise, 478 | .. Default::default() 479 | }; 480 | 481 | gui_data.update_colors(); 482 | gui_data.update_screen_dimensions(Vec2::new(target.get_dimensions().0 as f32,target.get_dimensions().1 as f32 )); 483 | 484 | for obj in gui_data.get_objects(){ 485 | let model = obj.get_model_matrix().to_cols_array_2d(); 486 | let color = obj.color.to_array(); 487 | let (positions, indices) = (&obj.vert_buff,&obj.index_buff);//create_mesh(&display); 488 | target.draw(positions, indices, &model_program, &uniform! {color: color, model: model, view: view, perspective: perspective }, ¶ms).unwrap(); 489 | } 490 | 491 | 492 | target.draw(&line_positions, &line_indices, &line_program, &uniform! { model: line_model, view: view, perspective: perspective }, ¶ms).unwrap(); 493 | 494 | egui_glium.paint(&display, &mut target); 495 | 496 | // draw things on top of egui here 497 | 498 | target.finish().unwrap(); 499 | } 500 | }; 501 | 502 | 503 | 504 | 505 | if r { 506 | redraw(); 507 | } 508 | 509 | match event { 510 | // Platform-dependent event handlers to workaround a winit bug 511 | // See: https://github.com/rust-windowing/winit/issues/987 512 | // See: https://github.com/rust-windowing/winit/issues/1619 513 | glutin::event::Event::RedrawEventsCleared if cfg!(windows) => redraw(), 514 | glutin::event::Event::RedrawRequested(_) if !cfg!(windows) => redraw(), 515 | 516 | glutin::event::Event::WindowEvent { event, .. } => { 517 | 518 | match event{ 519 | WindowEvent::CloseRequested | WindowEvent::Destroyed => { 520 | *control_flow = glutin::event_loop::ControlFlow::Exit; 521 | } 522 | WindowEvent::CursorMoved {position, ..} => { 523 | on_render_screen = on_render_screen && if let Some(rect ) = window_rec { 524 | //println!("{:?} {:?}",rect,position); 525 | !rect.contains(Pos2{x: position.x as f32,y: position.y as f32 }) 526 | } 527 | else{ 528 | true 529 | }; 530 | 531 | if on_render_screen { 532 | gui_data.mouse_move(Vec2::new(position.x as f32, position.y as f32)); 533 | } 534 | 535 | in_window = true; 536 | } 537 | WindowEvent::CursorLeft {..} =>{ 538 | in_window = false; 539 | } 540 | _ => {} 541 | } 542 | 543 | egui_glium.on_event(&event); 544 | 545 | display.gl_window().window().request_redraw(); // TODO: ask egui if the events warrants a repaint instead 546 | } 547 | glutin::event::Event::DeviceEvent {event, ..} => 548 | { 549 | match event { 550 | DeviceEvent::Button {button, state} => { 551 | if button == 1{ 552 | if left_mouse_button_state == ElementState::Released && state == ElementState::Pressed { 553 | gui_data.select_button_pressed() 554 | } 555 | if state == ElementState::Released { 556 | 557 | gui_data.select_button_released() 558 | } 559 | left_mouse_button_state = state; 560 | } 561 | } 562 | 563 | DeviceEvent::MouseMotion {delta: (dx,dy)} =>{ 564 | if left_mouse_button_state == ElementState::Pressed && on_render_screen && in_window{ 565 | gui_data.mouse_move_delta(Vec2::new(dx as f32,dy as f32)) 566 | } 567 | } 568 | DeviceEvent::MouseWheel {delta} =>{ 569 | if on_render_screen && in_window { 570 | if let MouseScrollDelta::LineDelta(_x, y) = delta { 571 | gui_data.mouse_wheel(y) 572 | } 573 | } 574 | } 575 | 576 | _ => { 577 | //println!("{:?}",event); 578 | } 579 | } 580 | } 581 | 582 | _ => { 583 | //println!("{:?}",event) 584 | }, 585 | } 586 | }); 587 | } 588 | 589 | 590 | fn get_translated_string(ctx:& JSONGetText, lang: &str,index_str: &str) -> String{ 591 | get_text!(ctx,lang, index_str).map_or(String::from("Not Translated"),|s| s.to_string()) 592 | } 593 | fn get_translated_string_argument(ctx:& JSONGetText, lang: &str,index_str: &str, argument: String) -> String{ 594 | get_text!(ctx,lang, index_str).map_or(String::from("Not Translated {}"),|s| s.to_string()).replace("{}", &argument) 595 | } 596 | 597 | fn get_translated_string_arguments(ctx:& JSONGetText, lang: &str,index_str: &str, arguments: &[String]) -> String{ 598 | let mut ret_string = get_text!(ctx,lang, index_str).map_or(String::from("Not Translated {}"),|s| s.to_string()); 599 | for (count,arg) in arguments.iter().enumerate(){ 600 | ret_string = ret_string.replace(&format!("{{{}}}",count), &arg); 601 | } 602 | 603 | ret_string 604 | } -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufRead, BufReader}; 2 | use std::process::{Command, Stdio}; 3 | use std::sync::{Arc, RwLock}; 4 | use gladius_shared::messages::Message; 5 | use gladius_shared::types::{CalculatedValues}; 6 | use glam::{Mat4, Vec2, Vec3}; 7 | use itertools::Itertools; 8 | use crate::Errors; 9 | use crate::object::{load, DisplayVertex, Object}; 10 | 11 | use native_dialog::FileDialog; 12 | 13 | pub struct GUIData{ 14 | objects: Vec, 15 | print_area_size: Vec3, 16 | closest_object_point: Option<(usize,Vec3,Vec3 )>, 17 | dragging: bool, 18 | camera: Camera, 19 | settings_path: String, 20 | calc_vals: Arc>>, 21 | gcode: Arc>>, 22 | commands: Arc>>>, 23 | error: Arc>>, 24 | command_running: Arc>, 25 | command_state: Arc>, 26 | refresh: Arc> 27 | 28 | } 29 | 30 | impl GUIData{ 31 | pub fn new(screen_dimensions: Vec2,print_area_size: Vec3) -> Self{ 32 | let mut center_pos = (print_area_size.x/2.0,print_area_size.y/2.0); 33 | 34 | 35 | 36 | GUIData{ 37 | objects: vec![], 38 | print_area_size: print_area_size, 39 | closest_object_point: None, 40 | dragging: false, 41 | camera: Camera::new(screen_dimensions, Vec3::new(center_pos.0 ,center_pos.1 , 0.0)), 42 | settings_path: String::new(), 43 | calc_vals: Arc::new(RwLock::new(None)), 44 | gcode:Arc::new(RwLock::new(None)), 45 | commands: Arc::new(RwLock::new(None)), 46 | error: Arc::new(RwLock::new(None)), 47 | command_running: Arc::new(RwLock::new(false)), 48 | command_state: Arc::new(RwLock::new(String::new())), 49 | refresh: Arc::new(RwLock::new(false)), 50 | } 51 | } 52 | 53 | pub fn extend_objects(&mut self, objs: I ) 54 | where I : IntoIterator 55 | { 56 | self.objects.extend(objs.into_iter()) 57 | } 58 | 59 | pub fn get_objects(&self) -> &Vec{ 60 | &self.objects 61 | } 62 | 63 | pub fn get_command_line_args(&self) -> Vec { 64 | self.objects.iter() 65 | .map(|obj|{ 66 | format!("{{\"Raw\":[\"{}\",{:?}]}} ", obj.file_path.replace('\\', "\\\\"),obj.get_model_matrix().transpose().to_cols_array_2d()) 67 | }) 68 | .collect() 69 | } 70 | 71 | pub fn update_colors(&mut self){ 72 | self.objects 73 | .iter_mut() 74 | .enumerate() 75 | .for_each(|(index,obj)| { 76 | let in_build_area = obj.aabb.as_ref().map(|aabb| { 77 | !(aabb.min_x < 0.0 || aabb.min_y < 0.0 || aabb.min_z < 0.0 || aabb.max_x > self.print_area_size.x || aabb.max_y > self.print_area_size.y || aabb.max_z > self.print_area_size.z) 78 | }).unwrap_or(false); 79 | 80 | let this_selected = self.closest_object_point.map(|(i,_,_)| i== index).unwrap_or(false); 81 | 82 | obj.color = match (obj.hovered,in_build_area,this_selected){ 83 | (false,false,false) => Vec3::new(1.0, 0.0, 0.0), 84 | (false,true,false) => Vec3::new(1.0, 1.0, 0.0), 85 | (true,true,false) => Vec3::new(0.0, 0.0, 1.0), 86 | (true,false,false) => Vec3::new(1.0, 0.0, 0.0), 87 | (_,false,true) => Vec3::new(1.0, 0.0, 1.0), 88 | (_,true,true) => Vec3::new(0.0, 1.0, 1.0), 89 | }; 90 | 91 | 92 | }); 93 | } 94 | 95 | pub fn set_settings_path(&mut self, path: String){ 96 | self.settings_path = path; 97 | } 98 | 99 | pub fn get_settings_path(&mut self ) -> &String{ 100 | &self.settings_path 101 | } 102 | 103 | 104 | pub fn can_slice(&self) -> bool{ 105 | !self.objects.is_empty() && !self.settings_path.is_empty() && ! *self.command_running.read().unwrap() 106 | } 107 | 108 | pub fn start_slice(&mut self) { 109 | *self.calc_vals.write().unwrap() = None; 110 | *self.gcode.write().unwrap() = None; 111 | *self.error.write().unwrap() = None; 112 | *self.commands.write().unwrap() = None; 113 | *self.command_running.write().unwrap() = true; 114 | 115 | 116 | let args = self.get_command_line_args() ; 117 | 118 | let calc_vals_clone = self.calc_vals.clone(); 119 | let commands_clone = self.commands.clone(); 120 | let gcode_clone = self.gcode.clone(); 121 | let error_clone = self.error.clone(); 122 | let command_running_clone = self.command_running.clone(); 123 | let command_state_clone = self.command_state.clone(); 124 | let settings_path_clone = self.settings_path.clone(); 125 | let refresh_clone = self.refresh.clone(); 126 | 127 | 128 | std::thread::spawn(move ||{ 129 | 130 | let mut command = if cfg!(target_os = "linux") { 131 | Command::new("./slicer/gladius_slicer") 132 | } else if cfg!(target_os = "windows") { 133 | Command::new("slicer\\gladius_slicer.exe") 134 | } else { 135 | unimplemented!() 136 | }; 137 | 138 | for arg in &args { 139 | //"{\"Raw\":[\"test_3D_models\\3DBenchy.stl\",[[1.0,0.0,0.0,124.0],[0.0,1.0,0.0,105.0],[0.0,0.0,1.0,0.0],[0.0,0.0,0.0,1.0]] }" 140 | command.arg(arg); 141 | print!("{}",arg.replace('\\', "\\\\").replace('\"', "\\\"")); 142 | } 143 | 144 | println!(); 145 | 146 | let cpus = format!("{}", (num_cpus::get()).max(1)); 147 | 148 | println!("{}",cpus); 149 | 150 | if let Ok(mut child) = command 151 | .arg("-m") 152 | .arg("-s") 153 | .arg( settings_path_clone.replace('\\', "\\\\")) 154 | .arg("-j") 155 | .arg(cpus) 156 | .stdout(Stdio::piped()) 157 | .stderr(Stdio::piped()) 158 | .spawn() 159 | { 160 | 161 | 162 | // Loop over the output from the first process 163 | if let Some(ref mut stdout) = child.stdout { 164 | while let Ok::( msg) = bincode::deserialize_from(&mut *stdout) { 165 | match msg { 166 | Message::CalculatedValues(cv) => { 167 | *calc_vals_clone.write().unwrap() = Some(cv); 168 | } 169 | Message::Commands(cmds) => { 170 | 171 | *commands_clone.write().unwrap() = Some(cmds); 172 | } 173 | Message::GCode(str) => { 174 | *gcode_clone.write().unwrap() = Some(str); 175 | } 176 | Message::Error(err) => { 177 | *error_clone.write().unwrap() = Some(Errors::SlicerError(err)); 178 | } 179 | Message::StateUpdate(msg) =>{ 180 | *command_state_clone.write().unwrap() = msg; 181 | } 182 | Message::Warning(_warn) =>{ 183 | } 184 | } 185 | 186 | *refresh_clone.write().unwrap() = true; 187 | } 188 | } 189 | 190 | if let Some(ref mut stderr) = child.stderr { 191 | let buff = BufReader::new(stderr); 192 | if buff.lines().next().is_some() { 193 | *error_clone.write().unwrap() = Some(Errors::SlicerCommunicationIssue); 194 | } 195 | } 196 | } 197 | else{ 198 | *error_clone.write().unwrap() = Some(Errors::SlicerApplicationIssue); 199 | 200 | } 201 | 202 | *command_running_clone.write().unwrap() = false; 203 | }); 204 | } 205 | 206 | pub fn mouse_move(&mut self, new_position: Vec2) { 207 | 208 | let inv_vp = (self.camera.proj_mat * self.camera.view_mat ).inverse(); 209 | 210 | let mouse_pos = Vec3::new((new_position.x / (self.camera.screen_dimensions.x as f32 * 0.5) - 1.0) as f32, -(new_position.y / (self.camera.screen_dimensions.y as f32 * 0.5) - 1.0) as f32, 1.0); 211 | 212 | let world_pos = inv_vp.transform_point3(mouse_pos); 213 | 214 | let cam_dir = world_pos.normalize(); 215 | 216 | if !self.dragging { 217 | self.closest_object_point = self.objects 218 | .iter_mut() 219 | .enumerate() 220 | .filter_map(|(en, obj)| { 221 | obj.hovered = false; 222 | obj.intersect_with_ray(self.camera.location, cam_dir).map(|p| (en, obj, p)) 223 | }) 224 | .min_by(|(_, _, (ta, _)), (_, _, (tb, _))| ta.partial_cmp(tb).unwrap()) 225 | .map(|(index, obj, point)| (index, point.1,*obj.get_location())); 226 | 227 | if let Some((index, _,_)) = self.closest_object_point { 228 | self.objects[index].hovered = true; 229 | } 230 | }else{ 231 | let (index, intersect_point, translation) = self.closest_object_point.expect("If selected closest point must be set"); 232 | let z_height = intersect_point.z; 233 | 234 | let (x_intercept, y_intercept) ={ 235 | let z_diff = self.camera.location.z - z_height; 236 | let y_over_z_slope = cam_dir.y / cam_dir.z; 237 | let x_over_z_slope = cam_dir.x / cam_dir.z; 238 | 239 | let x_diff = x_over_z_slope * z_diff; 240 | let y_diff = y_over_z_slope * z_diff; 241 | 242 | (self.camera.location.x - x_diff, self.camera.location.y - y_diff) 243 | }; 244 | 245 | let x_diff = x_intercept - intersect_point.x; 246 | let y_diff = y_intercept - intersect_point.y; 247 | 248 | self.objects[index].get_mut_location().x = translation.x + x_diff; 249 | self.objects[index].get_mut_location().y = translation.y + y_diff; 250 | 251 | self.objects[index].revalidate_cache(); 252 | //*gcode.write().unwrap() = None; 253 | //*calc_vals.write().unwrap() = None; 254 | 255 | } 256 | 257 | 258 | } 259 | 260 | pub fn mouse_move_delta(&mut self, delta: Vec2) { 261 | if !self.dragging { 262 | self.camera.yaw += delta.x as f32 * -0.01; 263 | self.camera.pitch = (self.camera.pitch + delta.y as f32 * 0.01).min(std::f32::consts::FRAC_PI_2 - 0.001).max(-std::f32::consts::FRAC_PI_2 + 0.001); 264 | } 265 | } 266 | 267 | pub fn mouse_wheel(&mut self, wheel_move: f32) { 268 | self.camera.zoom = (self.camera.zoom * (1.0 - (0.1 * wheel_move.signum()))).min(1000.0).max(5.0); 269 | } 270 | 271 | pub fn get_camera_view_and_proj_matrix(&mut self) -> ([[f32;4];4],[[f32;4];4]){ 272 | self.camera.rebuild_matrices(); 273 | (self.camera.get_camera_view_matrix(),self.camera.get_camera_proj_matrix()) 274 | } 275 | 276 | 277 | 278 | pub fn select_button_pressed(&mut self){ 279 | 280 | if self.closest_object_point.is_some() && !self.dragging{ 281 | self.dragging = true; 282 | } 283 | } 284 | 285 | pub fn select_button_released(&mut self){ 286 | 287 | self.dragging = false; 288 | 289 | } 290 | 291 | pub fn update_screen_dimensions(&mut self,screen_dimensions: Vec2){ 292 | self.camera.update_screen_dimensions(screen_dimensions) 293 | } 294 | 295 | pub fn is_command_running(&self) -> bool { 296 | *self.command_running.read().unwrap() 297 | } 298 | 299 | pub fn check_refresh_and_clear(&self) -> bool{ 300 | let old = *self.refresh.write().unwrap(); 301 | *self.refresh.write().unwrap() = false; 302 | old 303 | } 304 | 305 | pub fn load_settings_file(&mut self){ 306 | let path = FileDialog::new() 307 | .add_filter("Supported settings Types", &["json"]) 308 | .show_open_single_file() 309 | .unwrap(); 310 | 311 | let path = match path { 312 | Some(path) => path, 313 | None => return, 314 | }; 315 | 316 | self.set_settings_path(path.into_os_string().into_string().unwrap()); 317 | } 318 | pub fn load_model(&mut self, display: &glium::Display){ 319 | let paths = FileDialog::new() 320 | .add_filter("Supported Model Types", &["stl", "3mf"]) 321 | .show_open_multiple_file() 322 | .unwrap(); 323 | 324 | for path in paths { 325 | 326 | let model_path = path.into_os_string().into_string().unwrap(); 327 | 328 | match load(&model_path,display) 329 | { 330 | Ok(objs) => { self.extend_objects(objs) } 331 | Err(e) => { *self.error.write().unwrap() = Some(Errors::SlicerError(e)) } 332 | } 333 | } 334 | } 335 | 336 | pub fn get_calculated_values(&self) -> Option{ 337 | self.calc_vals.read().unwrap().clone() 338 | } 339 | 340 | pub fn get_command_state(&self) -> String{ 341 | self.command_state.read().unwrap().clone() 342 | } 343 | 344 | pub fn get_errors(&self) -> Vec{ 345 | self.error.read().unwrap().clone().into_iter().collect_vec() 346 | } 347 | 348 | pub fn get_gcode(&self) -> Option{ 349 | self.gcode.read().unwrap().clone() 350 | } 351 | 352 | pub fn get_commands(&self) -> Option>{ 353 | self.commands.read().unwrap().clone() 354 | } 355 | 356 | } 357 | 358 | struct GcodeViewerStates{ 359 | layer_count: usize 360 | } 361 | 362 | struct Camera{ 363 | proj_mat: Mat4, 364 | view_mat: Mat4, 365 | location: Vec3, 366 | center_loc: Vec3, 367 | screen_dimensions: Vec2, 368 | pub zoom: f32, 369 | pub pitch: f32, 370 | pub yaw: f32 371 | 372 | } 373 | 374 | impl Camera{ 375 | fn new(screen_dimensions: Vec2, center_loc: Vec3) -> Self { 376 | let zoom = 400.0; 377 | let pitch = std::f32::consts::FRAC_PI_4; 378 | let yaw = -std::f32::consts::FRAC_PI_4 + 0.12; 379 | 380 | let aspect_ratio = screen_dimensions.x as f32 / screen_dimensions.y as f32; 381 | 382 | let camera_vec = glam::Vec3::new(zoom *yaw.cos() * pitch.cos() ,zoom * yaw.sin() * pitch.cos() , zoom * pitch.sin()); 383 | 384 | let proj_mat = glam::Mat4::perspective_infinite_rh(60.0_f32.to_radians(),aspect_ratio,0.1); 385 | let location = camera_vec +center_loc; 386 | let view_mat =glam::Mat4::look_at_rh(location,center_loc,glam::Vec3::new(0.0,0.0,1.0)); 387 | 388 | 389 | Camera{ 390 | zoom, 391 | pitch, 392 | yaw, 393 | center_loc, 394 | location, 395 | view_mat, 396 | proj_mat, 397 | screen_dimensions 398 | } 399 | } 400 | 401 | pub fn rebuild_matrices(&mut self) { 402 | 403 | let aspect_ratio = self.screen_dimensions.x as f32 / self.screen_dimensions.y as f32; 404 | 405 | let camera_vec = glam::Vec3::new(self.zoom *self.yaw.cos() * self.pitch.cos() ,self.zoom * self.yaw.sin() * self.pitch.cos() , self.zoom * self.pitch.sin()); 406 | 407 | self.proj_mat = glam::Mat4::perspective_infinite_rh(60.0_f32.to_radians(),aspect_ratio,0.1); 408 | self.location = camera_vec +self.center_loc; 409 | self.view_mat =glam::Mat4::look_at_rh(self.location,self.center_loc,glam::Vec3::new(0.0,0.0,1.0)); 410 | 411 | } 412 | 413 | pub fn update_screen_dimensions(&mut self,screen_dimensions: Vec2){ 414 | self.screen_dimensions = screen_dimensions; 415 | } 416 | 417 | pub fn get_camera_view_matrix(&self) -> [[f32;4];4] { 418 | self.view_mat.to_cols_array_2d() 419 | } 420 | 421 | pub fn get_camera_proj_matrix(&self) -> [[f32;4];4]{ 422 | self.proj_mat.to_cols_array_2d() 423 | } 424 | 425 | } 426 | -------------------------------------------------------------------------------- /src/object.rs: -------------------------------------------------------------------------------- 1 | use crate::vertex; 2 | use gladius_shared::error::SlicerErrors; 3 | use gladius_shared::loader::*; 4 | use glam::{Mat4, Vec3}; 5 | use glium::implement_vertex; 6 | use itertools::*; 7 | use std::ffi::OsStr; 8 | use std::path::Path; 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | pub struct DisplayVertex { 12 | pub position: (f32, f32, f32), 13 | } 14 | 15 | implement_vertex!(DisplayVertex, position); 16 | 17 | #[derive(Debug)] 18 | pub struct AABB { 19 | pub min_x: f32, 20 | pub min_y: f32, 21 | pub min_z: f32, 22 | pub max_x: f32, 23 | pub max_y: f32, 24 | pub max_z: f32, 25 | } 26 | 27 | impl AABB { 28 | pub fn intersect_with_ray(&self, ray_origin: Vec3, ray_dir: Vec3) -> bool { 29 | //https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms 30 | // r.dir is unit direction vector of ray 31 | // lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner 32 | // r.org is origin of ray 33 | let t1 = (self.min_x - ray_origin.x) / ray_dir.x; 34 | let t2 = (self.max_x - ray_origin.x) / ray_dir.x; 35 | let t3 = (self.min_y - ray_origin.y) / ray_dir.y; 36 | let t4 = (self.max_y - ray_origin.y) / ray_dir.y; 37 | let t5 = (self.min_z - ray_origin.z) / ray_dir.z; 38 | let t6 = (self.max_z - ray_origin.z) / ray_dir.z; 39 | 40 | let tmin = t1.min(t2).max(t3.min(t4)).max(t5.min(t6)); 41 | let tmax = t1.max(t2).min(t3.max(t4)).min(t5.max(t6)); 42 | 43 | // if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us 44 | if tmax < 0.0 { 45 | return false; 46 | } 47 | 48 | // if tmin > tmax, ray doesn't intersect AABB 49 | if tmin > tmax { 50 | return false; 51 | } 52 | true 53 | } 54 | } 55 | #[derive(Debug)] 56 | pub struct Object { 57 | pub name: String, 58 | pub file_path: String, 59 | location: Vec3, 60 | default_offset: Vec3, 61 | scale: Vec3, 62 | pub color: Vec3, 63 | pub hovered: bool, 64 | pub vert_buff: glium::VertexBuffer, 65 | pub transformed_verts: Option>, 66 | pub aabb: Option, 67 | pub index_buff: glium::IndexBuffer, 68 | } 69 | impl Object { 70 | pub fn set_scale(&mut self, scale: Vec3) { 71 | self.scale = scale; 72 | } 73 | 74 | pub fn set_location(&mut self, location: Vec3) { 75 | self.location = location; 76 | } 77 | 78 | pub fn get_mut_location(&mut self) -> &mut Vec3 { 79 | &mut self.location 80 | } 81 | 82 | pub fn get_location(&self) -> &Vec3 { 83 | &self.location 84 | } 85 | 86 | pub fn get_mut_scale(&mut self) -> &mut Vec3 { 87 | &mut self.scale 88 | } 89 | pub fn get_scale(&self) -> &Vec3 { 90 | &self.scale 91 | } 92 | 93 | pub fn invalidate_cache(&mut self) { 94 | self.transformed_verts = None; 95 | self.aabb = None; 96 | } 97 | 98 | pub fn revalidate_cache(&mut self) { 99 | println!("revalidate"); 100 | let vertices = { 101 | let mat = self.get_model_matrix(); 102 | 103 | self.vert_buff 104 | .read() 105 | .unwrap() 106 | .iter() 107 | .map(|v| mat.transform_point3(Vec3::new(v.position.0, v.position.1, v.position.2))) 108 | .collect_vec() 109 | }; 110 | 111 | let aabb = { 112 | let (min_x, max_x, min_y, max_y, min_z, max_z) = vertices.iter().fold( 113 | ( 114 | f32::INFINITY, 115 | f32::NEG_INFINITY, 116 | f32::INFINITY, 117 | f32::NEG_INFINITY, 118 | f32::INFINITY, 119 | f32::NEG_INFINITY, 120 | ), 121 | |a, b| { 122 | ( 123 | a.0.min(b.x), 124 | a.1.max(b.x), 125 | a.2.min(b.y), 126 | a.3.max(b.y), 127 | a.4.min(b.z), 128 | a.5.max(b.z), 129 | ) 130 | }, 131 | ); 132 | 133 | AABB { 134 | min_x, 135 | max_x, 136 | min_y, 137 | max_y, 138 | min_z, 139 | max_z, 140 | } 141 | }; 142 | 143 | self.transformed_verts = Some(vertices); 144 | self.aabb = Some(aabb); 145 | } 146 | 147 | pub fn make_copy(&self, display: &glium::Display) -> Self { 148 | let positions = glium::VertexBuffer::new(display, &self.vert_buff.read().unwrap()).unwrap(); 149 | let indices = glium::IndexBuffer::new( 150 | display, 151 | glium::index::PrimitiveType::TrianglesList, 152 | &self.index_buff.read().unwrap(), 153 | ) 154 | .unwrap(); 155 | 156 | Object { 157 | name: self.name.clone(), 158 | file_path: self.file_path.clone(), 159 | location: self.location, 160 | default_offset: self.default_offset, 161 | scale: self.scale, 162 | color: self.color, 163 | hovered: false, 164 | vert_buff: positions, 165 | index_buff: indices, 166 | transformed_verts: None, 167 | aabb: None, 168 | } 169 | } 170 | 171 | pub fn get_model_matrix(&self) -> Mat4 { 172 | glam::Mat4::from_translation(self.location) 173 | * glam::Mat4::from_scale(self.scale) 174 | * glam::Mat4::from_translation(self.default_offset) 175 | } 176 | 177 | pub fn intersect_with_ray(&mut self, ray_origin: Vec3, ray_dir: Vec3) -> Option<(f32, Vec3)> { 178 | let vertices = self.transformed_verts.take().unwrap_or_else(|| { 179 | let mat = self.get_model_matrix(); 180 | 181 | self.vert_buff 182 | .read() 183 | .unwrap() 184 | .iter() 185 | .map(|v| mat.transform_point3(Vec3::new(v.position.0, v.position.1, v.position.2))) 186 | .collect() 187 | }); 188 | 189 | let aabb = self.aabb.take().unwrap_or_else(|| { 190 | let (min_x, max_x, min_y, max_y, min_z, max_z) = vertices.iter().fold( 191 | ( 192 | f32::INFINITY, 193 | f32::NEG_INFINITY, 194 | f32::INFINITY, 195 | f32::NEG_INFINITY, 196 | f32::INFINITY, 197 | f32::NEG_INFINITY, 198 | ), 199 | |a, b| { 200 | ( 201 | a.0.min(b.x), 202 | a.1.max(b.x), 203 | a.2.min(b.y), 204 | a.3.max(b.y), 205 | a.4.min(b.z), 206 | a.5.max(b.z), 207 | ) 208 | }, 209 | ); 210 | 211 | AABB { 212 | min_x, 213 | max_x, 214 | min_y, 215 | max_y, 216 | min_z, 217 | max_z, 218 | } 219 | }); 220 | 221 | if !aabb.intersect_with_ray(ray_origin, ray_dir) { 222 | self.transformed_verts = Some(vertices); 223 | self.aabb = Some(aabb); 224 | return None; 225 | } 226 | 227 | let ret = self 228 | .index_buff 229 | .read() 230 | .unwrap() 231 | .iter() 232 | .tuples::<(_, _, _)>() 233 | .map(|(v0, v1, v2)| { 234 | //make points 235 | ( 236 | vertices[*v0 as usize], 237 | vertices[*v1 as usize], 238 | vertices[*v2 as usize], 239 | ) 240 | }) 241 | .filter_map(|(v0, v1, v2)| { 242 | let edge1 = v1 - v0; 243 | let edge2 = v2 - v0; 244 | 245 | let h = ray_dir.cross(edge2); 246 | let a = edge1.dot(h); 247 | 248 | if a > -0.0001 && a < 0.0001 { 249 | None 250 | } else { 251 | let f = 1.0 / a; 252 | let s = ray_origin - v0; 253 | let u = f * s.dot(h); 254 | if u < 0.0 || u > 1.0 { 255 | None 256 | } else { 257 | let q = s.cross(edge1); 258 | let v = f * ray_dir.dot(q); 259 | if v < 0.0 || u + v > 1.0 { 260 | None 261 | } else { 262 | let t = f * edge2.dot(q); 263 | if t > f32::EPSILON 264 | // ray intersection 265 | { 266 | Some(t) 267 | } else // This means that there is a line intersection but not a ray intersection. 268 | { 269 | None 270 | } 271 | } 272 | } 273 | } 274 | }) 275 | .min_by(|a, b| a.partial_cmp(b).unwrap()) 276 | .map(|t| (t, ray_origin + ray_dir * t)); 277 | 278 | self.transformed_verts = Some(vertices); 279 | self.aabb = Some(aabb); 280 | 281 | ret 282 | } 283 | 284 | /* 285 | const float EPSILON = 0.0000001; 286 | Vector3D vertex0 = inTriangle->vertex0; 287 | Vector3D vertex1 = inTriangle->vertex1; 288 | Vector3D vertex2 = inTriangle->vertex2; 289 | Vector3D edge1, edge2, h, s, q; 290 | float a,f,u,v; 291 | edge1 = vertex1 - vertex0; 292 | edge2 = vertex2 - vertex0; 293 | h = rayVector.crossProduct(edge2); 294 | a = edge1.dotProduct(h); 295 | if (a > -EPSILON && a < EPSILON) 296 | return false; // This ray is parallel to this triangle. 297 | f = 1.0/a; 298 | s = rayOrigin - vertex0; 299 | u = f * s.dotProduct(h); 300 | if (u < 0.0 || u > 1.0) 301 | return false; 302 | q = s.crossProduct(edge1); 303 | v = f * rayVector.dotProduct(q); 304 | if (v < 0.0 || u + v > 1.0) 305 | return false; 306 | // At this stage we can compute t to find out where the intersection point is on the line. 307 | float t = f * edge2.dotProduct(q); 308 | if (t > EPSILON) // ray intersection 309 | { 310 | outIntersectionPoint = rayOrigin + rayVector * t; 311 | return true; 312 | } 313 | else // This means that there is a line intersection but not a ray intersection. 314 | return false; 315 | }*/ 316 | } 317 | 318 | pub fn load(filepath: &str, display: &glium::Display) -> Result, SlicerErrors> { 319 | let model_path = Path::new(filepath); 320 | let extension = model_path 321 | .extension() 322 | .and_then(OsStr::to_str) 323 | .expect("File Parse Issue"); 324 | 325 | let loader: &dyn Loader = match extension.to_lowercase().as_str() { 326 | "stl" => &STLLoader {}, 327 | "3mf" => &ThreeMFLoader {}, 328 | _ => panic!("File Format {} not supported", extension), 329 | }; 330 | 331 | Ok(loader 332 | .load(model_path.to_str().unwrap())? 333 | .into_iter() 334 | .map(|(vertices, triangles)| { 335 | let display_vertices: Vec = vertices 336 | .into_iter() 337 | .map(|v| vertex([v.x as f32, v.y as f32, v.z as f32])) 338 | .collect(); 339 | 340 | let indices: Vec = triangles 341 | .into_iter() 342 | .flat_map(|tri| tri.verts.into_iter()) 343 | .map(|u| u as u32) 344 | .collect(); 345 | 346 | let positions = glium::VertexBuffer::new(display, &display_vertices).unwrap(); 347 | let indices = glium::IndexBuffer::new( 348 | display, 349 | glium::index::PrimitiveType::TrianglesList, 350 | &indices, 351 | ) 352 | .unwrap(); 353 | 354 | let (min_x, max_x, min_y, max_y, min_z) = display_vertices.iter().fold( 355 | ( 356 | f32::INFINITY, 357 | f32::NEG_INFINITY, 358 | f32::INFINITY, 359 | f32::NEG_INFINITY, 360 | f32::INFINITY, 361 | ), 362 | |a, b| { 363 | ( 364 | a.0.min(b.position.0), 365 | a.1.max(b.position.0), 366 | a.2.min(b.position.1), 367 | a.3.max(b.position.1), 368 | a.4.min(b.position.2), 369 | ) 370 | }, 371 | ); 372 | 373 | let model_path = Path::new(filepath).file_name().unwrap(); 374 | Object { 375 | name: model_path.to_string_lossy().to_string(), 376 | file_path: filepath.to_string(), 377 | location: Vec3::new(0.0, 0.0, 0.0), 378 | scale: Vec3::new(1.0, 1.0, 1.0), 379 | default_offset: Vec3::new(-(max_x + min_x) / 2.0, -(max_y + min_y) / 2.0, -min_z), 380 | color: Vec3::new(1.0, 1.0, 0.0), 381 | index_buff: indices, 382 | vert_buff: positions, 383 | transformed_verts: None, 384 | aabb: None, 385 | hovered: false, 386 | } 387 | }) 388 | .collect()) 389 | } 390 | -------------------------------------------------------------------------------- /src/shaders.rs: -------------------------------------------------------------------------------- 1 | pub const VERTEX_SHADER_SRC: &str = r#" 2 | #version 150 3 | in vec3 position; 4 | out vec3 viewPosition; 5 | out vec3 v_position; 6 | out vec3 v_color; 7 | uniform vec3 color; 8 | uniform mat4 perspective; 9 | uniform mat4 view; 10 | uniform mat4 model; 11 | void main() { 12 | mat4 modelview = view * model; 13 | viewPosition = (modelview * vec4(position, 0.0)).xyz; 14 | gl_Position = perspective * modelview * vec4(position, 1.0); 15 | v_color = color; 16 | 17 | v_position = gl_Position.xyz / gl_Position.w; 18 | } 19 | "#; 20 | 21 | pub const FRAGMENT_SHADER_SRC: &str = r#" 22 | #version 150 23 | in vec3 viewPosition; 24 | in vec3 v_position; 25 | in vec3 v_color; 26 | out vec4 color; 27 | void main() { 28 | vec3 xTangent = vec3(dFdx(viewPosition.x),dFdx(viewPosition.y),dFdx(viewPosition.z)); 29 | vec3 yTangent = vec3(dFdy(viewPosition.x),dFdy(viewPosition.y),dFdy(viewPosition.z)); 30 | vec3 faceNormal = normalize( cross( xTangent, yTangent )); 31 | 32 | 33 | vec3 ambient_color = v_color * 0.2; 34 | vec3 diffuse_color = v_color; 35 | vec3 specular_color = vec3(1.0) - (vec3(1.0) - v_color ) * 0.2; 36 | 37 | float diffuse = max(dot(normalize(faceNormal), normalize(vec3(0., 0.0, 0.1))), 0.0); 38 | float specular = pow(diffuse, 128.0); 39 | color = vec4(ambient_color + diffuse * diffuse_color + specular * specular_color, 1.0); 40 | 41 | 42 | } 43 | "#; 44 | pub const LINE_VERTEX_SHADER_SRC: &str = r#" 45 | #version 150 46 | in vec3 position; 47 | uniform mat4 perspective; 48 | uniform mat4 view; 49 | uniform mat4 model; 50 | void main() { 51 | mat4 modelview = view * model; 52 | gl_Position = perspective * modelview * vec4(position, 1.0); 53 | } 54 | "#; 55 | 56 | pub const LINE_FRAGMENT_SHADER_SRC: &str = r#" 57 | #version 140 58 | out vec4 color; 59 | void main() { 60 | 61 | color = vec4( vec3(0.0, 0.0, 1.0), 1.0); 62 | 63 | } 64 | "#; 65 | --------------------------------------------------------------------------------