├── 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 |
--------------------------------------------------------------------------------