├── LICENSE ├── README.md ├── README_CN.md ├── __init__.py ├── blender_manifest.toml ├── keymaps.py ├── languages ├── __init__.py ├── simplified_chinese.py └── traditional_chinese.py ├── operators.py ├── properties.py └── ui.py /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Language: EN [CN][ReadmeCN] 2 | 3 | [ReadmeCN]: ./README_CN.md 4 | 5 | # Toggle Language 6 | [![Version][]](https://github.com/Mister-Kin/ToggleLanguage/releases) [![Blender Version][]](https://www.blender.org/download/) [![Downloads][]](https://github.com/Mister-Kin/ToggleLanguage/releases/latest) [![License][]](./LICENSE) 7 | 8 | [Blender Version]: https://img.shields.io/badge/blender-v2.83+-blue 9 | [Downloads]: https://img.shields.io/github/downloads/Mister-Kin/ToggleLanguage/total?color=blue 10 | [Version]: https://img.shields.io/github/v/release/Mister-Kin/ToggleLanguage?include_prereleases&color=blue 11 | [License]: https://img.shields.io/github/license/Mister-Kin/ToggleLanguage?color=blue 12 | 13 | ## Introduction 14 | An addon for blender, aiming to quickly and easily toggle UI between two languages by using one click instead of repeatedly opening preferences setting. 15 | 16 | ## Features 17 | - One click to toggle UI language (support multiple languages and hotkey) 18 | - One click to open user preferences (support hotkey) 19 | - Addon's Keymap (support customized hotkey) 20 | - One click to switch hint mode: default mode and developer mode 21 | - One click to delete all collections and objects in current scene 22 | - One click to add video progress bar 23 | - Auto setup model blueprint reference images 24 | - ...... 25 | 26 | For more detailed features introduction, please see [User's Manual](https://mister-kin.github.io/works/software-works/toggle-language/). 27 | 28 | ## Download 29 | [Jump to Download Page][] 30 | 31 | [Jump to Download Page]: https://github.com/Mister-Kin/ToggleLanguage/releases/latest 32 | 33 | ## Usage 34 | [Jump to Documentation Page][] 35 | 36 | [Offline Documentation - PDF][] 37 | 38 | [Jump to Documentation Page]: https://mister-kin.github.io/works/software-works/toggle-language/ 39 | [Offline Documentation - PDF]: https://github.com/Mister-Kin/OpenDocs/releases/download/latex2pdf/toggle_language.pdf 40 | 41 | ## Author 42 | **ToggleLanguage** © Mr. Kin, all files released under the [GNU GPL v3.0][] license. 43 | 44 | Authored and maintained by Mr. Kin. 45 | 46 | > [Blog][] · [GitHub][] · [Weibo][] · [Zhihu][] · [AcFun][] · [Bilibili][] · [Youku][] · [Headline][] · [YouTube][] 47 | 48 | [GNU GPL v3.0]: ./LICENSE 49 | [Blog]: https://mister-kin.github.io 50 | [GitHub]: https://github.com/mister-kin 51 | [Weibo]: https://weibo.com/6270111192 52 | [Bilibili]: http://space.bilibili.com/17025250? 53 | [Youku]: http://i.youku.com/i/UNjA3MTk5Mjgw?spm=a2hzp.8253869.0.0 54 | [YouTube]: https://www.youtube.com/@Mister-Kin 55 | [Headline]: https://www.toutiao.com/c/user/835254071079053/#mid=1663279303982091 56 | [Zhihu]: https://www.zhihu.com/people/drwu-94 57 | [AcFun]: https://www.acfun.cn/u/73269306 58 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 语言: [英][Readme] 中 2 | 3 | [Readme]: ./README.md 4 | 5 | # 切换语言 6 | [![Version][]](https://github.com/Mister-Kin/ToggleLanguage/releases) [![Blender Version][]](https://www.blender.org/download/) [![Downloads][]](https://github.com/Mister-Kin/ToggleLanguage/releases/latest) [![License][]](./LICENSE) 7 | 8 | [Blender Version]: https://img.shields.io/badge/blender-v2.83+-blue 9 | [Downloads]: https://img.shields.io/github/downloads/Mister-Kin/ToggleLanguage/total?color=blue 10 | [Version]: https://img.shields.io/github/v/release/Mister-Kin/ToggleLanguage?include_prereleases&color=blue 11 | [License]: https://img.shields.io/github/license/Mister-Kin/ToggleLanguage?color=blue 12 | 13 | ## 简介 14 | 一个Blender插件,目的是通过一次点击在两种语言之间快速而方便地切换用户界面,而不是重复地打开偏好设置。 15 | 16 | ## 功能 17 | - 一键切换用户界面语言(支持多种语言和快捷键) 18 | - 一键打开用户偏好设置(支持快捷键) 19 | - 插件的键位映射功能(允许自定义快捷键) 20 | - 一键切换提示模式:默认模式和开发者模式 21 | - 一键删除当前场景集合和物体 22 | - 一键添加视频进度条 23 | - 自动放置模型蓝图参考图片 24 | - …… 25 | 26 | 更多详细的功能介绍请详看[使用手册](https://mister-kin.github.io/works/software-works/toggle-language/)。 27 | 28 | ## 下载 29 | [跳转到下载页面][] 30 | 31 | [跳转到下载页面]: https://github.com/Mister-Kin/ToggleLanguage/releases/latest 32 | 33 | ## 使用方法 34 | [跳转到文档页面][] 35 | 36 | [离线文档 - PDF][] 37 | 38 | [跳转到文档页面]: https://mister-kin.github.io/works/software-works/toggle-language/ 39 | [离线文档 - PDF]: https://github.com/Mister-Kin/OpenDocs/releases/download/latex2pdf/toggle_language.pdf 40 | 41 | ## 作者 42 | **切换语言** © Mr. Kin,所有文件均采用 [GNU GPL v3.0][] 许可协议进行发布。 43 | 44 | 由 Mr. Kin 著作并维护。 45 | 46 | > [博客][] · [GitHub][] · [微博][] · [知乎][] · [AcFun][] · [哔哩哔哩][] · [优酷][] · [头条][] · [油管][] 47 | 48 | [GNU GPL v3.0]: ./LICENSE 49 | [博客]: https://mister-kin.github.io 50 | [GitHub]: https://github.com/mister-kin 51 | [微博]: https://weibo.com/6270111192 52 | [知乎]: https://www.zhihu.com/people/drwu-94 53 | [哔哩哔哩]: http://space.bilibili.com/17025250? 54 | [优酷]: http://i.youku.com/i/UNjA3MTk5Mjgw?spm=a2hzp.8253869.0.0 55 | [头条]: https://www.toutiao.com/c/user/835254071079053/#mid=1663279303982091 56 | [油管]: https://www.youtube.com/@Mister-Kin 57 | [AcFun]: https://www.acfun.cn/u/73269306 58 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) Mr. Kin - Toggle Language 2 | # License: http://www.gnu.org/licenses/gpl.html GPL version 3 or higher 3 | 4 | # ##### BEGIN GPL LICENSE BLOCK ##### 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, but 12 | # WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | # General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | bl_info = { 22 | "name": "Toggle Language", 23 | "description": "One click to toggle UI between two languages", 24 | "author": "Mr. Kin", 25 | "version": (1, 6, 3), 26 | "blender": (2, 83, 0), 27 | "location": "Topbar Menu", 28 | "category": "Interface", 29 | "doc_url": "https://mister-kin.github.io/works/software-works/toggle-language/", 30 | "tracker_url": "https://mister-kin.github.io/about/#联系方式", 31 | } 32 | 33 | _modules = [ 34 | "keymaps", 35 | "operators", 36 | "properties", 37 | "ui", 38 | "languages", 39 | ] 40 | 41 | # support reloading sub-modules (refer to scripts/startup/bl_ui/__init__.py) 42 | if "bpy" in locals(): 43 | from importlib import reload 44 | 45 | _modules_loaded[:] = [reload(val) for val in _modules_loaded] 46 | del reload 47 | 48 | __import__(name=__name__, fromlist=_modules) 49 | _namespace = locals() 50 | _modules_loaded = [_namespace[name] for name in _modules] 51 | del _namespace 52 | 53 | 54 | def register(): 55 | for mod in _modules_loaded: 56 | mod.register() 57 | 58 | 59 | def unregister(): 60 | for mod in _modules_loaded: 61 | mod.unregister() 62 | -------------------------------------------------------------------------------- /blender_manifest.toml: -------------------------------------------------------------------------------- 1 | schema_version = "1.0.0" 2 | id = "toggle_language" 3 | version = "1.6.3" 4 | name = "Toggle Language" 5 | tagline = "One click to toggle UI between two languages" 6 | maintainer = "Mr. Kin " 7 | type = "add-on" 8 | website = "https://mister-kin.github.io/works/software-works/toggle-language/" 9 | tags = ["User Interface"] 10 | blender_version_min = "4.2.0" 11 | license = ["SPDX:GPL-3.0-or-later"] 12 | -------------------------------------------------------------------------------- /keymaps.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | addon_keymaps = [] 4 | 5 | 6 | def register_keymaps(): 7 | wm = bpy.context.window_manager 8 | 9 | km = wm.keyconfigs.addon.keymaps.new(name="Window") 10 | kmi = km.keymap_items.new( 11 | idname="toggle_language.toggle_language", type="F5", value="PRESS" 12 | ) 13 | addon_keymaps.append((km, kmi)) 14 | 15 | km = wm.keyconfigs.addon.keymaps.new(name="Window") 16 | kmi = km.keymap_items.new( 17 | idname="screen.userpref_show", type="U", value="PRESS", ctrl=True, alt=True 18 | ) 19 | addon_keymaps.append((km, kmi)) 20 | 21 | 22 | def unregister_keymaps(): 23 | for km, kmi in addon_keymaps: 24 | km.keymap_items.remove(kmi) 25 | addon_keymaps.clear() 26 | 27 | 28 | def register(): 29 | register_keymaps() 30 | 31 | 32 | def unregister(): 33 | unregister_keymaps() 34 | -------------------------------------------------------------------------------- /languages/__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | langs_dict = {} 4 | 5 | from os import listdir, path 6 | from importlib import import_module 7 | 8 | files_list = listdir(path.dirname(__file__)) 9 | for f in files_list: 10 | if f.endswith(".py") and not f.startswith("__init__"): 11 | file_name = f.split(".") 12 | module_name = import_module("." + file_name[0], package=__name__) 13 | langs_dict.update(module_name.langs_dict) 14 | del files_list, file_name, module_name 15 | 16 | 17 | def register(): 18 | bpy.app.translations.register(__name__, langs_dict) 19 | 20 | 21 | def unregister(): 22 | bpy.app.translations.unregister(__name__) 23 | -------------------------------------------------------------------------------- /languages/simplified_chinese.py: -------------------------------------------------------------------------------- 1 | langs_dict = { 2 | "zh_HANS": { 3 | # __init.py 4 | ("*", "Toggle Language"): "切换语言", 5 | ( 6 | "*", 7 | "One click to toggle UI between two languages", 8 | ): "在两种语言中一键切换界面语言", 9 | ("*", "Topbar Menu"): "顶部菜单栏", 10 | # ui.py 11 | ("*", "Settings"): "设置", 12 | ("*", "Current Hint Scheme: Default"): "当前提示方案:默认", 13 | ("*", "Current Hint Scheme: Developer"): "当前提示方案:开发者", 14 | ("*", "Hint Scheme Menu"): "提示方案菜单", 15 | ("*", "Utilities"): "实用工具", 16 | # operators.py 17 | ("*", "Confirm"): "确认", 18 | ("*", "Message Box"): "消息框", 19 | ("Operator", "Toggle Language"): "切换语言", 20 | ("*", "Click button to toggle language"): "点击按钮以切换语言", 21 | ("*", "Switched to"): "已切换到", 22 | ("*", "interface!"): "界面!", 23 | ("*", "Fail to Toggle Language"): "切换语言失败", 24 | ( 25 | "*", 26 | "Two languages are same! Please select two different languages for addon.", 27 | ): "两种语言相同!请为插件选择两种不同的语言。", 28 | ("Operator", "Default Mode"): "默认模式", 29 | ("*", "Show no extra information"): "不显示多余的信息", 30 | ("*", "Switched to default mode!"): "已切换到默认模式!", 31 | ("Operator", "Developer Mode"): "开发者模式", 32 | ("*", "Show tooltips and options for developers"): "显示开发者的工具提示和选项", 33 | ("*", "Switched to developer mode!"): "已切换到开发者模式!", 34 | ("Operator", "Load My Blender Settings"): "加载我的Blender设置", 35 | ( 36 | "*", 37 | "Load my customized blender settings for startup file and preferences", 38 | ): "加载我的Blender启动文件和偏好设置的自定义设置", 39 | ( 40 | "*", 41 | "This will load my customized blender settings for startup file and preferences. It might change your current settings for startup file and preferences. Are you sure?", 42 | ): "这将会加载我的Blender启动文件和偏好设置的自定义设置。它可能会改变你当前的启动文件和偏好设置。你确定吗?", 43 | ("*", "Load My Blender Settings"): "加载我的Blender设置", 44 | ("Operator", "Load Blender Factory Settings"): "加载Blender初始设置", 45 | ( 46 | "*", 47 | "Load blender factory default startup file and preferences", 48 | ): "还原blender启动文件和默认偏好设置", 49 | ( 50 | "*", 51 | "This will load blender factory default startup file and preferences. It will completely restore every blender setting to default value, not just addon settings. Are you sure?", 52 | ): "这将会还原blender启动文件和默认偏好设置。它将完全恢复每个Blender设置项到默认值,不仅仅是插件设置。你确定吗?", 53 | ("*", "Load Factory Settings"): "加载初始设置", 54 | ( 55 | "Operator", 56 | "Delete All Collections and Objects in Current Scene", 57 | ): "删除当前场景中的所有集合和物体", 58 | ( 59 | "*", 60 | "Delete all collections and objects in current scene", 61 | ): "删除当前场景中的所有集合和物体", 62 | ( 63 | "*", 64 | "Delete all collections and objects in current scene successfully!", 65 | ): "成功删除当前场景中的所有集合和物体!", 66 | ( 67 | "*", 68 | "This will delete all collections and objects in current scene. Are you sure?", 69 | ): "这将删除当前场景中的所有集合和物体。你确定吗?", 70 | ("*", "Delete All"): "全部删除", 71 | ("Operator", "Add Video Progress Bar"): "添加视频进度条", 72 | ( 73 | "*", 74 | "Add video progress bar depend on current scene settings", 75 | ): "根据当前场景设置,添加视频进度条", 76 | ("*", "video progress bar bottom mask"): "视频进度条底遮罩", 77 | ("*", "video progress bar roll mask"): "视频进度条滚动遮罩", 78 | ("*", "video progress bar mask"): "视频进度条遮罩", 79 | ("*", "Add video progress bar successfully!"): "成功添加视频进度条!", 80 | ("Operator", "Import Blueprint (Reference Image)"): "导入蓝图(参考图)", 81 | ( 82 | "*", 83 | "Import blueprint (reference image) to current scene", 84 | ): "导入蓝图(参考图)到当前场景", 85 | ("*", "Fail to Import Blueprint (Reference Image)"): "无法导入蓝图(参考图)", 86 | ( 87 | "*", 88 | "Haven't selected any reference images! Please re-import and select some reference images.", 89 | ): "没有选择任何参考图像!请重新导入并选择一些参考图像。", 90 | ("*", "Blueprint"): "蓝图", 91 | ("*", "Blueprint Front"): "蓝图前视图", 92 | ("*", "Blueprint Rear"): "蓝图后视图", 93 | ("*", "Blueprint Right"): "蓝图右视图", 94 | ("*", "Blueprint Left"): "蓝图左视图", 95 | ("*", "Blueprint Top"): "蓝图俯视图", 96 | ("*", "Blueprint Bottom"): "蓝图仰视图", 97 | ( 98 | "*", 99 | "Import blueprint (reference image) successfully!", 100 | ): "成功导入蓝图(参考图)!", 101 | ("Operator", "Check Addon Update"): "检查插件更新", 102 | ("*", "Check for updates."): "检查插件更新情况", 103 | ("*", "not found"): "未找到", 104 | ( 105 | "*", 106 | "Current addon version can't be retrieved. Please check if", 107 | ): "无法检索当前插件的版本。请检查", 108 | ("*", "exists."): "是否存在。", 109 | ( 110 | "*", 111 | "Addon updated successfully. Please restart Blender to finish update.", 112 | ): "插件更新成功。请重新启动Blender以完成更新。", 113 | ("*", "Addon updated successfully"): "插件更新成功", 114 | ( 115 | "*", 116 | "Please restart Blender to finish update.", 117 | ): "请重新启动Blender以完成更新。", 118 | ( 119 | "*", 120 | "Failed to download latest version of the addon.", 121 | ): "无法下载最新版本的插件。", 122 | ("*", "current_version: "): "当前版本:", 123 | ("*", "latest_version: "): "最新版本:", 124 | ("*", "Your addon is out-of-date."): "你的插件需要更新。", 125 | ("*", "Your addon is already up-to-date."): "你的插件已经是最新发布版。", 126 | ("*", "Your addon is newer than latest."): "你的插件版本已超前于最新发布版。", 127 | ("*", "Failed to retrieve latest version."): "无法检索到最新版本。", 128 | # properties.py 129 | ("*", "Translate New Data-Block's Name"): "翻译新建数据块的名称", 130 | ( 131 | "*", 132 | "Enable or disable translation for new data-block's name", 133 | ): "启用或禁用新建数据块名称的翻译", 134 | ("*", "First Language"): "第一种语言", 135 | ("*", "First language for toggling"): "用于切换的第一种语言", 136 | ("*", "Second Language"): "第二种语言", 137 | ("*", "Second language for toggling"): "用于切换的第二种语言", 138 | ("*", "Disable Paths Setting"): "禁用路径设置", 139 | ( 140 | "*", 141 | "Disable paths setting for Load My Blender Settings feature", 142 | ): "禁用“加载我的Blender设置”功能的路径设置", 143 | ("*", "Disable Theme Setting"): "禁用主题设置", 144 | ( 145 | "*", 146 | "Disable theme setting for Load My Blender Settings feature", 147 | ): "禁用“加载我的Blender设置”功能的主题设置", 148 | ("*", "Disable Saving Startup File"): "禁止保存启动文件", 149 | ( 150 | "*", 151 | "Disable saving startup file when applying feature Load My Blender Settings", 152 | ): "应用“加载我的Blender设置”功能时,禁止保存启动文件", 153 | ( 154 | "*", 155 | "Please select two languages for addon to toggle UI language.", 156 | ): "请为插件选择两种语言以用于切换界面语言。", 157 | ( 158 | "*", 159 | "Addon's Keymaps", 160 | ): "插件的键位映射", 161 | ( 162 | "*", 163 | "Some settings for Load My Blender Settings feature.", 164 | ): "一些关于“加载我的Blender设置”功能的设置。", 165 | ( 166 | "*", 167 | "Please configure following settings before applying Load My Blender Settings feature.", 168 | ): "在应用“加载我的Blender设置”功能前,请配置以下设置。", 169 | ("*", "Use CPU in GPU Render Setting"): "在GPU渲染设置中使用CPU", 170 | ( 171 | "*", 172 | "Use CPU in GPU render setting for Load My Blender Settings feature", 173 | ): "在“加载我的Blender设置”功能的GPU渲染设置中使用CPU", 174 | ("*", "Enable Selection for Import Blueprint"): "启用“导入蓝图”的选择", 175 | ( 176 | "*", 177 | "Enable selection for Import Blueprint feature (Blueprint reference can't be selected after importing if not checked)", 178 | ): "启用“导入蓝图”功能的选择(若未勾选,则蓝图参考图在导入后无法被选中)", 179 | ("*", "Addon's Utility Settings"): "插件的实用工具设置", 180 | ("*", "Preset Theme"): "预设主题", 181 | ( 182 | "*", 183 | "Preset theme for Load My Blender Settings feature", 184 | ): "“加载我的Blender设置”功能的预设主题", 185 | ("*", "Blender Dark (Dark Theme)"): "Blender深(深色主题)", 186 | ("*", "Blender Light (Light Theme)"): "Blender浅(浅色主题)", 187 | ("*", "Deep Grey (Dark Theme)"): "深灰(深色主题)", 188 | ("*", "Maya (Dark Theme)"): "Maya(深色主题)", 189 | ("*", "Minimal Dark (Dark Theme)"): "小深(深色主题)", 190 | ("*", "Modo (Dark Theme)"): "Modo(深色主题)", 191 | ("*", "Print Friendly (Light Theme)"): "适合打印(浅色主题)", 192 | ("*", "White (Light Theme)"): "白色(浅色主题)", 193 | ("*", "XSI (Light Theme)"): "XSI(浅色主题)", 194 | } 195 | } 196 | 197 | langs_dict["zh_CN"] = langs_dict["zh_HANS"] 198 | -------------------------------------------------------------------------------- /languages/traditional_chinese.py: -------------------------------------------------------------------------------- 1 | langs_dict = { 2 | "zh_HANT": { 3 | # __init.py 4 | ("*", "Toggle Language"): "切換語言", 5 | ( 6 | "*", 7 | "One click to toggle UI between two languages", 8 | ): "在兩種語言中一鍵切換界面語言", 9 | ("*", "Topbar Menu"): "頂部菜單欄", 10 | # ui.py 11 | ("*", "Settings"): "設定", 12 | ("*", "Current Hint Scheme: Default"): "當前提示方案:默認", 13 | ("*", "Current Hint Scheme: Developer"): "當前提示方案:開發者", 14 | ("*", "Hint Scheme Menu"): "提示方案菜單", 15 | ("*", "Utilities"): "效用", 16 | # operators.py 17 | ("*", "Confirm"): "確認", 18 | ("*", "Message Box"): "消息框", 19 | ("Operator", "Toggle Language"): "切換語言", 20 | ("*", "Click button to toggle language"): "點擊按鈕以切換語言", 21 | ("*", "Switched to"): "已切換到", 22 | ("*", "interface!"): "界面!", 23 | ("*", "Fail to Toggle Language"): "切換語言失敗", 24 | ( 25 | "*", 26 | "Two languages are same! Please select two different languages for addon.", 27 | ): "兩種語言相同!請爲插件選擇兩種不同的語言。", 28 | ("Operator", "Default Mode"): "默認模式", 29 | ("*", "Show no extra information"): "不顯示多餘的信息", 30 | ("*", "Switched to default mode!"): "已切換到默認模式!", 31 | ("Operator", "Developer Mode"): "開發者模式", 32 | ("*", "Show tooltips and options for developers"): "顯示開發者的工具提示和選項", 33 | ("*", "Switched to developer mode!"): "已切換到開發者模式!", 34 | ("Operator", "Load My Blender Settings"): "載入我的Blender設定", 35 | ( 36 | "*", 37 | "Load my customized blender settings for startup file and preferences", 38 | ): "載入我的blender啓動文件和偏好設定的自定義設定", 39 | ( 40 | "*", 41 | "This will load my customized blender settings for startup file and preferences. It might change your current settings for startup file and preferences. Are you sure?", 42 | ): "這將會載入我的Blender啓動文件和偏好設定的自定義設定。它可能會改變你當前的啓動文件和偏好設定。你確定嗎?", 43 | ("*", "Load My Blender Settings"): "載入我的Blender設定", 44 | ("Operator", "Load Blender Factory Settings"): "載入Blender出廠設定", 45 | ( 46 | "*", 47 | "Load blender factory default startup file and preferences", 48 | ): "載入blender出廠預設的初始啓動檔案和偏好設定", 49 | ( 50 | "*", 51 | "This will load blender factory default startup file and preferences. It will completely restore every blender setting to default value, not just addon settings. Are you sure?", 52 | ): "這將會載入blender出廠預設的初始啓動檔案和偏好設定。它將完全恢復每個blender設定項到預設值,不僅僅是插件設定。你確定嗎?", 53 | ("*", "Load Factory Settings"): "載入出廠設定", 54 | ( 55 | "Operator", 56 | "Delete All Collections and Objects in Current Scene", 57 | ): "删除當前場景中的所有集合和物體", 58 | ( 59 | "*", 60 | "Delete all collections and objects in current scene", 61 | ): "删除當前場景中的所有集合和物體", 62 | ( 63 | "*", 64 | "Delete all collections and objects in current scene successfully!", 65 | ): "成功删除當前場景中的所有集合和物體!", 66 | ( 67 | "*", 68 | "This will delete all collections and objects in current scene. Are you sure?", 69 | ): "這將删除當前場景中的所有集合和物體。你確定嗎?", 70 | ("*", "Delete All"): "全部刪除", 71 | ("Operator", "Add Video Progress Bar"): "添加視頻進度條", 72 | ( 73 | "*", 74 | "Add video progress bar depend on current scene settings", 75 | ): "根據當前場景設定,添加視頻進度條", 76 | ("*", "video progress bar bottom mask"): "視頻進度條底遮罩", 77 | ("*", "video progress bar roll mask"): "視頻進度條滾動遮罩", 78 | ("*", "video progress bar mask"): "視頻進度條遮罩", 79 | ("*", "Add video progress bar successfully!"): "成功添加視頻進度條!", 80 | ("Operator", "Import Blueprint (Reference Image)"): "載入藍圖(參考圖)", 81 | ( 82 | "*", 83 | "Import blueprint (reference image) to current scene", 84 | ): "載入藍圖(參考圖)到當前場景", 85 | ("*", "Fail to Import Blueprint (Reference Image)"): "無法載入藍圖(參考圖)", 86 | ( 87 | "*", 88 | "Haven't selected any reference images! Please re-import and select some reference images.", 89 | ): "沒有選擇任何參考圖像!請重新載入並選擇一些參考圖像。", 90 | ("*", "Blueprint"): "藍圖", 91 | ("*", "Blueprint Front"): "藍圖前視圖", 92 | ("*", "Blueprint Rear"): "藍圖後視圖", 93 | ("*", "Blueprint Right"): "藍圖右視圖", 94 | ("*", "Blueprint Left"): "藍圖左視圖", 95 | ("*", "Blueprint Top"): "藍圖俯視圖", 96 | ("*", "Blueprint Bottom"): "藍圖仰視圖", 97 | ( 98 | "*", 99 | "Import blueprint (reference image) successfully!", 100 | ): "成功載入藍圖(參考圖)!", 101 | ("Operator", "Check Addon Update"): "檢查插件更新", 102 | ("*", "Check for updates."): "檢查插件更新情況", 103 | ("*", "not found"): "未找到", 104 | ( 105 | "*", 106 | "Current addon version can't be retrieved. Please check if", 107 | ): "無法檢索當前插件的版本。請檢查", 108 | ("*", "exists."): "是否存在。", 109 | ( 110 | "*", 111 | "Addon updated successfully. Please restart Blender to finish update.", 112 | ): "插件更新成功。請重新啓動Blender以完成更新。", 113 | ("*", "Addon updated successfully"): "插件更新成功", 114 | ( 115 | "*", 116 | "Please restart Blender to finish update.", 117 | ): "請重新啓動Blender以完成更新。", 118 | ( 119 | "*", 120 | "Failed to download latest version of the addon.", 121 | ): "無法下載最新版本的插件。", 122 | ("*", "current_version: "): "當前版本:", 123 | ("*", "latest_version: "): "最新版本:", 124 | ("*", "Your addon is out-of-date."): "你的插件需要更新。", 125 | ("*", "Your addon is already up-to-date."): "你的插件已經是最新發佈版。", 126 | ("*", "Your addon is newer than latest."): "你的插件版本已超前于最新發佈版。", 127 | ("*", "Failed to retrieve latest version."): "無法檢索到最新版本。", 128 | # properties.py 129 | ("*", "Translate New Data-Block's Name"): "翻譯新建數據塊的名稱", 130 | ( 131 | "*", 132 | "Enable or disable translation for new data-block's name", 133 | ): "啓用或禁用新建數據塊名稱的翻譯", 134 | ("*", "First Language"): "第一種語言", 135 | ("*", "First language for toggling"): "用於切換的第一種語言", 136 | ("*", "Second Language"): "第二種語言", 137 | ("*", "Second language for toggling"): "用於切換的第二種語言", 138 | ("*", "Disable Paths Setting"): "禁用路徑設定", 139 | ( 140 | "*", 141 | "Disable paths setting for Load My Blender Settings feature", 142 | ): "禁用“載入我的Blender設定”功能的路徑設定", 143 | ("*", "Disable Theme Setting"): "禁用主題設定", 144 | ( 145 | "*", 146 | "Disable theme setting for Load My Blender Settings feature", 147 | ): "禁用“載入我的Blender設定”功能的主題設定", 148 | ("*", "Disable Saving Startup File"): "禁止儲存初始啓動檔案", 149 | ( 150 | "*", 151 | "Disable saving startup file when applying feature Load My Blender Settings", 152 | ): "應用“載入我的Blender設定”功能時,禁止儲存初始啓動檔案", 153 | ( 154 | "*", 155 | "Please select two languages for addon to toggle UI language.", 156 | ): "請爲插件選擇兩種語言以用於切換界面語言。", 157 | ( 158 | "*", 159 | "Addon's Keymaps", 160 | ): "插件的鍵位映射", 161 | ( 162 | "*", 163 | "Some settings for Load My Blender Settings feature.", 164 | ): "一些關於“載入我的Blender設定”功能的設定。", 165 | ( 166 | "*", 167 | "Please configure following settings before applying Load My Blender Settings feature.", 168 | ): "在應用“載入我的Blender設定”功能前,請配置以下設定。", 169 | ("*", "Use CPU in GPU Render Setting"): "在GPU渲染設定中使用CPU", 170 | ( 171 | "*", 172 | "Use CPU in GPU render setting for Load My Blender Settings feature", 173 | ): "在“載入我的Blender設定”功能的GPU渲染設定中使用CPU", 174 | ("*", "Enable Selection for Import Blueprint"): "啓用“載入藍圖”的選擇", 175 | ( 176 | "*", 177 | "Enable selection for Import Blueprint feature (Blueprint reference can't be selected after importing if not checked)", 178 | ): "啓用“載入藍圖”功能的選擇(若未勾選,則藍圖參考圖在載入後無法被選中)", 179 | ("*", "Addon's Utility Settings"): "插件的效用設定", 180 | ("*", "Preset Theme"): "預設主題", 181 | ( 182 | "*", 183 | "Preset theme for Load My Blender Settings feature", 184 | ): "“載入我的Blender設定”功能的預設主題", 185 | ("*", "Blender Dark (Dark Theme)"): "Blender深(深色主題)", 186 | ("*", "Blender Light (Light Theme)"): "Blender浅(淺色主題)", 187 | ("*", "Deep Grey (Dark Theme)"): "深灰(深色主題)", 188 | ("*", "Maya (Dark Theme)"): "Maya(深色主題)", 189 | ("*", "Minimal Dark (Dark Theme)"): "小深(深色主題)", 190 | ("*", "Modo (Dark Theme)"): "Modo(深色主題)", 191 | ("*", "Print Friendly (Light Theme)"): "適合打印(淺色主題)", 192 | ("*", "White (Light Theme)"): "白色(淺色主題)", 193 | ("*", "XSI (Light Theme)"): "XSI(淺色主題)", 194 | } 195 | } 196 | 197 | langs_dict["zh_TW"] = langs_dict["zh_HANT"] 198 | -------------------------------------------------------------------------------- /operators.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Operator, OperatorFileListElement 3 | from bpy.app import translations 4 | from multiprocessing import cpu_count 5 | from . import properties 6 | from bpy_extras.io_utils import ImportHelper 7 | from bpy.props import StringProperty, CollectionProperty 8 | from math import radians 9 | import requests, os, io, json, shutil, zipfile, tempfile 10 | 11 | 12 | class TOGGLE_LANGUAGE_OT_message_box_with_confirm(bpy.types.Operator): 13 | bl_idname = "wm.message_box_with_confirm" 14 | bl_label = "Message Box" 15 | 16 | title: bpy.props.StringProperty(default="Message Box") 17 | message: bpy.props.StringProperty(default="") 18 | icon: bpy.props.StringProperty(default="INFO") 19 | 20 | def invoke(self, context, event): 21 | return context.window_manager.invoke_confirm( 22 | self, 23 | event, 24 | title=self.title, 25 | message=self.message, 26 | confirm_text="Confirm", 27 | icon=self.icon, 28 | ) 29 | 30 | 31 | def message_box_with_confirm(title="Message Box", message="", icon="INFO"): 32 | bpy.ops.wm.message_box_with_confirm( 33 | "INVOKE_DEFAULT", title=title, message=message, icon=icon 34 | ) 35 | 36 | 37 | def message_box(title="Message Box", message="", icon="INFO"): 38 | def draw(self, context): 39 | self.layout.label(text=message) 40 | 41 | bpy.context.window_manager.popup_menu( 42 | draw, title=translations.pgettext(title), icon=icon 43 | ) 44 | 45 | 46 | class TOGGLE_LANGUAGE_OT_toggle_language(Operator): 47 | bl_idname = "toggle_language.toggle_language" 48 | bl_label = "Toggle Language" 49 | bl_description = "Click button to toggle language" 50 | 51 | def execute(self, context): 52 | userpref = context.preferences 53 | addonpref = userpref.addons[__package__].preferences 54 | lang = translations.locale 55 | 56 | if userpref.version[0] >= 4: 57 | if addonpref.first_lang != addonpref.second_lang: 58 | if lang == addonpref.first_lang: 59 | userpref.view.language = addonpref.second_lang 60 | else: 61 | userpref.view.language = addonpref.first_lang 62 | enum_languages_dict = { 63 | lang[0]: lang[1] for lang in properties.enum_languages 64 | } 65 | self.report( 66 | {"INFO"}, 67 | "{} {} {}".format( 68 | translations.pgettext("Switched to"), 69 | enum_languages_dict.get(userpref.view.language), 70 | translations.pgettext("interface!"), 71 | ), 72 | ) 73 | else: 74 | userpref = bpy.context.preferences 75 | if ( 76 | userpref.version[0] == 4 and userpref.version[1] >= 1 77 | ) or userpref.version[0] >= 5: 78 | message_box_with_confirm( 79 | title="Fail to Toggle Language", 80 | message="Two languages are same! Please select two different languages for addon.", 81 | icon="ERROR", 82 | ) 83 | else: 84 | message_box_with_confirm( 85 | title="Fail to Toggle Language", 86 | message="Two languages are same! Please select two different languages for addon.", 87 | icon="ERROR", 88 | ) 89 | else: 90 | if addonpref.first_lang_before_v4 != addonpref.second_lang_before_v4: 91 | if lang == addonpref.first_lang_before_v4: 92 | userpref.view.language = addonpref.second_lang_before_v4 93 | else: 94 | userpref.view.language = addonpref.first_lang_before_v4 95 | enum_languages_before_v4_dict = { 96 | lang[0]: lang[1] for lang in properties.enum_languages_before_v4 97 | } 98 | self.report( 99 | {"INFO"}, 100 | "{} {} {}".format( 101 | translations.pgettext("Switched to"), 102 | enum_languages_before_v4_dict.get(userpref.view.language), 103 | translations.pgettext("interface!"), 104 | ), 105 | ) 106 | else: 107 | userpref = bpy.context.preferences 108 | if ( 109 | userpref.version[0] == 4 and userpref.version[1] >= 1 110 | ) or userpref.version[0] >= 5: 111 | message_box_with_confirm( 112 | title="Fail to Toggle Language", 113 | message="Two languages are same! Please select two different languages for addon.", 114 | icon="ERROR", 115 | ) 116 | else: 117 | message_box_with_confirm( 118 | title="Fail to Toggle Language", 119 | message="Two languages are same! Please select two different languages for addon.", 120 | icon="ERROR", 121 | ) 122 | 123 | # 检测并修正 use_translate_new_dataname 选项值。 124 | scene = context.scene 125 | lang = translations.locale 126 | if lang != "en_US": 127 | if ( 128 | userpref.view.use_translate_new_dataname 129 | != scene.toggle_language_settings.translate_new_dataname 130 | ): 131 | userpref.view.use_translate_new_dataname = ( 132 | scene.toggle_language_settings.translate_new_dataname 133 | ) 134 | 135 | return {"FINISHED"} 136 | 137 | 138 | class TOGGLE_LANGUAGE_OT_use_default_hint_scheme(Operator): 139 | bl_idname = "toggle_language.use_default_hint_scheme" 140 | bl_label = "Default Mode" 141 | bl_description = "Show no extra information" 142 | 143 | def execute(self, context): 144 | userpref = context.preferences 145 | 146 | userpref.view.show_developer_ui = False 147 | userpref.view.show_tooltips_python = False 148 | self.report({"INFO"}, "Switched to default mode!") 149 | return {"FINISHED"} 150 | 151 | 152 | class TOGGLE_LANGUAGE_OT_use_developer_hint_scheme(Operator): 153 | bl_idname = "toggle_language.use_developer_hint_scheme" 154 | bl_label = "Developer Mode" 155 | bl_description = "Show tooltips and options for developers" 156 | 157 | def execute(self, context): 158 | userpref = context.preferences 159 | 160 | userpref.view.show_developer_ui = True 161 | userpref.view.show_tooltips_python = True 162 | self.report({"INFO"}, "Switched to developer mode!") 163 | return {"FINISHED"} 164 | 165 | 166 | class TOGGLE_LANGUAGE_OT_load_my_blender_settings(Operator): 167 | bl_idname = "toggle_language.load_my_blender_settings" 168 | bl_label = "Load My Blender Settings" 169 | bl_description = ( 170 | "Load my customized blender settings for startup file and preferences" 171 | ) 172 | 173 | def execute(self, context): 174 | scene = context.scene 175 | userpref = context.preferences 176 | addonpref = userpref.addons[__package__].preferences 177 | 178 | if userpref.version[1] >= 90: 179 | blender_v290 = True 180 | else: 181 | blender_v290 = False 182 | if userpref.version[0] >= 3: 183 | blender_v3_plus = True 184 | if ( 185 | userpref.version[0] == 3 and userpref.version[1] >= 4 186 | ) or userpref.version[0] >= 4: 187 | blender_v34_plus = True 188 | if ( 189 | userpref.version[0] == 4 and userpref.version[1] >= 1 190 | ) or userpref.version[0] >= 5: 191 | blender_v41_plus = True 192 | if ( 193 | userpref.version[0] == 4 194 | and userpref.version[1] >= 2 195 | or userpref.version[0] >= 5 196 | ): 197 | blender_v42_plus = True 198 | else: 199 | blender_v42_plus = False 200 | else: 201 | blender_v41_plus = False 202 | else: 203 | blender_v34_plus = False 204 | else: 205 | blender_v3_plus = False 206 | 207 | # v2.93 及之后版本的文件命名有所变化。 208 | if blender_v42_plus: 209 | dict_blender_theme_name = { 210 | "blender_dark": "Blender_Dark.xml", 211 | "blender_light": "Blender_Light.xml", 212 | "deep_grey": "theme_deep_grey", 213 | "maya": "theme_maya", 214 | "minimal_dark": "theme_minimal_dark", 215 | "modo": "theme_modo", 216 | "print_friendly": "theme_print_friendly", 217 | "white": "theme_white", 218 | "xsi": "theme_xsi", 219 | } 220 | blender_keyconfig_name = "Blender" 221 | elif blender_v3_plus or userpref.version[1] == 93: 222 | dict_blender_theme_name = { 223 | "blender_dark": "Blender_Dark.xml", 224 | "blender_light": "Blender_Light.xml", 225 | "deep_grey": "Deep_Grey.xml", 226 | "maya": "Maya.xml", 227 | "minimal_dark": "Minimal_Dark.xml", 228 | "modo": "Modo.xml", 229 | "print_friendly": "Print_Friendly.xml", 230 | "white": "White.xml", 231 | "xsi": "XSI.xml", 232 | } 233 | blender_keyconfig_name = "Blender" 234 | else: 235 | dict_blender_theme_name = { 236 | "blender_dark": "blender_dark.xml", 237 | "blender_light": "blender_light.xml", 238 | "deep_grey": "deep_grey.xml", 239 | "maya": "maya.xml", 240 | "minimal_dark": "minimal_dark.xml", 241 | "modo": "modo.xml", 242 | "print_friendly": "print_friendly.xml", 243 | "white": "white.xml", 244 | "xsi": "xsi.xml", 245 | } 246 | blender_keyconfig_name = "blender" 247 | 248 | userpref.view.ui_scale = 1.3 249 | if blender_v290 or blender_v3_plus: 250 | userpref.view.show_statusbar_stats = True 251 | userpref.view.show_statusbar_memory = True 252 | if userpref.view.is_property_readonly("show_statusbar_vram"): 253 | pass # 无显卡时,该属性为只读,无法对其进行写操作。 254 | else: 255 | userpref.view.show_statusbar_vram = True 256 | else: 257 | # v2.83 及之前版本未引入系统原生的 API,用 SDL 替代 258 | userpref.system.audio_device = "SDL" 259 | 260 | blender_theme_path_variable = { 261 | "blender_dark": "", 262 | "blender_light": "", 263 | "deep_grey": "addons/", 264 | "maya": "addons/", 265 | "minimal_dark": "addons/", 266 | "modo": "addons/", 267 | "print_friendly": "addons/", 268 | "white": "addons/", 269 | "xsi": "addons/", 270 | } 271 | 272 | user_platform = bpy.app.build_platform 273 | execution_path = bpy.app.binary_path 274 | if addonpref.disable_theme_setting == False: 275 | if ( 276 | blender_v42_plus == True 277 | and addonpref.preset_theme != "blender_dark" 278 | and addonpref.preset_theme != "blender_light" 279 | ): 280 | bpy.ops.extensions.userpref_allow_online() 281 | bpy.ops.extensions.repo_sync_all() 282 | bpy.ops.extensions.package_install( 283 | repo_index=0, pkg_id=dict_blender_theme_name[addonpref.preset_theme] 284 | ) 285 | else: 286 | theme_path = ( 287 | "{First_Main_Number}.{Second_Main_Number}/scripts/" 288 | + blender_theme_path_variable[addonpref.preset_theme] 289 | + "presets/interface_theme/" 290 | + dict_blender_theme_name[addonpref.preset_theme] 291 | ) 292 | theme_path = theme_path.format( 293 | First_Main_Number=userpref.version[0], 294 | Second_Main_Number=userpref.version[1], 295 | ) 296 | # b"Windows" b"Darwin" b"Linux" 297 | if user_platform == b"Windows": 298 | theme_path = execution_path.replace("blender.exe", theme_path) 299 | elif user_platform == b"Darwin": 300 | theme_path = execution_path.replace( 301 | "MacOS/Blender", "Resources/" + theme_path 302 | ) 303 | else: 304 | theme_path = execution_path[:-7] + theme_path 305 | bpy.ops.script.execute_preset( 306 | filepath=theme_path, 307 | menu_idname="USERPREF_MT_interface_theme_presets", 308 | ) 309 | else: 310 | pass 311 | 312 | # 添加「视频剪辑」工作区 313 | video_editing_workspace_template_path = "{First_Main_Number}.{Second_Main_Number}/scripts/startup/bl_app_templates_system/Video_Editing/startup.blend" 314 | video_editing_workspace_template_path = ( 315 | video_editing_workspace_template_path.format( 316 | First_Main_Number=userpref.version[0], 317 | Second_Main_Number=userpref.version[1], 318 | ) 319 | ) 320 | # b"Windows" b"Darwin" b"Linux" 321 | if user_platform == b"Windows": 322 | video_editing_workspace_template_path = execution_path.replace( 323 | "blender.exe", video_editing_workspace_template_path 324 | ) 325 | elif user_platform == b"Darwin": 326 | video_editing_workspace_template_path = execution_path.replace( 327 | "MacOS/Blender", "Resources/" + video_editing_workspace_template_path 328 | ) 329 | else: 330 | video_editing_workspace_template_path = ( 331 | execution_path[:-7] + video_editing_workspace_template_path 332 | ) 333 | # 追加并激活工作区为 Video Editing 334 | bpy.ops.workspace.append_activate( 335 | idname="Video Editing", filepath=video_editing_workspace_template_path 336 | ) 337 | # 重新激活工作区为 Layout 338 | bpy.context.window.workspace = bpy.data.workspaces["Layout"] 339 | 340 | bpy.ops.preferences.addon_enable(module="node_wrangler") 341 | if blender_v42_plus: 342 | bpy.ops.extensions.package_install(repo_index=0, pkg_id="cell_fracture") 343 | bpy.ops.extensions.package_install(repo_index=0, pkg_id="icon_viewer") 344 | else: 345 | bpy.ops.preferences.addon_enable(module="object_fracture_cell") 346 | bpy.ops.preferences.addon_enable(module="development_icon_get") 347 | # v3.0 及之后版本用的是 cyclesX,其中的 Auto Tiles 功能是为了减少内存占用, 348 | # 即渲染 tile 大小的图像数据并缓存进硬盘,渲染结束时再合并在一起。 349 | # 与老版的 cycles 渲染调度逻辑不一样,因此 auto_tile_size 插件在 v3.0 中被移除。 350 | if blender_v3_plus: 351 | # TODO:根据当前可用内存自动设置合适大小(仍需查询资料或者查看源码确认显存是否会受影响) 352 | # 设置平铺大小为4096px,避免渲染4k图像时导致分割 353 | scene.cycles.tile_size = 4096 354 | else: 355 | bpy.ops.preferences.addon_enable(module="render_auto_tile_size") 356 | 357 | userpref.inputs.use_rotate_around_active = True 358 | userpref.inputs.use_zoom_to_mouse = True 359 | 360 | kcpref = context.window_manager.keyconfigs[blender_keyconfig_name].preferences 361 | kcpref.use_pie_click_drag = True 362 | kcpref.use_v3d_shade_ex_pie = True 363 | 364 | userpref.filepaths.use_file_compression = True 365 | if addonpref.disable_paths_setting == False: 366 | # userpref.filepaths.use_auto_save_temporary_files = False 367 | userpref.filepaths.texture_directory = "H:/texture/" 368 | # userpref.filepaths.temporary_directory = "E:/temp/" 369 | # userpref.filepaths.render_cache_directory = "E:/temp/" 370 | userpref.filepaths.render_output_directory = "D:/process/" 371 | scene.render.filepath = "D:/process/" 372 | else: 373 | pass 374 | 375 | userpref.edit.undo_steps = 256 376 | 377 | # cycles渲染引擎设置 378 | scene.render.engine = "CYCLES" 379 | cpref = userpref.addons["cycles"].preferences 380 | if blender_v3_plus: 381 | cpref.refresh_devices() # 刷新设备。 382 | else: 383 | cpref.get_devices() 384 | # 获取当前版本支持的设备类型,逐一设置以检测是否存在显卡。 385 | for device_type in cpref.get_device_types(bpy.context): 386 | try: 387 | cpref.compute_device_type = device_type[0] 388 | if cpref.has_active_device(): 389 | gpu_exist = True 390 | # 当optix可用时优先选择。 391 | if cpref.compute_device_type == "CUDA": 392 | try: 393 | cpref.compute_device_type = "OPTIX" 394 | if cpref.has_active_device(): 395 | optix_exist = True 396 | break 397 | else: 398 | optix_exist = False 399 | cpref.compute_device_type = "CUDA" 400 | except TypeError: 401 | pass 402 | break 403 | else: 404 | gpu_exist = False 405 | cpref.compute_device_type = "NONE" 406 | except TypeError: 407 | pass 408 | if gpu_exist: 409 | if blender_v3_plus: 410 | cpref.refresh_devices() # 刷新设备。 411 | else: 412 | cpref.get_devices() 413 | for device in cpref.devices: 414 | if device.type == "CPU": 415 | device.use = addonpref.use_cpu_in_gpu_render_setting 416 | else: 417 | device.use = True 418 | scene.cycles.device = "GPU" 419 | else: 420 | pass 421 | if blender_v3_plus: 422 | if not gpu_exist and blender_v34_plus: 423 | scene.cycles.use_guiding = True 424 | else: 425 | scene.cycles.use_adaptive_sampling = True 426 | scene.cycles.adaptive_threshold = 0.1 427 | # render_auto_tile_size插件的tiles大小设置 428 | scene.ats_settings.gpu_choice = "128" 429 | 430 | # 渲染降噪设置 431 | if blender_v290 or blender_v3_plus: 432 | scene.cycles.use_denoising = True 433 | scene.cycles.use_preview_denoising = True 434 | scene.cycles.preview_denoising_input_passes = "RGB_ALBEDO_NORMAL" 435 | # gpu_exist为false时,根据短路求值原理,不会执行后面的语句。因此即使optix_exist未赋值时直接引用,也不会报错“UnboundLocalError: local variable 'optix_exist' referenced before assignment” 436 | if gpu_exist and optix_exist: 437 | if blender_v41_plus: 438 | scene.cycles.denoiser = "OPENIMAGEDENOISE" 439 | scene.cycles.denoising_use_gpu = True 440 | scene.cycles.preview_denoiser = "OPENIMAGEDENOISE" 441 | scene.cycles.preview_denoising_use_gpu = True 442 | scene.cycles.preview_denoising_prefilter = "ACCURATE" 443 | else: 444 | scene.cycles.denoiser = "OPTIX" 445 | scene.cycles.preview_denoiser = "OPTIX" 446 | elif gpu_exist and not optix_exist: 447 | scene.cycles.denoiser = "OPENIMAGEDENOISE" 448 | scene.cycles.preview_denoiser = "OPENIMAGEDENOISE" 449 | scene.cycles.preview_denoising_prefilter = "ACCURATE" 450 | if blender_v41_plus: 451 | scene.cycles.denoising_use_gpu = True 452 | scene.cycles.preview_denoising_use_gpu = True 453 | elif blender_v290: 454 | scene.cycles.denoiser = "NLM" 455 | scene.cycles.preview_denoiser = "OPENIMAGEDENOISE" 456 | else: 457 | scene.cycles.denoiser = "OPENIMAGEDENOISE" 458 | scene.cycles.preview_denoiser = "OPENIMAGEDENOISE" 459 | scene.cycles.preview_denoising_prefilter = "ACCURATE" 460 | else: 461 | context.view_layer.cycles.use_denoising = True 462 | if gpu_exist and optix_exist: 463 | context.view_layer.cycles.use_optix_denoising = True 464 | context.view_layer.cycles.denoising_optix_input_passes = ( 465 | "RGB_ALBEDO_NORMAL" 466 | ) 467 | scene.cycles.preview_denoising = "OPTIX" 468 | 469 | # cycles引擎采样设置 470 | scene.cycles.samples = 250 471 | scene.cycles.preview_samples = 1 472 | 473 | # 其他优化渲染速度的设置 474 | scene.render.use_persistent_data = True 475 | 476 | scene.render.threads_mode = "FIXED" 477 | scene.render.threads = max(1, cpu_count() - 2) 478 | bpy.ops.file.autopack_toggle() # 自动打包资源,例如加载的外部纹理图片,避免路径改变后导致文件未找到 479 | bpy.ops.wm.save_userpref() 480 | if addonpref.disable_saving_startup_file == False: 481 | bpy.ops.wm.save_homefile() 482 | else: 483 | pass 484 | return {"FINISHED"} 485 | 486 | def invoke(self, context, event): 487 | userpref = bpy.context.preferences 488 | if (userpref.version[0] == 4 and userpref.version[1] >= 1) or userpref.version[ 489 | 0 490 | ] >= 5: 491 | return context.window_manager.invoke_confirm( 492 | self, 493 | event, 494 | message="This will load my customized blender settings for startup file and preferences. It might change your current settings for startup file and preferences. Are you sure?", 495 | confirm_text="Load My Blender Settings", 496 | icon="WARNING", 497 | ) 498 | else: 499 | return context.window_manager.invoke_confirm(self, event) 500 | 501 | 502 | class TOGGLE_LANGUAGE_OT_load_blender_factory_settings(Operator): 503 | bl_idname = "toggle_language.load_blender_factory_settings" 504 | bl_label = "Load Blender Factory Settings" 505 | bl_description = "Load blender factory default startup file and preferences" 506 | 507 | def execute(self, context): 508 | bpy.ops.wm.read_factory_settings() 509 | bpy.ops.wm.save_userpref() 510 | bpy.ops.wm.save_homefile() 511 | return {"FINISHED"} 512 | 513 | def invoke(self, context, event): 514 | userpref = bpy.context.preferences 515 | if (userpref.version[0] == 4 and userpref.version[1] >= 1) or userpref.version[ 516 | 0 517 | ] >= 5: 518 | return context.window_manager.invoke_confirm( 519 | self, 520 | event, 521 | message="This will load blender factory default startup file and preferences. It will completely restore every blender setting to default value, not just addon settings. Are you sure?", 522 | confirm_text="Load Factory Settings", 523 | icon="WARNING", 524 | ) 525 | else: 526 | return context.window_manager.invoke_confirm(self, event) 527 | 528 | 529 | class TOGGLE_LANGUAGE_OT_delete_all_collections_and_objects(Operator): 530 | bl_idname = "toggle_language.delete_all_collections_and_objects" 531 | bl_label = "Delete All Collections and Objects in Current Scene" 532 | bl_description = "Delete all collections and objects in current scene" 533 | 534 | def execute(self, context): 535 | scene = context.scene 536 | 537 | # 删除 Scene Collection 子项的物体 538 | for obj in scene.collection.objects: 539 | # if obj.users != 0可以检测data数据引用次数,不为0就删除,避免出现删除错误:which still has 1 users (including 0 'extra' shallow users),实际测试无效 540 | bpy.data.objects.remove(obj) 541 | 542 | # 删除 Scene Collection 子项的集合,该操作也可直接删除子项集合中的物体 543 | for collection in scene.collection.children: 544 | bpy.data.collections.remove(collection) 545 | 546 | # remove会一同删除项目本身及其子项,但object的data还存储在blend文件中,可通过垃圾回收orphans_purge清除 547 | bpy.ops.outliner.orphans_purge(do_recursive=True) 548 | 549 | self.report( 550 | {"INFO"}, 551 | "Delete all collections and objects in current scene successfully!", 552 | ) 553 | return {"FINISHED"} 554 | 555 | def invoke(self, context, event): 556 | userpref = bpy.context.preferences 557 | if (userpref.version[0] == 4 and userpref.version[1] >= 1) or userpref.version[ 558 | 0 559 | ] >= 5: 560 | return context.window_manager.invoke_confirm( 561 | self, 562 | event, 563 | message="This will delete all collections and objects in current scene. Are you sure?", 564 | confirm_text="Delete All", 565 | icon="WARNING", 566 | ) 567 | else: 568 | return context.window_manager.invoke_confirm(self, event) 569 | 570 | 571 | # TODO:可调参数,弹出窗口,待完善开发 572 | # TODO:首次完成添加视频进度条后,弹出可调参数窗口 573 | # TODO:旁边按钮添加一个调出控制窗口的按钮 574 | # class TOGGLE_LANGUAGE_OT_adjust_video_progress_bar(Operator): 575 | # bl_idname = "toggle_language.add_video_progress_bar" 576 | # bl_label = "" 577 | 578 | # def execute(self, context): 579 | 580 | # return {"FINISHED"} 581 | 582 | 583 | class TOGGLE_LANGUAGE_OT_add_video_progress_bar(Operator): 584 | bl_idname = "toggle_language.add_video_progress_bar" 585 | bl_label = "Add Video Progress Bar" 586 | bl_description = "Add video progress bar depend on current scene settings" 587 | 588 | def execute(self, context): 589 | scene = context.scene 590 | sequence_editor = scene.sequence_editor 591 | sequences = sequence_editor.sequences 592 | 593 | # 如果当前正在展开编辑meta片段就退出编辑,合闭meta片段元素,避免影响后续的选择创建meta片段 594 | # meta_stack[-1]倒数第一个元素就是当前展开编辑的meta片段 595 | if len(sequence_editor.meta_stack) > 0: 596 | bpy.ops.sequencer.meta_toggle() 597 | 598 | # 获取vse中没有片段最顶部的频道数字 599 | top_empty_channel_number = 0 600 | strips = sequence_editor.sequences_all 601 | for strip in strips: 602 | # 剔除meta子片段 603 | if strip.parent_meta() == None: 604 | if strip.channel > top_empty_channel_number: 605 | top_empty_channel_number = strip.channel 606 | 607 | # 获取当前场景的设置 608 | start_frame = scene.frame_start 609 | end_frame = scene.frame_end 610 | 611 | if scene.toggle_language_settings.translate_new_dataname == False: 612 | name_text_bottom = "video progress bar bottom mask" 613 | name_text_roll = "video progress bar roll mask" 614 | name_text_meta = "video progress bar mask" 615 | else: 616 | name_text_bottom = translations.pgettext("video progress bar bottom mask") 617 | name_text_roll = translations.pgettext("video progress bar roll mask") 618 | name_text_meta = translations.pgettext("video progress bar mask") 619 | bottom_effect = sequences.new_effect( 620 | name=name_text_bottom, 621 | type="COLOR", 622 | channel=top_empty_channel_number + 1, 623 | frame_start=start_frame, 624 | frame_end=end_frame, 625 | ) 626 | roll_effect = sequences.new_effect( 627 | name=name_text_roll, 628 | type="COLOR", 629 | channel=top_empty_channel_number + 2, 630 | frame_start=start_frame, 631 | frame_end=end_frame, 632 | ) 633 | 634 | # 设置子进度条颜色 635 | bottom_effect.color = (0.45, 0.45, 0.45) 636 | roll_effect.color = (0.255, 0.255, 0.255) 637 | 638 | # 设置滚动遮罩层的关键帧动画 639 | date_path = "offset_x" 640 | roll_effect.transform.keyframe_insert(date_path, frame=start_frame) 641 | roll_effect.transform.offset_x = scene.render.resolution_x 642 | roll_effect.transform.keyframe_insert(date_path, frame=end_frame) 643 | 644 | bpy.ops.sequencer.select_all(action="DESELECT") 645 | effect_list = [bottom_effect, roll_effect] 646 | for effect in effect_list: 647 | effect.select = True 648 | bpy.ops.sequencer.meta_make() 649 | active_strip = sequence_editor.active_strip 650 | active_strip.name = name_text_meta 651 | active_strip.channel = top_empty_channel_number + 1 652 | # 裁切meta片段和设置透明度 653 | crop_value = scene.render.resolution_y - 44 654 | blend_alpha_value = 0.9 655 | active_strip.crop.min_y = crop_value 656 | active_strip.blend_alpha = blend_alpha_value 657 | active_strip.select = False 658 | 659 | # 强制刷新vse 660 | bpy.ops.sequencer.refresh_all() 661 | 662 | self.report({"INFO"}, "Add video progress bar successfully!") 663 | return {"FINISHED"} 664 | 665 | 666 | class TOGGLE_LANGUAGE_OT_import_blueprint(Operator, ImportHelper): 667 | bl_idname = "toggle_language.import_blueprint" 668 | bl_label = "Import Blueprint (Reference Image)" 669 | bl_description = "Import blueprint (reference image) to current scene" 670 | 671 | filter_glob: StringProperty( 672 | # below line can support all image formats supported by blender 673 | # default="*" + ";*".join(bpy.path.extensions_image), 674 | default="*.png;*.jpg;*.jpeg;*.jp2;*.bmp;*.webp", 675 | options={"HIDDEN"}, 676 | maxlen=255, 677 | ) 678 | 679 | files: CollectionProperty( 680 | name="File Path", 681 | type=OperatorFileListElement, 682 | ) 683 | 684 | directory: StringProperty( 685 | subtype="DIR_PATH", 686 | ) 687 | 688 | def execute(self, context): 689 | files = self.files 690 | directory = self.directory 691 | if files[0].name == "": 692 | userpref = bpy.context.preferences 693 | if ( 694 | userpref.version[0] == 4 and userpref.version[1] >= 1 695 | ) or userpref.version[0] >= 5: 696 | message_box_with_confirm( 697 | title="Fail to Import Blueprint (Reference Image)", 698 | message="Haven't selected any reference images! Please re-import and select some reference images.", 699 | icon="ERROR", 700 | ) 701 | else: 702 | message_box( 703 | title="Fail to Import Blueprint (Reference Image)", 704 | message="Haven't selected any reference images! Please re-import and select some reference images.", 705 | icon="ERROR", 706 | ) 707 | else: 708 | blueprint_path_front = blueprint_path_right = blueprint_path_top = ( 709 | blueprint_path_rear 710 | ) = blueprint_path_left = blueprint_path_bottom = "" 711 | 712 | connector_list = ("-", "_", " ", ".") 713 | suffix_list_front = ( 714 | "front", 715 | "frontview", 716 | "front-view", 717 | "front_view", 718 | "前", 719 | "前视", 720 | "前视图", 721 | ) 722 | suffix_list_right = ( 723 | "right", 724 | "rightview", 725 | "right-view", 726 | "right_view", 727 | "右", 728 | "右视", 729 | "右视图", 730 | ) 731 | suffix_list_top = ( 732 | "top", 733 | "topview", 734 | "top-view", 735 | "top_view", 736 | "俯", 737 | "俯视", 738 | "俯视图", 739 | "顶", 740 | "顶视", 741 | "顶视图", 742 | ) 743 | suffix_list_rear = ( 744 | "rear", 745 | "rearview", 746 | "rear-view", 747 | "rear_view", 748 | "后", 749 | "后视", 750 | "后视图", 751 | ) 752 | suffix_list_left = ( 753 | "left", 754 | "leftview", 755 | "left-view", 756 | "left_view", 757 | "左", 758 | "左视", 759 | "左视图", 760 | ) 761 | suffix_list_bottom = ( 762 | "bottom", 763 | "bottomview", 764 | "bottom-view", 765 | "bottom_view", 766 | "仰", 767 | "仰视", 768 | "仰视图", 769 | ) 770 | 771 | for file in files: 772 | for connector in connector_list: 773 | for suffix in suffix_list_front: 774 | if connector + suffix in file.name: 775 | blueprint_path_front = directory + file.name 776 | for suffix in suffix_list_right: 777 | if connector + suffix in file.name: 778 | blueprint_path_right = directory + file.name 779 | for suffix in suffix_list_top: 780 | if connector + suffix in file.name: 781 | blueprint_path_top = directory + file.name 782 | for suffix in suffix_list_rear: 783 | if connector + suffix in file.name: 784 | blueprint_path_rear = directory + file.name 785 | for suffix in suffix_list_left: 786 | if connector + suffix in file.name: 787 | blueprint_path_left = directory + file.name 788 | for suffix in suffix_list_bottom: 789 | if connector + suffix in file.name: 790 | blueprint_path_bottom = directory + file.name 791 | 792 | blueprint_collection = bpy.data.collections.new( 793 | translations.pgettext("Blueprint") 794 | ) 795 | userpref = context.preferences 796 | addonpref = userpref.addons[__package__].preferences 797 | if addonpref.enable_selection_for_import_blueprint == False: 798 | blueprint_collection.hide_select = True 799 | else: 800 | blueprint_collection.hide_select = False 801 | bpy.context.scene.collection.children.link(blueprint_collection) 802 | 803 | # 所有参考图缩放基准为前视图的高度,固定显示长度为4M。 804 | if blueprint_path_front != "": 805 | front_image_object = bpy.data.objects.new( 806 | translations.pgettext("Blueprint Front"), None 807 | ) 808 | blueprint_collection.objects.link(front_image_object) 809 | front_image_object.empty_display_type = "IMAGE" 810 | front_image = bpy.data.images.load(filepath=blueprint_path_front) 811 | front_image_object.data = front_image 812 | front_image_object.empty_display_size = 4 813 | front_width = front_image.size[0] 814 | front_height = front_image.size[1] 815 | if front_width > front_height: 816 | scale = front_width / front_height 817 | front_image_object.scale = (scale, scale, scale) 818 | front_image_object.location = (0, 4, 0) 819 | front_image_object.rotation_euler = (radians(90), 0, 0) 820 | front_image_object.empty_image_side = "FRONT" 821 | front_image_object.use_empty_image_alpha = True 822 | front_image_object.color[3] = 0.5 823 | if blueprint_path_rear != "": 824 | rear_image_object = bpy.data.objects.new( 825 | translations.pgettext("Blueprint Rear"), None 826 | ) 827 | blueprint_collection.objects.link(rear_image_object) 828 | rear_image_object.empty_display_type = "IMAGE" 829 | rear_image = bpy.data.images.load(filepath=blueprint_path_rear) 830 | rear_image_object.data = rear_image 831 | rear_image_object.empty_display_size = 4 832 | rear_width = rear_image.size[0] 833 | rear_height = rear_image.size[1] 834 | if rear_width > rear_height: 835 | scale = rear_width / rear_height 836 | rear_image_object.scale = (scale, scale, scale) 837 | rear_image_object.location = (0, -4, 0) 838 | rear_image_object.rotation_euler = (radians(90), 0, radians(180)) 839 | rear_image_object.empty_image_side = "FRONT" 840 | rear_image_object.use_empty_image_alpha = True 841 | rear_image_object.color[3] = 0.5 842 | if blueprint_path_right != "": 843 | right_image_object = bpy.data.objects.new( 844 | translations.pgettext("Blueprint Right"), None 845 | ) 846 | blueprint_collection.objects.link(right_image_object) 847 | right_image_object.empty_display_type = "IMAGE" 848 | right_image = bpy.data.images.load(filepath=blueprint_path_right) 849 | right_image_object.data = right_image 850 | right_image_object.empty_display_size = 4 851 | right_width = right_image.size[0] 852 | right_height = right_image.size[1] 853 | if right_width > right_height: 854 | scale = right_width / right_height 855 | right_image_object.scale = (scale, scale, scale) 856 | right_image_object.location = (-4, 0, 0) 857 | right_image_object.rotation_euler = (radians(90), 0, radians(90)) 858 | right_image_object.empty_image_side = "FRONT" 859 | right_image_object.use_empty_image_alpha = True 860 | right_image_object.color[3] = 0.5 861 | if blueprint_path_left != "": 862 | left_image_object = bpy.data.objects.new( 863 | translations.pgettext("Blueprint Left"), None 864 | ) 865 | blueprint_collection.objects.link(left_image_object) 866 | left_image_object.empty_display_type = "IMAGE" 867 | left_image = bpy.data.images.load(filepath=blueprint_path_left) 868 | left_image_object.data = left_image 869 | left_image_object.empty_display_size = 4 870 | left_width = left_image.size[0] 871 | left_height = left_image.size[1] 872 | if left_width > left_height: 873 | scale = left_width / left_height 874 | left_image_object.scale = (scale, scale, scale) 875 | left_image_object.location = (4, 0, 0) 876 | left_image_object.rotation_euler = (radians(90), 0, radians(-90)) 877 | left_image_object.empty_image_side = "FRONT" 878 | left_image_object.use_empty_image_alpha = True 879 | left_image_object.color[3] = 0.5 880 | if blueprint_path_top != "": 881 | top_image_object = bpy.data.objects.new( 882 | translations.pgettext("Blueprint Top"), None 883 | ) 884 | blueprint_collection.objects.link(top_image_object) 885 | top_image_object.empty_display_type = "IMAGE" 886 | top_image = bpy.data.images.load(filepath=blueprint_path_top) 887 | top_image_object.data = top_image 888 | top_image_object.empty_display_size = 4 889 | top_width = top_image.size[0] 890 | top_height = top_image.size[1] 891 | if blueprint_path_front != "" or blueprint_path_rear != "": 892 | if top_width > top_height: 893 | if blueprint_path_front != "": 894 | scale = (front_width / front_height) * ( 895 | top_width / top_height 896 | ) 897 | else: 898 | scale = (rear_width / rear_height) * ( 899 | top_width / top_height 900 | ) 901 | else: 902 | if blueprint_path_front != "": 903 | scale = front_width / front_height 904 | else: 905 | scale = rear_width / rear_height 906 | top_image_object.scale = (scale, scale, scale) 907 | elif blueprint_path_right != "" or blueprint_path_left != "": 908 | if top_width > top_height: 909 | if blueprint_path_right != "": 910 | scale = right_width / right_height 911 | else: 912 | scale = left_width / left_height 913 | else: 914 | if blueprint_path_right != "": 915 | scale = (right_width / right_height) * ( 916 | top_height / top_width 917 | ) 918 | else: 919 | scale = (left_width / left_height) * ( 920 | top_height / top_width 921 | ) 922 | top_image_object.scale = (scale, scale, scale) 923 | top_image_object.location = (0, 0, -4) 924 | top_image_object.rotation_euler = (0, 0, radians(90)) 925 | top_image_object.empty_image_side = "FRONT" 926 | top_image_object.use_empty_image_alpha = True 927 | top_image_object.color[3] = 0.5 928 | if blueprint_path_bottom != "": 929 | bottom_image_object = bpy.data.objects.new( 930 | translations.pgettext("Blueprint Bottom"), None 931 | ) 932 | blueprint_collection.objects.link(bottom_image_object) 933 | bottom_image_object.empty_display_type = "IMAGE" 934 | bottom_image = bpy.data.images.load(filepath=blueprint_path_bottom) 935 | bottom_image_object.data = bottom_image 936 | bottom_image_object.empty_display_size = 4 937 | bottom_width = bottom_image.size[0] 938 | bottom_height = bottom_image.size[1] 939 | if blueprint_path_front != "" or blueprint_path_rear != "": 940 | if bottom_width > bottom_height: 941 | if blueprint_path_front != "": 942 | scale = (front_width / front_height) * ( 943 | bottom_width / bottom_height 944 | ) 945 | else: 946 | scale = (rear_width / rear_height) * ( 947 | bottom_width / bottom_height 948 | ) 949 | else: 950 | if blueprint_path_front != "": 951 | scale = front_width / front_height 952 | else: 953 | scale = rear_width / rear_height 954 | bottom_image_object.scale = (scale, scale, scale) 955 | elif blueprint_path_right != "" or blueprint_path_left != "": 956 | if bottom_width > bottom_height: 957 | if blueprint_path_right != "": 958 | scale = right_width / right_height 959 | else: 960 | scale = left_width / left_height 961 | else: 962 | if blueprint_path_right != "": 963 | scale = (right_width / right_height) * ( 964 | bottom_height / bottom_width 965 | ) 966 | else: 967 | scale = (left_width / left_height) * ( 968 | bottom_height / bottom_width 969 | ) 970 | bottom_image_object.scale = (scale, scale, scale) 971 | bottom_image_object.location = (0, 0, 4) 972 | bottom_image_object.rotation_euler = (radians(180), 0, radians(90)) 973 | bottom_image_object.empty_image_side = "FRONT" 974 | bottom_image_object.use_empty_image_alpha = True 975 | bottom_image_object.color[3] = 0.5 976 | self.report({"INFO"}, "Import blueprint (reference image) successfully!") 977 | return {"FINISHED"} 978 | 979 | 980 | class TOGGLE_LANGUAGE_OT_check_addon_update(Operator): 981 | bl_idname = "toggle_language.check_addon_update" 982 | bl_label = "Check Addon Update" 983 | bl_description = "Check for updates." 984 | 985 | def get_current_addon_version(self, manifest_file): 986 | if os.path.exists(manifest_file): 987 | with open(manifest_file, "r") as f: 988 | content = f.read() 989 | lines = content.splitlines() 990 | version = None 991 | for line in lines: 992 | if line.startswith("version = "): 993 | version = line.split(" = ")[1].strip('"') 994 | break 995 | return version 996 | else: 997 | userpref = bpy.context.preferences 998 | if ( 999 | userpref.version[0] == 4 and userpref.version[1] >= 1 1000 | ) or userpref.version[0] >= 5: 1001 | message_box_with_confirm( 1002 | title=f"{manifest_file} {translations.pgettext('not found')}", 1003 | message="{} {} {}".format( 1004 | translations.pgettext( 1005 | "Current addon version can't be retrieved. Please check if" 1006 | ), 1007 | manifest_file, 1008 | translations.pgettext("exists."), 1009 | ), 1010 | icon="ERROR", 1011 | ) 1012 | else: 1013 | message_box( 1014 | title=f"{manifest_file} {translations.pgettext('not found')}", 1015 | message="{} {} {}".format( 1016 | translations.pgettext( 1017 | "Current addon version can't be retrieved. Please check if" 1018 | ), 1019 | manifest_file, 1020 | translations.pgettext("exists."), 1021 | ), 1022 | icon="ERROR", 1023 | ) 1024 | return {"CANCELLED"} 1025 | 1026 | def download_and_install(self, latest_tag): 1027 | url = f"https://github.com/Mister-Kin/ToggleLanguage/archive/refs/tags/{latest_tag}.zip" 1028 | response = requests.get(url) 1029 | if response.status_code == 200: 1030 | zip_file_bytes = io.BytesIO(response.content) 1031 | temp_dir = tempfile.gettempdir() 1032 | with zipfile.ZipFile(zip_file_bytes) as zip_ref: 1033 | zip_ref.extractall(temp_dir) 1034 | latest_version = latest_tag.lstrip("v") 1035 | latest_dir = os.path.join(temp_dir, f"ToggleLanguage-{latest_version}") 1036 | current_dir = os.path.dirname(__file__) 1037 | print(latest_dir, current_dir) 1038 | shutil.rmtree(current_dir) 1039 | shutil.copytree(latest_dir, current_dir, dirs_exist_ok=True, ignore=None) 1040 | shutil.rmtree(latest_dir) 1041 | # 暂未找到可靠方案实现自动刷新插件,因此需要手动重启Blender完成更新 1042 | self.report( 1043 | {"INFO"}, 1044 | "Addon updated successfully. Please restart Blender to finish update.", 1045 | ) 1046 | userpref = bpy.context.preferences 1047 | if ( 1048 | userpref.version[0] == 4 and userpref.version[1] >= 1 1049 | ) or userpref.version[0] >= 5: 1050 | message_box_with_confirm( 1051 | title="Addon updated successfully", 1052 | message="Please restart Blender to finish update.", 1053 | icon="INFO", 1054 | ) 1055 | else: 1056 | message_box( 1057 | title="Addon updated successfully", 1058 | message="Please restart Blender to finish update.", 1059 | icon="INFO", 1060 | ) 1061 | return {"FINISHED"} 1062 | else: 1063 | self.report({"ERROR"}, "Failed to download latest version of the addon.") 1064 | return {"CANCELLED"} 1065 | 1066 | def execute(self, context): 1067 | 1068 | current_dir = os.path.dirname(__file__) 1069 | manifest_file = os.path.join(current_dir, "blender_manifest.toml") 1070 | current_version = self.get_current_addon_version(manifest_file) 1071 | addon_url = ( 1072 | "https://api.github.com/repos/Mister-Kin/ToggleLanguage/releases/latest" 1073 | ) 1074 | response = requests.get(addon_url) 1075 | if response.status_code == 200: 1076 | data = json.loads(response.text) 1077 | latest_tag = data["tag_name"] 1078 | latest_version = latest_tag.lstrip("v") 1079 | self.report( 1080 | {"INFO"}, 1081 | f"{translations.pgettext('current_version: ')}{current_version}, {translations.pgettext('latest_version: ')}{latest_version}", 1082 | ) 1083 | if latest_version > current_version: 1084 | self.report({"INFO"}, "Your addon is out-of-date.") 1085 | self.download_and_install(latest_tag) 1086 | return {"FINISHED"} 1087 | elif latest_version == current_version: 1088 | self.report({"INFO"}, "Your addon is already up-to-date.") 1089 | return {"CANCELLED"} 1090 | else: 1091 | self.report({"INFO"}, "Your addon is newer than latest.") 1092 | return {"CANCELLED"} 1093 | else: 1094 | self.report({"ERROR"}, "Failed to retrieve latest version.") 1095 | return {"CANCELLED"} 1096 | 1097 | 1098 | classes = ( 1099 | TOGGLE_LANGUAGE_OT_toggle_language, 1100 | TOGGLE_LANGUAGE_OT_use_default_hint_scheme, 1101 | TOGGLE_LANGUAGE_OT_use_developer_hint_scheme, 1102 | TOGGLE_LANGUAGE_OT_load_my_blender_settings, 1103 | TOGGLE_LANGUAGE_OT_load_blender_factory_settings, 1104 | TOGGLE_LANGUAGE_OT_delete_all_collections_and_objects, 1105 | TOGGLE_LANGUAGE_OT_add_video_progress_bar, 1106 | TOGGLE_LANGUAGE_OT_import_blueprint, 1107 | TOGGLE_LANGUAGE_OT_check_addon_update, 1108 | TOGGLE_LANGUAGE_OT_message_box_with_confirm, 1109 | ) 1110 | 1111 | 1112 | def register(): 1113 | from bpy.utils import register_class 1114 | 1115 | for cls in classes: 1116 | register_class(cls) 1117 | 1118 | 1119 | def unregister(): 1120 | from bpy.utils import unregister_class 1121 | 1122 | for cls in classes: 1123 | unregister_class(cls) 1124 | -------------------------------------------------------------------------------- /properties.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import PropertyGroup, AddonPreferences 3 | from bpy.props import BoolProperty, EnumProperty 4 | from bpy.app import translations 5 | import rna_keymap_ui 6 | from . import keymaps 7 | 8 | enum_languages = ( 9 | ("zh_HANS", "Simplified Chinese (简体中文)", "zh_HANS", 1), 10 | ("zh_HANT", "Traditional Chinese (繁體中文)", "zh_HANT", 2), 11 | ("en_US", "English (English)", "en_US", 3), 12 | ("ca_AD", "Catalan (Català)", "ca_AD", 4), 13 | ("es", "Spanish (Español)", "es", 5), 14 | ("fr_FR", "French (Français)", "fr_FR", 6), 15 | ("ja_JP", "Japanese (日本語)", "ja_JP", 7), 16 | ("sk_SK", "Slovak (Slovenčina)", "sk_SK", 8), 17 | ("cs_CZ", "Czech (Čeština)", "cs_CZ", 9), 18 | ("de_DE", "German (Deutsch)", "de_DE", 10), 19 | ("it_IT", "Italian (Italiano)", "it_IT", 11), 20 | ("ka", "Georgian (ქართული)", "ka", 12), 21 | ("ko_KR", "Korean (한국어)", "ko_KR", 13), 22 | ("pt_BR", "Brazilian Portuguese (Português do Brasil)", "pt_BR", 14), 23 | ("pt_PT", "Portuguese (Português)", "pt_PT", 15), 24 | ("ru_RU", "Russian (Русский)", "ru_RU", 16), 25 | ("uk_UA", "Ukrainian (Українська)", "uk_UA", 17), 26 | ("vi_VN", "Vietnamese (Tiếng Việt)", "vi_VN", 18), 27 | ) 28 | 29 | enum_languages_before_v4 = ( 30 | ("zh_CN", "Simplified Chinese (简体中文)", "zh_CN", 1), 31 | ("zh_TW", "Traditional Chinese (繁體中文)", "zh_TW", 2), 32 | ("en_US", "English (English)", "en_US", 3), 33 | ("es", "Spanish (Español)", "es", 4), 34 | ("ja_JP", "Japanese (日本語)", "ja_JP", 5), 35 | ("sk_SK", "Slovak (Slovenčina)", "sk_SK", 6), 36 | ("uk_UA", "Ukrainian (Український)", "uk_UA", 7), 37 | ("vi_VN", "Vietnamese (tiếng Việt)", "vi_VN", 8), 38 | ("ar_EG", "Arabic (ﺔﻴﺑﺮﻌﻟﺍ)", "ar_EG", 9), 39 | ("cs_CZ", "Czech (Český)", "cs_CZ", 10), 40 | ("de_DE", "German (Deutsch)", "de_DE", 11), 41 | ("fr_FR", "French (Français)", "fr_FR", 12), 42 | ("it_IT", "Italian (Italiano)", "it_IT", 13), 43 | ("ko_KR", "Korean (한국 언어)", "ko_KR", 14), 44 | ("pt_BR", "Brazilian Portuguese (Português do Brasil)", "pt_BR", 15), 45 | ("pt_PT", "Portuguese (Português)", "pt_PT", 16), 46 | ("ru_RU", "Russian (Русский)", "ru_RU", 17), 47 | ) 48 | 49 | enum_themes = ( 50 | ("blender_dark", "Blender Dark", "Blender Dark (Dark Theme)", 1), 51 | ("blender_light", "Blender Light", "Blender Light (Light Theme)", 2), 52 | ("deep_grey", "Deep Grey", "Deep Grey (Dark Theme)", 3), 53 | ("maya", "Maya", "Maya (Dark Theme)", 4), 54 | ("minimal_dark", "Minimal Dark", "Minimal Dark (Dark Theme)", 5), 55 | ("modo", "Modo", "Modo (Dark Theme)", 6), 56 | ("print_friendly", "Print Friendly", "Print Friendly (Light Theme)", 7), 57 | ("white", "White", "White (Light Theme)", 8), 58 | ("xsi", "XSI", "XSI (Light Theme)", 9), 59 | ) 60 | 61 | 62 | def update_translate_new_dataname_state(self, context): 63 | userpref = context.preferences 64 | scene = context.scene 65 | lang = translations.locale 66 | if lang != "en_US": 67 | userpref.view.use_translate_new_dataname = ( 68 | scene.toggle_language_settings.translate_new_dataname 69 | ) 70 | 71 | 72 | class Toggle_Language_settings(PropertyGroup): 73 | translate_new_dataname: BoolProperty( 74 | name="Translate New Data-Block's Name", 75 | description="Enable or disable translation for new data-block's name", 76 | default=False, 77 | update=update_translate_new_dataname_state, 78 | ) 79 | 80 | 81 | class Toggle_Language_preferences(AddonPreferences): 82 | bl_idname = __package__ 83 | 84 | # 在 AddonPreferences class 中构建 property,其值才能随着用户偏好设置自动保存。 85 | first_lang: EnumProperty( 86 | name="First Language", 87 | description="First language for toggling", 88 | default="zh_HANS", 89 | items=enum_languages, 90 | ) 91 | 92 | first_lang_before_v4: EnumProperty( 93 | name="First Language", 94 | description="First language for toggling", 95 | default="zh_CN", 96 | items=enum_languages_before_v4, 97 | ) 98 | 99 | second_lang: EnumProperty( 100 | name="Second Language", 101 | description="Second language for toggling", 102 | default="en_US", 103 | items=enum_languages, 104 | ) 105 | 106 | second_lang_before_v4: EnumProperty( 107 | name="Second Language", 108 | description="Second language for toggling", 109 | default="en_US", 110 | items=enum_languages_before_v4, 111 | ) 112 | 113 | preset_theme: EnumProperty( 114 | name="Preset Theme", 115 | description="Preset theme for Load My Blender Settings feature", 116 | default="white", 117 | items=enum_themes, 118 | ) 119 | 120 | disable_paths_setting: BoolProperty( 121 | name="Disable Paths Setting", 122 | description="Disable paths setting for Load My Blender Settings feature", 123 | default=False, 124 | ) 125 | 126 | disable_theme_setting: BoolProperty( 127 | name="Disable Theme Setting", 128 | description="Disable theme setting for Load My Blender Settings feature", 129 | default=False, 130 | ) 131 | 132 | disable_saving_startup_file: BoolProperty( 133 | name="Disable Saving Startup File", 134 | description="Disable saving startup file when applying feature Load My Blender Settings", 135 | default=False, 136 | ) 137 | 138 | use_cpu_in_gpu_render_setting: BoolProperty( 139 | name="Use CPU in GPU Render Setting", 140 | description="Use CPU in GPU render setting for Load My Blender Settings feature", 141 | default=False, 142 | ) 143 | 144 | enable_selection_for_import_blueprint: BoolProperty( 145 | name="Enable Selection for Import Blueprint", 146 | description="Enable selection for Import Blueprint feature (Blueprint reference can't be selected after importing if not checked)", 147 | default=False, 148 | ) 149 | 150 | def draw(self, context): 151 | layout = self.layout 152 | userpref = context.preferences 153 | 154 | box = layout.box() 155 | box.label( 156 | text="Please select two languages for addon to toggle UI language.", 157 | icon="SETTINGS", 158 | ) 159 | row = box.row(align=True) 160 | if userpref.version[0] >= 4: 161 | row.prop(self, "first_lang") 162 | row.separator() 163 | row.prop(self, "second_lang") 164 | else: 165 | row.prop(self, "first_lang_before_v4") 166 | row.separator() 167 | row.prop(self, "second_lang_before_v4") 168 | 169 | box = layout.box() 170 | box.label( 171 | text="Addon's Keymaps", 172 | icon="TOOL_SETTINGS", 173 | ) 174 | col = box.column() 175 | kc = bpy.context.window_manager.keyconfigs.addon 176 | # km = context.window_manager.keyconfigs.user.keymaps["Window"] 177 | for km, kmi in keymaps.addon_keymaps: 178 | km = km.active() 179 | kmi = self.get_addon_keymaps_item(km, kmi.idname) 180 | col.context_pointer_set("keymap", km) 181 | rna_keymap_ui.draw_kmi([], kc, km, kmi, col, 0) 182 | 183 | box = layout.box() 184 | box.label( 185 | text="Addon's Utility Settings", 186 | icon="TOOL_SETTINGS", 187 | ) 188 | row = box.row(align=True) 189 | row.prop(self, "enable_selection_for_import_blueprint") 190 | 191 | box = layout.box() 192 | box.label( 193 | text="Some settings for Load My Blender Settings feature.", 194 | icon="TOOL_SETTINGS", 195 | ) 196 | box.label( 197 | text="Please configure following settings before applying Load My Blender Settings feature.", 198 | ) 199 | row = box.row(align=True) 200 | row.prop(self, "disable_paths_setting") 201 | row.separator() 202 | row.prop(self, "disable_theme_setting") 203 | 204 | row = box.row(align=True) 205 | row.prop(self, "disable_saving_startup_file") 206 | row.separator() 207 | row.prop(self, "use_cpu_in_gpu_render_setting") 208 | 209 | row = box.row(align=True) 210 | row.prop(self, "preset_theme") 211 | 212 | def get_addon_keymaps_item(self, km, kmi_idname): 213 | for i, km_item in enumerate(km.keymap_items): 214 | if km.keymap_items.keys()[i] == kmi_idname: 215 | return km_item 216 | return None 217 | 218 | 219 | classes = ( 220 | Toggle_Language_settings, 221 | Toggle_Language_preferences, 222 | ) 223 | 224 | 225 | def register(): 226 | from bpy.utils import register_class 227 | 228 | for cls in classes: 229 | register_class(cls) 230 | 231 | bpy.types.Scene.toggle_language_settings = bpy.props.PointerProperty( 232 | type=Toggle_Language_settings 233 | ) 234 | 235 | 236 | def unregister(): 237 | from bpy.utils import unregister_class 238 | 239 | for cls in classes: 240 | unregister_class(cls) 241 | 242 | del bpy.types.Scene.toggle_language_settings 243 | -------------------------------------------------------------------------------- /ui.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Menu 3 | from bpy.app import translations 4 | 5 | 6 | def draw_ui(self, context): 7 | layout = self.layout 8 | row = layout.row(align=True) 9 | row.operator("toggle_language.toggle_language") 10 | row.menu("TOGGLE_LANGUAGE_MT_utilities") 11 | row.menu("TOGGLE_LANGUAGE_MT_settings") 12 | row.operator("screen.userpref_show", icon="PREFERENCES", text="") 13 | 14 | 15 | class TOGGLE_LANGUAGE_MT_settings(Menu): 16 | bl_idname = "TOGGLE_LANGUAGE_MT_settings" 17 | bl_label = "Settings" 18 | 19 | def draw(self, context): 20 | scene = context.scene 21 | userpref = context.preferences 22 | if userpref.view.show_developer_ui: 23 | hint_scheme_menu_name = "Current Hint Scheme: Developer" 24 | else: 25 | hint_scheme_menu_name = "Current Hint Scheme: Default" 26 | 27 | layout = self.layout 28 | col = layout.column(align=True) 29 | col.menu( 30 | "TOGGLE_LANGUAGE_MT_hint_scheme", icon="TEXT", text=hint_scheme_menu_name 31 | ) 32 | col.prop(scene.toggle_language_settings, "translate_new_dataname") 33 | col.operator("toggle_language.load_my_blender_settings", icon="SETTINGS") 34 | col.operator( 35 | "toggle_language.load_blender_factory_settings", icon="TOOL_SETTINGS" 36 | ) 37 | col.operator("toggle_language.check_addon_update", icon="TRIA_UP") 38 | 39 | 40 | class TOGGLE_LANGUAGE_MT_hint_scheme(Menu): 41 | bl_idname = "TOGGLE_LANGUAGE_MT_hint_scheme" 42 | bl_label = "Hint Scheme Menu" 43 | 44 | def draw(self, context): 45 | layout = self.layout 46 | col = layout.column(align=True) 47 | col.operator("toggle_language.use_default_hint_scheme") 48 | col.operator("toggle_language.use_developer_hint_scheme") 49 | 50 | 51 | class TOGGLE_LANGUAGE_MT_utilities(Menu): 52 | bl_idname = "TOGGLE_LANGUAGE_MT_utilities" 53 | bl_label = "Utilities" 54 | 55 | def draw(self, context): 56 | layout = self.layout 57 | col = layout.column(align=True) 58 | col.operator( 59 | "toggle_language.delete_all_collections_and_objects", 60 | icon="OUTLINER", 61 | ) 62 | col.operator("toggle_language.add_video_progress_bar", icon="TOPBAR") 63 | col.operator("toggle_language.import_blueprint", icon="IMAGE_REFERENCE") 64 | 65 | 66 | classes = ( 67 | TOGGLE_LANGUAGE_MT_settings, 68 | TOGGLE_LANGUAGE_MT_hint_scheme, 69 | TOGGLE_LANGUAGE_MT_utilities, 70 | ) 71 | 72 | 73 | def register(): 74 | from bpy.utils import register_class 75 | 76 | for cls in classes: 77 | register_class(cls) 78 | 79 | bpy.types.TOPBAR_MT_editor_menus.append(draw_ui) 80 | 81 | 82 | def unregister(): 83 | from bpy.utils import unregister_class 84 | 85 | for cls in classes: 86 | unregister_class(cls) 87 | 88 | bpy.types.TOPBAR_MT_editor_menus.remove(draw_ui) 89 | --------------------------------------------------------------------------------