├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __init__.py ├── auto_rename.py ├── clean_gp_slots.py ├── clean_slots.py ├── colornames.json ├── fn.py ├── set_color.py └── ui.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.py[cod] -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | 0.3.0 4 | 5 | - feat: material cleaner's _similarity check_ is more robust, also check nodes values 6 | - feat: add Gpencil material cleaner in materials submenu (copy of `gp toolbox`'s operator) 7 | - don't register if gp toolbox addon is active to avoid duplicate 8 | 9 | 0.2.0 10 | 11 | - feat: material incremental clone (duplication) remover 12 | - fix: bad register in Blender 2.93.0 13 | 14 | 0.1.1 15 | 16 | - code: fix bl_infos and typos 17 | 18 | 0.1.0 19 | 20 | - feat: new auto-renaming system based on xkcd color 21 | - feat: better color source check (if tex_image type found, sample center pixel) 22 | - feat: Also works on grease pencil materials 23 | - feat: added addon prefs option to rename only non-named material 24 | - fix: littles bugs 25 | - code: big refactor 26 | 27 | 0.0.5 28 | 29 | - port from 2.79. Only color transfer options -------------------------------------------------------------------------------- /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 | # Auto material 2 | 3 | Blender addon - automate stuff to handle materials 4 | 5 | **[Download latest](https://github.com/Pullusb/autoMaterial/archive/master.zip)** 6 | 7 | 8 | 9 | 10 | Want to support me? [Check this page](http://www.samuelbernou.fr/donate) 11 | 12 | ### Description: 13 | 14 | **Set viewport color from nodes** (or the opposite) 15 | 16 | ![set_color](https://github.com/Pullusb/images_repo/raw/master/AM_set_vp_color_from_node_and_back.gif) 17 | 18 | When getting from node, the node tree is reverse climbed until it found a "relevant" color input. (result can be unexpected) 19 | 20 | > If it stumble upon an image texture it will sample the center pixel color of the image 21 | 22 | 23 | **Auto name material from closest color name** 24 | 25 | ![auto_rename](https://github.com/Pullusb/images_repo/raw/master/AM_material_auto_renaming.png) 26 | 27 | 28 | Applied on selected objects. There is an option to apply on all slots instead of active only 29 | 30 | > On grease pencil material the fill color is always taken if activated 31 | 32 | *Only unnamed* option (in addon preferences, disabled by default) : Allow to rename only materials that have default names ('Material', 'Material.001'...) 33 | 34 | 35 | The color name are taken from xkcd database (Licence [CC0](http://creativecommons.org/publicdomain/zero/1.0/)) listed here : https://xkcd.com/color/rgb 36 | Made for a [color survey by Randall Munroe](https://blog.xkcd.com/2010/05/03/color-survey-results/) 37 | 38 | If you want to use another data base, just swap yours with colornames.json in addon folder (formated like `"name" : "hexa code"`) 39 | 40 | e.g: 41 | 42 | ``` 43 | { 44 | "cloudy blue": "#acc2d9", 45 | "dark pastel green": "#56ae57" 46 | } 47 | ``` -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # ##### BEGIN GPL LICENSE BLOCK ##### 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | # 16 | # ##### END GPL LICENSE BLOCK ##### 17 | 18 | bl_info = { 19 | "name": "Auto material", 20 | "description": "Some materials handling tools", 21 | "author": "Samuel Bernou", 22 | "version": (0, 3, 0), 23 | "blender": (2, 91, 0), 24 | "location": "Properties > Material > Settings", 25 | "warning": "", 26 | "doc_url": "https://github.com/Pullusb/autoMat", 27 | "tracker_url": "https://github.com/Pullusb/autoMat/issues", 28 | "category": "Material"} 29 | 30 | from . import set_color 31 | from . import auto_rename 32 | from . import clean_slots 33 | from . import clean_gp_slots 34 | from . import ui 35 | 36 | import bpy 37 | class AM_preferences(bpy.types.AddonPreferences): 38 | bl_idname = __name__.split('.')[0] # or with: os.path.splitext(__name__)[0] 39 | 40 | only_unnamed: bpy.props.BoolProperty( 41 | name='Rename only unnamed materials (when using multiple renaming)', 42 | description="In 'Rename all slots' mode, rename only unnamed materials (starting with 'Material')", 43 | default=False) 44 | 45 | def draw(self, context): 46 | layout = self.layout 47 | # layout.use_property_split = True 48 | # flow = layout.grid_flow(row_major=True, columns=0, even_columns=True, even_rows=False, align=False) 49 | # layout = flow.column() 50 | layout.label(text='Renaming options:') 51 | layout.prop(self, "only_unnamed") 52 | 53 | 54 | def register(): 55 | bpy.utils.register_class(AM_preferences) 56 | 57 | auto_rename.register() 58 | set_color.register() 59 | clean_slots.register() 60 | clean_gp_slots.register() 61 | ui.register() 62 | 63 | def unregister(): 64 | ui.unregister() 65 | auto_rename.unregister() 66 | clean_gp_slots.unregister() 67 | clean_slots.unregister() 68 | set_color.unregister() 69 | 70 | bpy.utils.unregister_class(AM_preferences) 71 | 72 | if __name__ == "__main__": 73 | register() 74 | -------------------------------------------------------------------------------- /auto_rename.py: -------------------------------------------------------------------------------- 1 | from . import fn 2 | import bpy 3 | import os 4 | from pathlib import Path 5 | 6 | 7 | def get_material_color(mat, viewport=False): 8 | '''return closest color from nodes (cycle only), or viewport color''' 9 | if mat.is_grease_pencil: 10 | if mat.grease_pencil.show_fill: 11 | return mat.grease_pencil.fill_color[:] 12 | else:# if not fill get 13 | return mat.grease_pencil.color[:] 14 | 15 | if viewport: 16 | return mat.diffuse_color[:] 17 | else: 18 | # later, try to get closest node from output having a color, fallback on viewport color 19 | return fn.get_closest_node_color(mat) 20 | 21 | 22 | def rename_mat(viewport=True, self=None, color_dic=None, context=None): 23 | '''If any, rename active material of active objects''' 24 | 25 | matlist = fn.material_selection_scope() 26 | if not matlist: 27 | fn.report('No material to rename', self=self, mode='ERROR') 28 | 29 | errors = [] 30 | warnings = [] 31 | 32 | ct = 0 33 | for mat in matlist: 34 | col = get_material_color(mat, viewport) # get rgb color 35 | colname = fn.get_color_name(col, color_dic) # get name 36 | if not colname: 37 | errors.append(f'Error trying to get color name from {col}') 38 | continue 39 | 40 | if colname == mat.name: # check if already named 41 | warnings.append(f'Material "{mat.name}" already named') 42 | continue 43 | 44 | # check if already exist 45 | if bpy.data.materials.get(colname): 46 | warnings.append(f'"{mat.name}" -> "{colname}" but already existed') 47 | 48 | print(f'"{mat.name}" -> "{colname}"') 49 | mat.name = colname 50 | ct += 1 51 | 52 | if warnings: 53 | fn.report(f"Auto material {len(warnings)} warnings--\n" + '\n'.join(errors), self=self, mode='WARNING') 54 | 55 | if errors: 56 | fn.report(f"Auto material {len(errors)} errors--\n" + '\n'.join(errors), self=self, mode='ERROR') 57 | 58 | if not warnings and not errors: 59 | fn.report(f'{ct} Materials renamed', self=self) 60 | else: 61 | print(f'{ct} Materials renamed') 62 | 63 | 64 | 65 | class AM_OT_auto_name_material(bpy.types.Operator): 66 | bl_idname = "materials.auto_name_material" 67 | bl_label = "Auto name material" 68 | bl_description = "rename material according to color" 69 | bl_options = {"REGISTER"} 70 | 71 | viewport: bpy.props.BoolProperty() 72 | 73 | def execute(self, context): 74 | db = Path(os.path.realpath(__file__)).parent / 'colornames.json' 75 | color_dic = fn.load_color_dic(db) 76 | rename_mat(viewport=self.viewport, self=self, 77 | color_dic=color_dic, context=context) 78 | return {"FINISHED"} 79 | 80 | 81 | class AM_OT_convert_clipboard_color_to_name(bpy.types.Operator): 82 | bl_idname = "materials.convert_clip_color_to_name" 83 | bl_label = "convert clipboard color to name" 84 | bl_description = "Convert color in paperclip to name (replace paperclip)" 85 | bl_options = {"REGISTER"} 86 | 87 | def execute(self, context): 88 | clip = context.window_manager.clipboard 89 | 90 | import re # regex check if looks like a list/tuple/hex to ensure safety 91 | reclip = re.match(r'^(\(|\[)[\d., ]+(\)|\])$', clip) 92 | rehex = re.match(r'#?[a-zA-Z0-9]{3,6}', clip) 93 | 94 | if not reclip and not rehex: 95 | fn.report(f'Could not get color name from clipboard\nFormat exemple : [0.8, 0.8, 0.8, 1.0], (0.80, 0.80, 0.80)', 96 | self=self, mode='ERROR') 97 | return {"CANCELLED"} 98 | 99 | db = Path(os.path.realpath(__file__)).parent / 'colornames.json' 100 | color_dic = fn.load_color_dic(db) 101 | 102 | if reclip: 103 | # try to safely eval string to get tuple/list 104 | try: 105 | import ast # string 106 | clip = ast.literal_eval(clip) 107 | except: 108 | fn.report(f'Could not evaluate:\n{clip}', 109 | self=self, mode='ERROR') 110 | return {"CANCELLED"} 111 | 112 | # send rgb as tuple or hex as str 113 | newclip = fn.get_color_name(clip, color_dic) 114 | 115 | if not newclip: 116 | fn.report('Could not get color name', self=self, mode='ERROR') 117 | return {"CANCELLED"} 118 | 119 | fn.report(newclip, self=self) 120 | context.window_manager.clipboard = newclip 121 | 122 | return {"FINISHED"} 123 | 124 | 125 | classes = ( 126 | AM_OT_auto_name_material, 127 | AM_OT_convert_clipboard_color_to_name, 128 | ) 129 | 130 | 131 | def register(): 132 | bpy.types.Scene.mat_change_multiple = bpy.props.BoolProperty( 133 | name="Affect All Slots", default=True, 134 | description="Affect all material slots (skip already named materials if option is active in addon prefs)\nelse only active slot", options={'HIDDEN'}) 135 | 136 | for cls in classes: 137 | bpy.utils.register_class(cls) 138 | 139 | 140 | def unregister(): 141 | for cls in reversed(classes): 142 | bpy.utils.unregister_class(cls) 143 | 144 | del bpy.types.Scene.mat_change_multiple 145 | -------------------------------------------------------------------------------- /clean_gp_slots.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import addon_utils 3 | 4 | class AMT_OT_clean_gp_material_stack(bpy.types.Operator): 5 | bl_idname = "materials.clean_gp_material_stack" 6 | bl_label = "Clean GPencil Material Stack" 7 | bl_description = "Clean materials duplication in active GP object stack" 8 | bl_options = {"REGISTER", "UNDO"} 9 | 10 | use_clean_mats : bpy.props.BoolProperty(name="Remove Duplication", 11 | description="All duplicated material (with suffix .001, .002 ...) will be replaced by the material with clean name (if found in scene)" , 12 | default=True) 13 | 14 | skip_different_materials : bpy.props.BoolProperty(name="Skip Different Material", 15 | description="Will not touch duplication if color settings are different (and show infos about skipped materials)", 16 | default=True) 17 | 18 | use_fuses_mats : bpy.props.BoolProperty(name="Fuse Materials Slots", 19 | description="Fuse materials slots when multiple uses same materials", 20 | default=True) 21 | 22 | remove_empty_slots : bpy.props.BoolProperty(name="Remove Empty Slots", 23 | description="Remove slots that haven't any material attached ", 24 | default=True) 25 | 26 | @classmethod 27 | def poll(cls, context): 28 | return context.object and context.object.type == 'GPENCIL' 29 | 30 | def invoke(self, context, event): 31 | self.ob = context.object 32 | return context.window_manager.invoke_props_dialog(self) 33 | 34 | def draw(self, context): 35 | layout = self.layout 36 | 37 | box = layout.box() 38 | box.prop(self, 'use_clean_mats') 39 | if self.use_clean_mats: 40 | box.prop(self, 'skip_different_materials') 41 | 42 | box = layout.box() 43 | box.prop(self, 'use_fuses_mats') 44 | box = layout.box() 45 | box.prop(self, 'remove_empty_slots') 46 | 47 | 48 | def different_gp_mat(self, mata, matb): 49 | a = mata.grease_pencil 50 | b = matb.grease_pencil 51 | if a.color[:] != b.color[:]: 52 | return f'! {self.ob.name}: {mata.name} and {matb.name} stroke color is different' 53 | if a.fill_color[:] != b.fill_color[:]: 54 | return f'! {self.ob.name}: {mata.name} and {matb.name} fill_color color is different' 55 | if a.show_stroke != b.show_stroke: 56 | return f'! {self.ob.name}: {mata.name} and {matb.name} stroke has different state' 57 | if a.show_fill != b.show_fill: 58 | return f'! {self.ob.name}: {mata.name} and {matb.name} fill has different state' 59 | 60 | ## Clean dups 61 | def clean_mats_duplication(self, ob): 62 | import re 63 | diff_ct = 0 64 | todel = [] 65 | if ob.type != 'GPENCIL': 66 | return 67 | if not hasattr(ob, 'material_slots'): 68 | return 69 | for i, ms in enumerate(ob.material_slots): 70 | mat = ms.material 71 | if not mat: 72 | continue 73 | match = re.search(r'(.*)\.\d{3}$', mat.name) 74 | if not match: 75 | continue 76 | basemat = bpy.data.materials.get(match.group(1)) 77 | if not basemat: 78 | continue 79 | diff = self.different_gp_mat(mat, basemat) 80 | if diff: 81 | print(diff) 82 | diff_ct += 1 83 | if self.skip_different_materials: 84 | continue 85 | 86 | if mat not in todel: 87 | todel.append(mat) 88 | ms.material = basemat 89 | print(f'{ob.name} : slot {i} >> replaced {mat.name}') 90 | mat.use_fake_user = False 91 | 92 | if diff_ct: 93 | return('INFO', f'{diff_ct} mat skipped >> same name but different color settings!') 94 | 95 | ## fuse 96 | def fuse_object_mats(self, ob): 97 | for i in range(len(ob.material_slots))[::-1]: 98 | ms = ob.material_slots[i] 99 | mat = ms.material 100 | 101 | # update mat list 102 | mlist = [ms.material for ms in ob.material_slots if ms.material] 103 | if mlist.count(mat) > 1: 104 | # get first material in list 105 | new_mat_id = mlist.index(mat) 106 | 107 | # iterate in all strokes and replace with new_mat_id 108 | for l in ob.data.layers: 109 | for f in l.frames: 110 | for s in f.strokes: 111 | if s.material_index == i: 112 | s.material_index = new_mat_id 113 | 114 | # delete slot (or add to the remove_slot list 115 | ob.active_material_index = i 116 | bpy.ops.object.material_slot_remove() 117 | 118 | def delete_empty_material_slots(self, ob): 119 | for i in range(len(ob.material_slots))[::-1]: 120 | ms = ob.material_slots[i] 121 | mat = ms.material 122 | if not mat: 123 | ob.active_material_index = i 124 | bpy.ops.object.material_slot_remove() 125 | 126 | def execute(self, context): 127 | ob = context.object 128 | info = None 129 | 130 | if not self.use_clean_mats and not self.use_fuses_mats and not self.remove_empty_slots: 131 | self.report({'ERROR'}, 'At least one operation should be selected') 132 | return {"CANCELLED"} 133 | 134 | if self.use_clean_mats: 135 | info = self.clean_mats_duplication(ob) 136 | if self.use_fuses_mats: 137 | self.fuse_object_mats(ob) 138 | if self.remove_empty_slots: 139 | self.delete_empty_material_slots(ob) 140 | 141 | if info: 142 | self.report({info[0]}, info[1]) 143 | 144 | return {"FINISHED"} 145 | 146 | 147 | def material_gp_clean_menu(self, context): 148 | '''To append to GPENCIL_MT_material_context_menu''' 149 | layout = self.layout 150 | layout.operator("materials.clean_gp_material_stack", text='Clean Material Slots', icon='NODE_MATERIAL') 151 | 152 | # avoid unregister error if Gp toolbox was unregistered before 153 | registered = True 154 | 155 | def register(): 156 | global registered 157 | # Don't register if gp toolbox is loaded (same ops) 158 | if not any(addon_utils.check('gp_toolbox')): 159 | bpy.utils.register_class(AMT_OT_clean_gp_material_stack) 160 | bpy.types.GPENCIL_MT_material_context_menu.append(material_gp_clean_menu) 161 | else: 162 | registered = False 163 | 164 | def unregister(): 165 | if not registered: 166 | return 167 | if not any(addon_utils.check('gp_toolbox')): 168 | bpy.types.GPENCIL_MT_material_context_menu.remove(material_gp_clean_menu) 169 | bpy.utils.unregister_class(AMT_OT_clean_gp_material_stack) -------------------------------------------------------------------------------- /clean_slots.py: -------------------------------------------------------------------------------- 1 | import bpy, re 2 | import numpy as np 3 | 4 | # TODO option : Delete duplication if it isn't assigned at all (a bit hazardous) 5 | 6 | attr_exclusion = [ 7 | '__doc__', 8 | '__module__', 9 | '__slots__', 10 | 'bl_rna', 11 | 'rna_type', 12 | 'animation_data_clear', 13 | 'animation_data_create', 14 | 'asset_clear', 15 | 'asset_mark', 16 | 'copy', 17 | 'cycles', 18 | 'diffuse_color', 19 | 'evaluated_get', 20 | 'line_color', 21 | 'lineart', 22 | 'make_local', 23 | 'name', 24 | 'name_full', 25 | 'node_tree', 26 | 'original', 27 | 'override_create', 28 | 'override_hierarchy_create', 29 | 'override_template_create', 30 | 'preview', 31 | 'preview_ensure', 32 | 'texture_paint_images', 33 | 'texture_paint_slots', 34 | 'update_tag', 35 | 'user_clear', 36 | 'user_of_id', 37 | 'user_remap', 38 | 'users', 39 | 'is_embedded_data', 40 | 'tag', 41 | ] 42 | # animation_data 43 | 44 | def up_node_tree(node, nlist=[]): 45 | '''Recursively go up a tree and return a list of each nodes''' 46 | for input in node.inputs: 47 | if input.is_linked: 48 | for link in input.links: 49 | nlist = up_node_tree(link.from_node, nlist) 50 | return nlist+[node] 51 | 52 | def get_shader_output(m): 53 | outputs = [n for n in m.node_tree.nodes if n.type == 'OUTPUT_MATERIAL' and n.is_active_output] 54 | if not outputs: 55 | return 56 | return outputs[0] 57 | 58 | def mats_similarity_check(a, b, check_settings=True, check_node_tree=True): 59 | '''Naive check for similarity between two material 60 | Return True if similar 61 | ''' 62 | if check_settings: 63 | for att, b_att in zip(dir(a), dir(b)): 64 | if att != b_att: 65 | print(f'! Setting attribute list does not match ! {a.name}:{att} Vs {b.name}:{b_att}') 66 | return 67 | if att in attr_exclusion or att.startswith('__'): 68 | continue 69 | # print(a_att) 70 | if getattr(a, att) != getattr(b, att): 71 | print(f'{att}: {a.name} != {b.name}') 72 | return 73 | 74 | if check_node_tree and a.use_nodes and b.use_nodes: 75 | if len(a.node_tree.nodes) != len(b.node_tree.nodes): 76 | return 77 | outa = get_shader_output(a) 78 | outb = get_shader_output(b) 79 | if not outa or not outb: 80 | return 81 | ## just check output connected node_trees have same nodetypes 82 | for na, nb in zip(up_node_tree(outa), up_node_tree(outb)): 83 | if na.type != nb.type: 84 | # print(f'type : {na.type} != {nb.type}') 85 | return 86 | 87 | for i1, i2 in zip(na.inputs, nb.inputs): 88 | if i1.is_linked != i2.is_linked: 89 | return 90 | if i1.is_linked: 91 | continue 92 | if not hasattr(i1, 'default_value'): 93 | continue 94 | 95 | ## use isclose since value approximation break similarity checking 96 | if not np.isclose(i1.default_value, i2.default_value).all(): 97 | print(f'{a.name} > {na.name} > {i1.name}: {i1.default_value} != {i2.default_value} ({b.name})') 98 | return 99 | 100 | return True 101 | 102 | def replace_increment_duplication(targets='ACTIVE', similar_check=False, skip_fake_user=False, force_delete=False): 103 | """Replace duplication (.001, .002) of a material in object slots by the original material (if any) 104 | :targets: Select which material slots to scan to affect in ('ACTIVE', 'SELECTED', 'ALL') 105 | :similar_check: replace material only if settings/node_tree are exactly similar (approximate method, dont check node values) 106 | :skip_fake_user: skip every material that have a fake user 107 | :force_delete: True remove the material from blend immediately (usefull to delete fake_user materials). 108 | """ 109 | 110 | if targets == 'ACTIVE': 111 | pool = [bpy.context.object] 112 | elif targets == 'SELECTED': 113 | pool = bpy.context.selected_objects 114 | elif targets == 'ALL': 115 | pool = bpy.context.scene.objects 116 | else: 117 | pool = [] 118 | 119 | matnum = 0 120 | todel = [] 121 | for ob in pool: 122 | if not hasattr(ob, 'material_slots'): 123 | continue 124 | for i, ms in enumerate(ob.material_slots): 125 | mat = ms.material 126 | if not mat: 127 | continue 128 | 129 | if skip_fake_user and mat.use_fake_user: 130 | continue 131 | 132 | match = re.search(r'(.*)\.\d{3}$', mat.name) 133 | if not match: 134 | continue 135 | 136 | basemat = bpy.data.materials.get(match.group(1)) 137 | if not basemat: 138 | continue 139 | 140 | if similar_check and not mats_similarity_check(basemat, mat): 141 | continue 142 | 143 | if mat not in todel: 144 | todel.append(mat) 145 | 146 | ms.material = basemat 147 | print(f'{ob.name} : slot {i} >> replaced {mat.name}') 148 | matnum += 1 149 | mat.use_fake_user = False 150 | 151 | if force_delete: 152 | for m in reversed(todel): 153 | bpy.data.materials.remove(m) 154 | 155 | return matnum 156 | 157 | 158 | class AM_OT_replace_mat_duplication(bpy.types.Operator): 159 | bl_idname = "materials.replace_mat_duplication" 160 | bl_label = "Replace Material Duplication" 161 | bl_description = "Delete materials incremental duplications (.001 .002 ...) and replace in slot by material holding original name" 162 | bl_options = {"REGISTER", "UNDO"} # , "INTERNAL" 163 | 164 | target : bpy.props.EnumProperty( 165 | name="Target Objects", description="Choose objects targets to check material slots", 166 | default='ACTIVE', 167 | items=( 168 | ('ACTIVE', 'Active', 'Replace incremental duplication in active objects material slots', 0), 169 | ('SELECTED', 'Selected', 'Replace incremental duplication in selected objects material slots', 1), 170 | ('ALL', 'All', 'Replace incremental duplication in all objects material slots', 2), 171 | )) 172 | 173 | # use_remove_dup : bpy.props.BoolProperty(name="Remove Duplication", 174 | # description="All duplicated material (with suffix .001, .002 ...) will be replaced by the material with clean name (if found in scene)" , 175 | # default=True) 176 | 177 | skip_different_materials : bpy.props.BoolProperty(name="Skip Different Material", 178 | description="Will not affect duplication if settings/node_tree is different", 179 | default=True) 180 | 181 | skip_fake_user : bpy.props.BoolProperty(name="Skip Fake User", 182 | description="Duplication with fake user will be untouched, even if they are identical", 183 | default=False) 184 | 185 | force_delete : bpy.props.BoolProperty(name="Direct Delete", 186 | description="Replaced duplication will be immediately deleted after being replaced\nThis is usefull to be sure duplication with fake users are not kept in the blend", 187 | default=False) 188 | 189 | # remove_empty_slots : bpy.props.BoolProperty(name="Remove Empty Slots", 190 | # description="Remove slots that haven't any material attached ", 191 | # default=True) 192 | 193 | @classmethod 194 | def poll(cls, context): 195 | return context.object 196 | 197 | def invoke(self, context, event): 198 | self.ob = context.object 199 | return context.window_manager.invoke_props_dialog(self, width=400) 200 | 201 | def draw(self, context): 202 | layout = self.layout 203 | 204 | layout.label(text='Replace incremented clones (.001 .002 ...) by original material' ) 205 | box = layout.box() 206 | box.prop(self, 'target') 207 | box.prop(self, 'skip_different_materials') 208 | box.prop(self, 'skip_fake_user') 209 | box.prop(self, 'force_delete') 210 | 211 | # box.prop(self, 'use_remove_dup') 212 | # if self.use_remove_dup: 213 | # box.prop(self, 'skip_different_materials') 214 | # if self.use_remove_dup: 215 | # box.prop(self, 'skip_fake_user') 216 | # if self.use_remove_dup: 217 | # box.prop(self, 'force_delete') 218 | 219 | # box = layout.box() 220 | # box.prop(self, 'remove_empty_slots') 221 | 222 | ## override to affect non-active objects ? 223 | # def delete_empty_material_slots(self, ob): 224 | # for i in range(len(ob.material_slots))[::-1]: 225 | # ms = ob.material_slots[i] 226 | # mat = ms.material 227 | # if not mat: 228 | # ob.active_material_index = i 229 | # bpy.ops.object.material_slot_remove() 230 | 231 | def execute(self, context): 232 | ob = context.object 233 | info = None 234 | 235 | # if not self.use_remove_dup and not self.remove_empty_slots: 236 | # self.report({'ERROR'}, 'At least one operation should be selected') 237 | # return {"CANCELLED"} 238 | 239 | # if self.use_remove_dup: 240 | # info = self.clean_mats_duplication(ob) 241 | 242 | # if self.remove_empty_slots: 243 | # self.delete_empty_material_slots(ob) 244 | 245 | info = replace_increment_duplication(targets=self.target, similar_check=self.skip_different_materials, skip_fake_user=self.skip_fake_user, force_delete=self.force_delete) 246 | self.report({'INFO'}, f'{info} material slot replaced') 247 | 248 | # if info: 249 | # self.report({info[0]}, info[1]) 250 | # else: 251 | # self.report({'WARNING'}, '') 252 | 253 | return {"FINISHED"} 254 | 255 | 256 | def material_clean_menu(self, context): 257 | '''To append to MATERIAL_MT_context_menu''' 258 | layout = self.layout 259 | layout.operator("materials.replace_mat_duplication", text='Remove Duplications', icon='NODE_MATERIAL') 260 | 261 | classes = ( 262 | AM_OT_replace_mat_duplication, 263 | ) 264 | 265 | def register(): 266 | for cls in classes: 267 | bpy.utils.register_class(cls) 268 | 269 | bpy.types.MATERIAL_MT_context_menu.append(material_clean_menu) 270 | 271 | def unregister(): 272 | bpy.types.MATERIAL_MT_context_menu.remove(material_clean_menu) 273 | for cls in reversed(classes): 274 | bpy.utils.unregister_class(cls) -------------------------------------------------------------------------------- /colornames.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "cloudy blue": "#acc2d9", 4 | "dark pastel green": "#56ae57", 5 | "dust": "#b2996e", 6 | "electric lime": "#a8ff04", 7 | "fresh green": "#69d84f", 8 | "light eggplant": "#894585", 9 | "nasty green": "#70b23f", 10 | "really light blue": "#d4ffff", 11 | "tea": "#65ab7c", 12 | "warm purple": "#952e8f", 13 | "yellowish tan": "#fcfc81", 14 | "cement": "#a5a391", 15 | "dark grass green": "#388004", 16 | "dusty teal": "#4c9085", 17 | "grey teal": "#5e9b8a", 18 | "macaroni and cheese": "#efb435", 19 | "pinkish tan": "#d99b82", 20 | "spruce": "#0a5f38", 21 | "strong blue": "#0c06f7", 22 | "toxic green": "#61de2a", 23 | "windows blue": "#3778bf", 24 | "blue blue": "#2242c7", 25 | "blue with a hint of purple": "#533cc6", 26 | "booger": "#9bb53c", 27 | "bright sea green": "#05ffa6", 28 | "dark green blue": "#1f6357", 29 | "deep turquoise": "#017374", 30 | "green teal": "#0cb577", 31 | "strong pink": "#ff0789", 32 | "bland": "#afa88b", 33 | "deep aqua": "#08787f", 34 | "lavender pink": "#dd85d7", 35 | "light moss green": "#a6c875", 36 | "light seafoam green": "#a7ffb5", 37 | "olive yellow": "#c2b709", 38 | "pig pink": "#e78ea5", 39 | "deep lilac": "#966ebd", 40 | "desert": "#ccad60", 41 | "dusty lavender": "#ac86a8", 42 | "purpley grey": "#947e94", 43 | "purply": "#983fb2", 44 | "candy pink": "#ff63e9", 45 | "light pastel green": "#b2fba5", 46 | "boring green": "#63b365", 47 | "kiwi green": "#8ee53f", 48 | "light grey green": "#b7e1a1", 49 | "orange pink": "#ff6f52", 50 | "tea green": "#bdf8a3", 51 | "very light brown": "#d3b683", 52 | "egg shell": "#fffcc4", 53 | "eggplant purple": "#430541", 54 | "powder pink": "#ffb2d0", 55 | "reddish grey": "#997570", 56 | "baby shit brown": "#ad900d", 57 | "liliac": "#c48efd", 58 | "stormy blue": "#507b9c", 59 | "ugly brown": "#7d7103", 60 | "custard": "#fffd78", 61 | "darkish pink": "#da467d", 62 | "deep brown": "#410200", 63 | "greenish beige": "#c9d179", 64 | "manilla": "#fffa86", 65 | "off blue": "#5684ae", 66 | "battleship grey": "#6b7c85", 67 | "browny green": "#6f6c0a", 68 | "bruise": "#7e4071", 69 | "kelley green": "#009337", 70 | "sickly yellow": "#d0e429", 71 | "sunny yellow": "#fff917", 72 | "azul": "#1d5dec", 73 | "darkgreen": "#054907", 74 | "green/yellow": "#b5ce08", 75 | "lichen": "#8fb67b", 76 | "light light green": "#c8ffb0", 77 | "pale gold": "#fdde6c", 78 | "sun yellow": "#ffdf22", 79 | "tan green": "#a9be70", 80 | "burple": "#6832e3", 81 | "butterscotch": "#fdb147", 82 | "toupe": "#c7ac7d", 83 | "dark cream": "#fff39a", 84 | "indian red": "#850e04", 85 | "light lavendar": "#efc0fe", 86 | "poison green": "#40fd14", 87 | "baby puke green": "#b6c406", 88 | "bright yellow green": "#9dff00", 89 | "charcoal grey": "#3c4142", 90 | "squash": "#f2ab15", 91 | "cinnamon": "#ac4f06", 92 | "light pea green": "#c4fe82", 93 | "radioactive green": "#2cfa1f", 94 | "raw sienna": "#9a6200", 95 | "baby purple": "#ca9bf7", 96 | "cocoa": "#875f42", 97 | "light royal blue": "#3a2efe", 98 | "orangeish": "#fd8d49", 99 | "rust brown": "#8b3103", 100 | "sand brown": "#cba560", 101 | "swamp": "#698339", 102 | "tealish green": "#0cdc73", 103 | "burnt siena": "#b75203", 104 | "camo": "#7f8f4e", 105 | "dusk blue": "#26538d", 106 | "fern": "#63a950", 107 | "old rose": "#c87f89", 108 | "pale light green": "#b1fc99", 109 | "peachy pink": "#ff9a8a", 110 | "rosy pink": "#f6688e", 111 | "light bluish green": "#76fda8", 112 | "light bright green": "#53fe5c", 113 | "light neon green": "#4efd54", 114 | "light seafoam": "#a0febf", 115 | "tiffany blue": "#7bf2da", 116 | "washed out green": "#bcf5a6", 117 | "browny orange": "#ca6b02", 118 | "nice blue": "#107ab0", 119 | "sapphire": "#2138ab", 120 | "greyish teal": "#719f91", 121 | "orangey yellow": "#fdb915", 122 | "parchment": "#fefcaf", 123 | "straw": "#fcf679", 124 | "very dark brown": "#1d0200", 125 | "terracota": "#cb6843", 126 | "ugly blue": "#31668a", 127 | "clear blue": "#247afd", 128 | "creme": "#ffffb6", 129 | "foam green": "#90fda9", 130 | "grey/green": "#86a17d", 131 | "light gold": "#fddc5c", 132 | "seafoam blue": "#78d1b6", 133 | "topaz": "#13bbaf", 134 | "violet pink": "#fb5ffc", 135 | "wintergreen": "#20f986", 136 | "yellow tan": "#ffe36e", 137 | "dark fuchsia": "#9d0759", 138 | "indigo blue": "#3a18b1", 139 | "light yellowish green": "#c2ff89", 140 | "pale magenta": "#d767ad", 141 | "rich purple": "#720058", 142 | "sunflower yellow": "#ffda03", 143 | "green/blue": "#01c08d", 144 | "leather": "#ac7434", 145 | "racing green": "#014600", 146 | "vivid purple": "#9900fa", 147 | "dark royal blue": "#02066f", 148 | "hazel": "#8e7618", 149 | "muted pink": "#d1768f", 150 | "booger green": "#96b403", 151 | "canary": "#fdff63", 152 | "cool grey": "#95a3a6", 153 | "dark taupe": "#7f684e", 154 | "darkish purple": "#751973", 155 | "true green": "#089404", 156 | "coral pink": "#ff6163", 157 | "dark sage": "#598556", 158 | "dark slate blue": "#214761", 159 | "flat blue": "#3c73a8", 160 | "mushroom": "#ba9e88", 161 | "rich blue": "#021bf9", 162 | "dirty purple": "#734a65", 163 | "greenblue": "#23c48b", 164 | "icky green": "#8fae22", 165 | "light khaki": "#e6f2a2", 166 | "warm blue": "#4b57db", 167 | "dark hot pink": "#d90166", 168 | "deep sea blue": "#015482", 169 | "carmine": "#9d0216", 170 | "dark yellow green": "#728f02", 171 | "pale peach": "#ffe5ad", 172 | "plum purple": "#4e0550", 173 | "golden rod": "#f9bc08", 174 | "neon red": "#ff073a", 175 | "old pink": "#c77986", 176 | "very pale blue": "#d6fffe", 177 | "blood orange": "#fe4b03", 178 | "grapefruit": "#fd5956", 179 | "sand yellow": "#fce166", 180 | "clay brown": "#b2713d", 181 | "dark blue grey": "#1f3b4d", 182 | "flat green": "#699d4c", 183 | "light green blue": "#56fca2", 184 | "warm pink": "#fb5581", 185 | "dodger blue": "#3e82fc", 186 | "gross green": "#a0bf16", 187 | "ice": "#d6fffa", 188 | "metallic blue": "#4f738e", 189 | "pale salmon": "#ffb19a", 190 | "sap green": "#5c8b15", 191 | "algae": "#54ac68", 192 | "bluey grey": "#89a0b0", 193 | "greeny grey": "#7ea07a", 194 | "highlighter green": "#1bfc06", 195 | "light light blue": "#cafffb", 196 | "light mint": "#b6ffbb", 197 | "raw umber": "#a75e09", 198 | "vivid blue": "#152eff", 199 | "deep lavender": "#8d5eb7", 200 | "dull teal": "#5f9e8f", 201 | "light greenish blue": "#63f7b4", 202 | "mud green": "#606602", 203 | "pinky": "#fc86aa", 204 | "red wine": "#8c0034", 205 | "shit green": "#758000", 206 | "tan brown": "#ab7e4c", 207 | "darkblue": "#030764", 208 | "rosa": "#fe86a4", 209 | "lipstick": "#d5174e", 210 | "pale mauve": "#fed0fc", 211 | "claret": "#680018", 212 | "dandelion": "#fedf08", 213 | "orangered": "#fe420f", 214 | "poop green": "#6f7c00", 215 | "ruby": "#ca0147", 216 | "dark": "#1b2431", 217 | "greenish turquoise": "#00fbb0", 218 | "pastel red": "#db5856", 219 | "piss yellow": "#ddd618", 220 | "bright cyan": "#41fdfe", 221 | "dark coral": "#cf524e", 222 | "algae green": "#21c36f", 223 | "darkish red": "#a90308", 224 | "reddy brown": "#6e1005", 225 | "blush pink": "#fe828c", 226 | "camouflage green": "#4b6113", 227 | "lawn green": "#4da409", 228 | "putty": "#beae8a", 229 | "vibrant blue": "#0339f8", 230 | "dark sand": "#a88f59", 231 | "purple/blue": "#5d21d0", 232 | "saffron": "#feb209", 233 | "twilight": "#4e518b", 234 | "warm brown": "#964e02", 235 | "bluegrey": "#85a3b2", 236 | "bubble gum pink": "#ff69af", 237 | "duck egg blue": "#c3fbf4", 238 | "greenish cyan": "#2afeb7", 239 | "petrol": "#005f6a", 240 | "royal": "#0c1793", 241 | "butter": "#ffff81", 242 | "dusty orange": "#f0833a", 243 | "off yellow": "#f1f33f", 244 | "pale olive green": "#b1d27b", 245 | "orangish": "#fc824a", 246 | "leaf": "#71aa34", 247 | "light blue grey": "#b7c9e2", 248 | "dried blood": "#4b0101", 249 | "lightish purple": "#a552e6", 250 | "rusty red": "#af2f0d", 251 | "lavender blue": "#8b88f8", 252 | "light grass green": "#9af764", 253 | "light mint green": "#a6fbb2", 254 | "sunflower": "#ffc512", 255 | "velvet": "#750851", 256 | "brick orange": "#c14a09", 257 | "lightish red": "#fe2f4a", 258 | "pure blue": "#0203e2", 259 | "twilight blue": "#0a437a", 260 | "violet red": "#a50055", 261 | "yellowy brown": "#ae8b0c", 262 | "carnation": "#fd798f", 263 | "muddy yellow": "#bfac05", 264 | "dark seafoam green": "#3eaf76", 265 | "deep rose": "#c74767", 266 | "dusty red": "#b9484e", 267 | "grey/blue": "#647d8e", 268 | "lemon lime": "#bffe28", 269 | "purple/pink": "#d725de", 270 | "brown yellow": "#b29705", 271 | "purple brown": "#673a3f", 272 | "wisteria": "#a87dc2", 273 | "banana yellow": "#fafe4b", 274 | "lipstick red": "#c0022f", 275 | "water blue": "#0e87cc", 276 | "brown grey": "#8d8468", 277 | "vibrant purple": "#ad03de", 278 | "baby green": "#8cff9e", 279 | "barf green": "#94ac02", 280 | "eggshell blue": "#c4fff7", 281 | "sandy yellow": "#fdee73", 282 | "cool green": "#33b864", 283 | "pale": "#fff9d0", 284 | "blue/grey": "#758da3", 285 | "hot magenta": "#f504c9", 286 | "greyblue": "#77a1b5", 287 | "purpley": "#8756e4", 288 | "baby shit green": "#889717", 289 | "brownish pink": "#c27e79", 290 | "dark aquamarine": "#017371", 291 | "diarrhea": "#9f8303", 292 | "light mustard": "#f7d560", 293 | "pale sky blue": "#bdf6fe", 294 | "turtle green": "#75b84f", 295 | "bright olive": "#9cbb04", 296 | "dark grey blue": "#29465b", 297 | "greeny brown": "#696006", 298 | "lemon green": "#adf802", 299 | "light periwinkle": "#c1c6fc", 300 | "seaweed green": "#35ad6b", 301 | "sunshine yellow": "#fffd37", 302 | "ugly purple": "#a442a0", 303 | "medium pink": "#f36196", 304 | "puke brown": "#947706", 305 | "very light pink": "#fff4f2", 306 | "viridian": "#1e9167", 307 | "bile": "#b5c306", 308 | "faded yellow": "#feff7f", 309 | "very pale green": "#cffdbc", 310 | "vibrant green": "#0add08", 311 | "bright lime": "#87fd05", 312 | "spearmint": "#1ef876", 313 | "light aquamarine": "#7bfdc7", 314 | "light sage": "#bcecac", 315 | "yellowgreen": "#bbf90f", 316 | "baby poo": "#ab9004", 317 | "dark seafoam": "#1fb57a", 318 | "deep teal": "#00555a", 319 | "heather": "#a484ac", 320 | "rust orange": "#c45508", 321 | "dirty blue": "#3f829d", 322 | "fern green": "#548d44", 323 | "bright lilac": "#c95efb", 324 | "weird green": "#3ae57f", 325 | "peacock blue": "#016795", 326 | "avocado green": "#87a922", 327 | "faded orange": "#f0944d", 328 | "grape purple": "#5d1451", 329 | "hot green": "#25ff29", 330 | "lime yellow": "#d0fe1d", 331 | "mango": "#ffa62b", 332 | "shamrock": "#01b44c", 333 | "bubblegum": "#ff6cb5", 334 | "purplish brown": "#6b4247", 335 | "vomit yellow": "#c7c10c", 336 | "pale cyan": "#b7fffa", 337 | "key lime": "#aeff6e", 338 | "tomato red": "#ec2d01", 339 | "lightgreen": "#76ff7b", 340 | "merlot": "#730039", 341 | "night blue": "#040348", 342 | "purpleish pink": "#df4ec8", 343 | "apple": "#6ecb3c", 344 | "baby poop green": "#8f9805", 345 | "green apple": "#5edc1f", 346 | "heliotrope": "#d94ff5", 347 | "yellow/green": "#c8fd3d", 348 | "almost black": "#070d0d", 349 | "cool blue": "#4984b8", 350 | "leafy green": "#51b73b", 351 | "mustard brown": "#ac7e04", 352 | "dusk": "#4e5481", 353 | "dull brown": "#876e4b", 354 | "frog green": "#58bc08", 355 | "vivid green": "#2fef10", 356 | "bright light green": "#2dfe54", 357 | "fluro green": "#0aff02", 358 | "kiwi": "#9cef43", 359 | "seaweed": "#18d17b", 360 | "navy green": "#35530a", 361 | "ultramarine blue": "#1805db", 362 | "iris": "#6258c4", 363 | "pastel orange": "#ff964f", 364 | "yellowish orange": "#ffab0f", 365 | "perrywinkle": "#8f8ce7", 366 | "tealish": "#24bca8", 367 | "dark plum": "#3f012c", 368 | "pear": "#cbf85f", 369 | "pinkish orange": "#ff724c", 370 | "midnight purple": "#280137", 371 | "light urple": "#b36ff6", 372 | "dark mint": "#48c072", 373 | "greenish tan": "#bccb7a", 374 | "light burgundy": "#a8415b", 375 | "turquoise blue": "#06b1c4", 376 | "ugly pink": "#cd7584", 377 | "sandy": "#f1da7a", 378 | "electric pink": "#ff0490", 379 | "muted purple": "#805b87", 380 | "mid green": "#50a747", 381 | "greyish": "#a8a495", 382 | "neon yellow": "#cfff04", 383 | "banana": "#ffff7e", 384 | "carnation pink": "#ff7fa7", 385 | "tomato": "#ef4026", 386 | "sea": "#3c9992", 387 | "muddy brown": "#886806", 388 | "turquoise green": "#04f489", 389 | "buff": "#fef69e", 390 | "fawn": "#cfaf7b", 391 | "muted blue": "#3b719f", 392 | "pale rose": "#fdc1c5", 393 | "dark mint green": "#20c073", 394 | "amethyst": "#9b5fc0", 395 | "blue/green": "#0f9b8e", 396 | "chestnut": "#742802", 397 | "sick green": "#9db92c", 398 | "pea": "#a4bf20", 399 | "rusty orange": "#cd5909", 400 | "stone": "#ada587", 401 | "rose red": "#be013c", 402 | "pale aqua": "#b8ffeb", 403 | "deep orange": "#dc4d01", 404 | "earth": "#a2653e", 405 | "mossy green": "#638b27", 406 | "grassy green": "#419c03", 407 | "pale lime green": "#b1ff65", 408 | "light grey blue": "#9dbcd4", 409 | "pale grey": "#fdfdfe", 410 | "asparagus": "#77ab56", 411 | "blueberry": "#464196", 412 | "purple red": "#990147", 413 | "pale lime": "#befd73", 414 | "greenish teal": "#32bf84", 415 | "caramel": "#af6f09", 416 | "deep magenta": "#a0025c", 417 | "light peach": "#ffd8b1", 418 | "milk chocolate": "#7f4e1e", 419 | "ocher": "#bf9b0c", 420 | "off green": "#6ba353", 421 | "purply pink": "#f075e6", 422 | "lightblue": "#7bc8f6", 423 | "dusky blue": "#475f94", 424 | "golden": "#f5bf03", 425 | "light beige": "#fffeb6", 426 | "butter yellow": "#fffd74", 427 | "dusky purple": "#895b7b", 428 | "french blue": "#436bad", 429 | "ugly yellow": "#d0c101", 430 | "greeny yellow": "#c6f808", 431 | "orangish red": "#f43605", 432 | "shamrock green": "#02c14d", 433 | "orangish brown": "#b25f03", 434 | "tree green": "#2a7e19", 435 | "deep violet": "#490648", 436 | "gunmetal": "#536267", 437 | "blue/purple": "#5a06ef", 438 | "cherry": "#cf0234", 439 | "sandy brown": "#c4a661", 440 | "warm grey": "#978a84", 441 | "dark indigo": "#1f0954", 442 | "midnight": "#03012d", 443 | "bluey green": "#2bb179", 444 | "grey pink": "#c3909b", 445 | "soft purple": "#a66fb5", 446 | "blood": "#770001", 447 | "brown red": "#922b05", 448 | "medium grey": "#7d7f7c", 449 | "berry": "#990f4b", 450 | "poo": "#8f7303", 451 | "purpley pink": "#c83cb9", 452 | "light salmon": "#fea993", 453 | "snot": "#acbb0d", 454 | "easter purple": "#c071fe", 455 | "light yellow green": "#ccfd7f", 456 | "dark navy blue": "#00022e", 457 | "drab": "#828344", 458 | "light rose": "#ffc5cb", 459 | "rouge": "#ab1239", 460 | "purplish red": "#b0054b", 461 | "slime green": "#99cc04", 462 | "baby poop": "#937c00", 463 | "irish green": "#019529", 464 | "pink/purple": "#ef1de7", 465 | "dark navy": "#000435", 466 | "greeny blue": "#42b395", 467 | "light plum": "#9d5783", 468 | "pinkish grey": "#c8aca9", 469 | "dirty orange": "#c87606", 470 | "rust red": "#aa2704", 471 | "pale lilac": "#e4cbff", 472 | "orangey red": "#fa4224", 473 | "primary blue": "#0804f9", 474 | "kermit green": "#5cb200", 475 | "brownish purple": "#76424e", 476 | "murky green": "#6c7a0e", 477 | "wheat": "#fbdd7e", 478 | "very dark purple": "#2a0134", 479 | "bottle green": "#044a05", 480 | "watermelon": "#fd4659", 481 | "deep sky blue": "#0d75f8", 482 | "fire engine red": "#fe0002", 483 | "yellow ochre": "#cb9d06", 484 | "pumpkin orange": "#fb7d07", 485 | "pale olive": "#b9cc81", 486 | "light lilac": "#edc8ff", 487 | "lightish green": "#61e160", 488 | "carolina blue": "#8ab8fe", 489 | "mulberry": "#920a4e", 490 | "shocking pink": "#fe02a2", 491 | "auburn": "#9a3001", 492 | "bright lime green": "#65fe08", 493 | "celadon": "#befdb7", 494 | "pinkish brown": "#b17261", 495 | "poo brown": "#885f01", 496 | "bright sky blue": "#02ccfe", 497 | "celery": "#c1fd95", 498 | "dirt brown": "#836539", 499 | "strawberry": "#fb2943", 500 | "dark lime": "#84b701", 501 | "copper": "#b66325", 502 | "medium brown": "#7f5112", 503 | "muted green": "#5fa052", 504 | "robin's egg": "#6dedfd", 505 | "bright aqua": "#0bf9ea", 506 | "bright lavender": "#c760ff", 507 | "ivory": "#ffffcb", 508 | "very light purple": "#f6cefc", 509 | "light navy": "#155084", 510 | "pink red": "#f5054f", 511 | "olive brown": "#645403", 512 | "poop brown": "#7a5901", 513 | "mustard green": "#a8b504", 514 | "ocean green": "#3d9973", 515 | "very dark blue": "#000133", 516 | "dusty green": "#76a973", 517 | "light navy blue": "#2e5a88", 518 | "minty green": "#0bf77d", 519 | "adobe": "#bd6c48", 520 | "barney": "#ac1db8", 521 | "jade green": "#2baf6a", 522 | "bright light blue": "#26f7fd", 523 | "light lime": "#aefd6c", 524 | "dark khaki": "#9b8f55", 525 | "orange yellow": "#ffad01", 526 | "ocre": "#c69c04", 527 | "maize": "#f4d054", 528 | "faded pink": "#de9dac", 529 | "british racing green": "#05480d", 530 | "sandstone": "#c9ae74", 531 | "mud brown": "#60460f", 532 | "light sea green": "#98f6b0", 533 | "robin egg blue": "#8af1fe", 534 | "aqua marine": "#2ee8bb", 535 | "dark sea green": "#11875d", 536 | "soft pink": "#fdb0c0", 537 | "orangey brown": "#b16002", 538 | "cherry red": "#f7022a", 539 | "burnt yellow": "#d5ab09", 540 | "brownish grey": "#86775f", 541 | "camel": "#c69f59", 542 | "purplish grey": "#7a687f", 543 | "marine": "#042e60", 544 | "greyish pink": "#c88d94", 545 | "pale turquoise": "#a5fbd5", 546 | "pastel yellow": "#fffe71", 547 | "bluey purple": "#6241c7", 548 | "canary yellow": "#fffe40", 549 | "faded red": "#d3494e", 550 | "sepia": "#985e2b", 551 | "coffee": "#a6814c", 552 | "bright magenta": "#ff08e8", 553 | "mocha": "#9d7651", 554 | "ecru": "#feffca", 555 | "purpleish": "#98568d", 556 | "cranberry": "#9e003a", 557 | "darkish green": "#287c37", 558 | "brown orange": "#b96902", 559 | "dusky rose": "#ba6873", 560 | "melon": "#ff7855", 561 | "sickly green": "#94b21c", 562 | "silver": "#c5c9c7", 563 | "purply blue": "#661aee", 564 | "purpleish blue": "#6140ef", 565 | "hospital green": "#9be5aa", 566 | "shit brown": "#7b5804", 567 | "mid blue": "#276ab3", 568 | "amber": "#feb308", 569 | "easter green": "#8cfd7e", 570 | "soft blue": "#6488ea", 571 | "cerulean blue": "#056eee", 572 | "golden brown": "#b27a01", 573 | "bright turquoise": "#0ffef9", 574 | "red pink": "#fa2a55", 575 | "red purple": "#820747", 576 | "greyish brown": "#7a6a4f", 577 | "vermillion": "#f4320c", 578 | "russet": "#a13905", 579 | "steel grey": "#6f828a", 580 | "lighter purple": "#a55af4", 581 | "bright violet": "#ad0afd", 582 | "prussian blue": "#004577", 583 | "slate green": "#658d6d", 584 | "dirty pink": "#ca7b80", 585 | "dark blue green": "#005249", 586 | "pine": "#2b5d34", 587 | "yellowy green": "#bff128", 588 | "dark gold": "#b59410", 589 | "bluish": "#2976bb", 590 | "darkish blue": "#014182", 591 | "dull red": "#bb3f3f", 592 | "pinky red": "#fc2647", 593 | "bronze": "#a87900", 594 | "pale teal": "#82cbb2", 595 | "military green": "#667c3e", 596 | "barbie pink": "#fe46a5", 597 | "bubblegum pink": "#fe83cc", 598 | "pea soup green": "#94a617", 599 | "dark mustard": "#a88905", 600 | "shit": "#7f5f00", 601 | "medium purple": "#9e43a2", 602 | "very dark green": "#062e03", 603 | "dirt": "#8a6e45", 604 | "dusky pink": "#cc7a8b", 605 | "red violet": "#9e0168", 606 | "lemon yellow": "#fdff38", 607 | "pistachio": "#c0fa8b", 608 | "dull yellow": "#eedc5b", 609 | "dark lime green": "#7ebd01", 610 | "denim blue": "#3b5b92", 611 | "teal blue": "#01889f", 612 | "lightish blue": "#3d7afd", 613 | "purpley blue": "#5f34e7", 614 | "light indigo": "#6d5acf", 615 | "swamp green": "#748500", 616 | "brown green": "#706c11", 617 | "dark maroon": "#3c0008", 618 | "hot purple": "#cb00f5", 619 | "dark forest green": "#002d04", 620 | "faded blue": "#658cbb", 621 | "drab green": "#749551", 622 | "light lime green": "#b9ff66", 623 | "snot green": "#9dc100", 624 | "yellowish": "#faee66", 625 | "light blue green": "#7efbb3", 626 | "bordeaux": "#7b002c", 627 | "light mauve": "#c292a1", 628 | "ocean": "#017b92", 629 | "marigold": "#fcc006", 630 | "muddy green": "#657432", 631 | "dull orange": "#d8863b", 632 | "steel": "#738595", 633 | "electric purple": "#aa23ff", 634 | "fluorescent green": "#08ff08", 635 | "yellowish brown": "#9b7a01", 636 | "blush": "#f29e8e", 637 | "soft green": "#6fc276", 638 | "bright orange": "#ff5b00", 639 | "lemon": "#fdff52", 640 | "purple grey": "#866f85", 641 | "acid green": "#8ffe09", 642 | "pale lavender": "#eecffe", 643 | "violet blue": "#510ac9", 644 | "light forest green": "#4f9153", 645 | "burnt red": "#9f2305", 646 | "khaki green": "#728639", 647 | "cerise": "#de0c62", 648 | "faded purple": "#916e99", 649 | "apricot": "#ffb16d", 650 | "dark olive green": "#3c4d03", 651 | "grey brown": "#7f7053", 652 | "green grey": "#77926f", 653 | "true blue": "#010fcc", 654 | "pale violet": "#ceaefa", 655 | "periwinkle blue": "#8f99fb", 656 | "light sky blue": "#c6fcff", 657 | "blurple": "#5539cc", 658 | "green brown": "#544e03", 659 | "bluegreen": "#017a79", 660 | "bright teal": "#01f9c6", 661 | "brownish yellow": "#c9b003", 662 | "pea soup": "#929901", 663 | "forest": "#0b5509", 664 | "barney purple": "#a00498", 665 | "ultramarine": "#2000b1", 666 | "purplish": "#94568c", 667 | "puke yellow": "#c2be0e", 668 | "bluish grey": "#748b97", 669 | "dark periwinkle": "#665fd1", 670 | "dark lilac": "#9c6da5", 671 | "reddish": "#c44240", 672 | "light maroon": "#a24857", 673 | "dusty purple": "#825f87", 674 | "terra cotta": "#c9643b", 675 | "avocado": "#90b134", 676 | "marine blue": "#01386a", 677 | "teal green": "#25a36f", 678 | "slate grey": "#59656d", 679 | "lighter green": "#75fd63", 680 | "electric green": "#21fc0d", 681 | "dusty blue": "#5a86ad", 682 | "golden yellow": "#fec615", 683 | "bright yellow": "#fffd01", 684 | "light lavender": "#dfc5fe", 685 | "umber": "#b26400", 686 | "poop": "#7f5e00", 687 | "dark peach": "#de7e5d", 688 | "jungle green": "#048243", 689 | "eggshell": "#ffffd4", 690 | "denim": "#3b638c", 691 | "yellow brown": "#b79400", 692 | "dull purple": "#84597e", 693 | "chocolate brown": "#411900", 694 | "wine red": "#7b0323", 695 | "neon blue": "#04d9ff", 696 | "dirty green": "#667e2c", 697 | "light tan": "#fbeeac", 698 | "ice blue": "#d7fffe", 699 | "cadet blue": "#4e7496", 700 | "dark mauve": "#874c62", 701 | "very light blue": "#d5ffff", 702 | "grey purple": "#826d8c", 703 | "pastel pink": "#ffbacd", 704 | "very light green": "#d1ffbd", 705 | "dark sky blue": "#448ee4", 706 | "evergreen": "#05472a", 707 | "dull pink": "#d5869d", 708 | "aubergine": "#3d0734", 709 | "mahogany": "#4a0100", 710 | "reddish orange": "#f8481c", 711 | "deep green": "#02590f", 712 | "vomit green": "#89a203", 713 | "purple pink": "#e03fd8", 714 | "dusty pink": "#d58a94", 715 | "faded green": "#7bb274", 716 | "camo green": "#526525", 717 | "pinky purple": "#c94cbe", 718 | "pink purple": "#db4bda", 719 | "brownish red": "#9e3623", 720 | "dark rose": "#b5485d", 721 | "mud": "#735c12", 722 | "brownish": "#9c6d57", 723 | "emerald green": "#028f1e", 724 | "pale brown": "#b1916e", 725 | "dull blue": "#49759c", 726 | "burnt umber": "#a0450e", 727 | "medium green": "#39ad48", 728 | "clay": "#b66a50", 729 | "light aqua": "#8cffdb", 730 | "light olive green": "#a4be5c", 731 | "brownish orange": "#cb7723", 732 | "dark aqua": "#05696b", 733 | "purplish pink": "#ce5dae", 734 | "dark salmon": "#c85a53", 735 | "greenish grey": "#96ae8d", 736 | "jade": "#1fa774", 737 | "ugly green": "#7a9703", 738 | "dark beige": "#ac9362", 739 | "emerald": "#01a049", 740 | "pale red": "#d9544d", 741 | "light magenta": "#fa5ff7", 742 | "sky": "#82cafc", 743 | "light cyan": "#acfffc", 744 | "yellow orange": "#fcb001", 745 | "reddish purple": "#910951", 746 | "reddish pink": "#fe2c54", 747 | "orchid": "#c875c4", 748 | "dirty yellow": "#cdc50a", 749 | "orange red": "#fd411e", 750 | "deep red": "#9a0200", 751 | "orange brown": "#be6400", 752 | "cobalt blue": "#030aa7", 753 | "neon pink": "#fe019a", 754 | "rose pink": "#f7879a", 755 | "greyish purple": "#887191", 756 | "raspberry": "#b00149", 757 | "aqua green": "#12e193", 758 | "salmon pink": "#fe7b7c", 759 | "tangerine": "#ff9408", 760 | "brownish green": "#6a6e09", 761 | "red brown": "#8b2e16", 762 | "greenish brown": "#696112", 763 | "pumpkin": "#e17701", 764 | "pine green": "#0a481e", 765 | "charcoal": "#343837", 766 | "baby pink": "#ffb7ce", 767 | "cornflower": "#6a79f7", 768 | "blue violet": "#5d06e9", 769 | "chocolate": "#3d1c02", 770 | "greyish green": "#82a67d", 771 | "scarlet": "#be0119", 772 | "green yellow": "#c9ff27", 773 | "dark olive": "#373e02", 774 | "sienna": "#a9561e", 775 | "pastel purple": "#caa0ff", 776 | "terracotta": "#ca6641", 777 | "aqua blue": "#02d8e9", 778 | "sage green": "#88b378", 779 | "blood red": "#980002", 780 | "deep pink": "#cb0162", 781 | "grass": "#5cac2d", 782 | "moss": "#769958", 783 | "pastel blue": "#a2bffe", 784 | "bluish green": "#10a674", 785 | "green blue": "#06b48b", 786 | "dark tan": "#af884a", 787 | "greenish blue": "#0b8b87", 788 | "pale orange": "#ffa756", 789 | "vomit": "#a2a415", 790 | "forrest green": "#154406", 791 | "dark lavender": "#856798", 792 | "dark violet": "#34013f", 793 | "purple blue": "#632de9", 794 | "dark cyan": "#0a888a", 795 | "olive drab": "#6f7632", 796 | "pinkish": "#d46a7e", 797 | "cobalt": "#1e488f", 798 | "neon purple": "#bc13fe", 799 | "light turquoise": "#7ef4cc", 800 | "apple green": "#76cd26", 801 | "dull green": "#74a662", 802 | "wine": "#80013f", 803 | "powder blue": "#b1d1fc", 804 | "off white": "#ffffe4", 805 | "electric blue": "#0652ff", 806 | "dark turquoise": "#045c5a", 807 | "blue purple": "#5729ce", 808 | "azure": "#069af3", 809 | "bright red": "#ff000d", 810 | "pinkish red": "#f10c45", 811 | "cornflower blue": "#5170d7", 812 | "light olive": "#acbf69", 813 | "grape": "#6c3461", 814 | "greyish blue": "#5e819d", 815 | "purplish blue": "#601ef9", 816 | "yellowish green": "#b0dd16", 817 | "greenish yellow": "#cdfd02", 818 | "medium blue": "#2c6fbb", 819 | "dusty rose": "#c0737a", 820 | "light violet": "#d6b4fc", 821 | "midnight blue": "#020035", 822 | "bluish purple": "#703be7", 823 | "red orange": "#fd3c06", 824 | "dark magenta": "#960056", 825 | "greenish": "#40a368", 826 | "ocean blue": "#03719c", 827 | "coral": "#fc5a50", 828 | "cream": "#ffffc2", 829 | "reddish brown": "#7f2b0a", 830 | "burnt sienna": "#b04e0f", 831 | "brick": "#a03623", 832 | "sage": "#87ae73", 833 | "grey green": "#789b73", 834 | "white": "#ffffff", 835 | "robin's egg blue": "#98eff9", 836 | "moss green": "#658b38", 837 | "steel blue": "#5a7d9a", 838 | "eggplant": "#380835", 839 | "light yellow": "#fffe7a", 840 | "leaf green": "#5ca904", 841 | "light grey": "#d8dcd6", 842 | "puke": "#a5a502", 843 | "pinkish purple": "#d648d7", 844 | "sea blue": "#047495", 845 | "pale purple": "#b790d4", 846 | "slate blue": "#5b7c99", 847 | "blue grey": "#607c8e", 848 | "hunter green": "#0b4008", 849 | "fuchsia": "#ed0dd9", 850 | "crimson": "#8c000f", 851 | "pale yellow": "#ffff84", 852 | "ochre": "#bf9005", 853 | "mustard yellow": "#d2bd0a", 854 | "light red": "#ff474c", 855 | "cerulean": "#0485d1", 856 | "pale pink": "#ffcfdc", 857 | "deep blue": "#040273", 858 | "rust": "#a83c09", 859 | "light teal": "#90e4c1", 860 | "slate": "#516572", 861 | "goldenrod": "#fac205", 862 | "dark yellow": "#d5b60a", 863 | "dark grey": "#363737", 864 | "army green": "#4b5d16", 865 | "grey blue": "#6b8ba4", 866 | "seafoam": "#80f9ad", 867 | "puce": "#a57e52", 868 | "spring green": "#a9f971", 869 | "dark orange": "#c65102", 870 | "sand": "#e2ca76", 871 | "pastel green": "#b0ff9d", 872 | "mint": "#9ffeb0", 873 | "light orange": "#fdaa48", 874 | "bright pink": "#fe01b1", 875 | "chartreuse": "#c1f80a", 876 | "deep purple": "#36013f", 877 | "dark brown": "#341c02", 878 | "taupe": "#b9a281", 879 | "pea green": "#8eab12", 880 | "puke green": "#9aae07", 881 | "kelly green": "#02ab2e", 882 | "seafoam green": "#7af9ab", 883 | "blue green": "#137e6d", 884 | "khaki": "#aaa662", 885 | "burgundy": "#610023", 886 | "dark teal": "#014d4e", 887 | "brick red": "#8f1402", 888 | "royal purple": "#4b006e", 889 | "plum": "#580f41", 890 | "mint green": "#8fff9f", 891 | "gold": "#dbb40c", 892 | "baby blue": "#a2cffe", 893 | "yellow green": "#c0fb2d", 894 | "bright purple": "#be03fd", 895 | "dark red": "#840000", 896 | "pale blue": "#d0fefe", 897 | "grass green": "#3f9b0b", 898 | "navy": "#01153e", 899 | "aquamarine": "#04d8b2", 900 | "burnt orange": "#c04e01", 901 | "neon green": "#0cff0c", 902 | "bright blue": "#0165fc", 903 | "rose": "#cf6275", 904 | "light pink": "#ffd1df", 905 | "mustard": "#ceb301", 906 | "indigo": "#380282", 907 | "lime": "#aaff32", 908 | "sea green": "#53fca1", 909 | "periwinkle": "#8e82fe", 910 | "dark pink": "#cb416b", 911 | "olive green": "#677a04", 912 | "peach": "#ffb07c", 913 | "pale green": "#c7fdb5", 914 | "light brown": "#ad8150", 915 | "hot pink": "#ff028d", 916 | "black": "#000000", 917 | "lilac": "#cea2fd", 918 | "navy blue": "#001146", 919 | "royal blue": "#0504aa", 920 | "beige": "#e6daa6", 921 | "salmon": "#ff796c", 922 | "olive": "#6e750e", 923 | "maroon": "#650021", 924 | "bright green": "#01ff07", 925 | "dark purple": "#35063e", 926 | "mauve": "#ae7181", 927 | "forest green": "#06470c", 928 | "aqua": "#13eac9", 929 | "cyan": "#00ffff", 930 | "tan": "#d1b26f", 931 | "dark blue": "#00035b", 932 | "lavender": "#c79fef", 933 | "turquoise": "#06c2ac", 934 | "dark green": "#033500", 935 | "violet": "#9a0eea", 936 | "light purple": "#bf77f6", 937 | "lime green": "#89fe05", 938 | "grey": "#929591", 939 | "sky blue": "#75bbfd", 940 | "yellow": "#ffff14", 941 | "magenta": "#c20078", 942 | "light green": "#96f97b", 943 | "orange": "#f97306", 944 | "teal": "#029386", 945 | "light blue": "#95d0fc", 946 | "red": "#e50000", 947 | "brown": "#653700", 948 | "pink": "#ff81c0", 949 | "blue": "#0343df", 950 | "green": "#15b01a", 951 | "purple": "#7e1e9c"} -------------------------------------------------------------------------------- /fn.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def report(*args, self=None, mode='INFO'): 4 | #mode in 'INFO' (default), 'WARNING', 'ERROR' 5 | text = ' '.join([str(a) for a in args]) 6 | if self: 7 | self.report({mode}, text) 8 | else: 9 | print(text) 10 | 11 | def get_addon_prefs(): 12 | import os 13 | addon_name = os.path.splitext(__name__)[0] 14 | preferences = bpy.context.preferences 15 | addon_prefs = preferences.addons[addon_name].preferences 16 | return (addon_prefs) 17 | 18 | ### --- Object Color <-> Material Color 19 | 20 | #-# seem like it's needed to be passed to linear in blender for correct result 21 | #-# https://blender.stackexchange.com/questions/158896/how-set-hex-in-rgb-node-python/158902 22 | 23 | def srgb_to_linearrgb(c): 24 | if c < 0: return 0 25 | elif c < 0.04045: return c/12.92 26 | else: return ((c+0.055)/1.055)**2.4 27 | 28 | def hex_to_rgb(h): 29 | '''Convert a hexadecimal color value to a 3-tuple of integers 30 | suitable for use in an ``rgb()`` triplet specifying that color. 31 | ''' 32 | 33 | h=int(h[1:], 16) 34 | r = (h & 0xff0000) >> 16 35 | g = (h & 0x00ff00) >> 8 36 | b = (h & 0x0000ff) 37 | return tuple([srgb_to_linearrgb(c/0xff) for c in (r,g,b)]) 38 | 39 | 40 | def get_color_name(rgb, color_dict): 41 | '''Get a rgb[a] (tuple/list) or an hex (str) 42 | return nearest color name found in passed color database 43 | ''' 44 | if isinstance(rgb, str): # must be an hexa code 45 | rgb = rgb if rgb.clip.startswith('#') else '#'+rgb 46 | rgb = hex_to_rgb(rgb) 47 | 48 | min_colours = {} 49 | for name, hex in color_dict.items(): 50 | r_c, g_c, b_c = hex_to_rgb(hex)#.strip('#') 51 | rd = (r_c - rgb[0]) ** 2 52 | gd = (g_c - rgb[1]) ** 2 53 | bd = (b_c - rgb[2]) ** 2 54 | min_colours[(rd + gd + bd)] = name 55 | closest_name = min_colours[min(min_colours.keys())] 56 | 57 | return closest_name 58 | 59 | def load_color_dic(fp): 60 | import json 61 | from pathlib import Path 62 | color_dict = None 63 | color_db = Path(fp) 64 | with color_db.open() as fd: 65 | color_dict = json.load(fd) 66 | return color_dict 67 | 68 | 69 | 70 | ### --- Object Color <-> Material Color 71 | 72 | 73 | def get_single_pixel_color_from_image(img): 74 | ''' 75 | :img: A blender type image 76 | Sample color of center pixel 77 | ''' 78 | 79 | # do a mean of every color (not so usefull...) ? 80 | if img.type != 'IMAGE': 81 | print (f'{img.name}, not an image type >> {img.type}') 82 | return 83 | 84 | pix_num = len(img.pixels) 85 | width = img.size[0] 86 | height = img.size[1] 87 | 88 | target = [width//2, height//2] #sample middle pixel 89 | # target = [0,0] # sample first pixel 90 | 91 | index = ( target[1] * width + target[0] ) * img.channels 92 | print('index: ', index) 93 | 94 | rgb = (img.pixels[index], 95 | img.pixels[index + 1], 96 | img.pixels[index + 2], 1.0) 97 | 98 | print(rgb) 99 | return rgb 100 | 101 | 102 | color_node_exclude = ('MIX_RGB', 'TEX_GRADIENT', 'BRIGHTCONTRAST', 'CURVE_RGB', 'VALTORGB') 103 | 104 | 105 | def find_color_up_tree(node): 106 | '''Recursive find color in node_tree''' 107 | # print(node.type)#Dbg 108 | 109 | # Ouputs 110 | if node.outputs.get('Color') and node.type not in color_node_exclude: 111 | # if has an out (rgb nodes or ramp, etc, take this first) 112 | if node.type == 'TEX_IMAGE' and node.image and node.image.type == 'IMAGE': 113 | # if is a texture, sample center pixel color and return 114 | return get_single_pixel_color_from_image(node.image) 115 | return node.outputs['Color'].default_value[:] 116 | 117 | # Inputs (Try to get colors) 118 | colsocket = node.inputs.get('Base Color') 119 | if not colsocket: 120 | colsocket = node.inputs.get('Color') 121 | 122 | if colsocket: 123 | if colsocket.is_linked: 124 | #print('has linked color socket')#Dbg 125 | for link in colsocket.links: 126 | color = find_color_up_tree(link.from_node) 127 | if color: 128 | return color 129 | else: 130 | #print('unlinked input color socket ', colsocket.default_value[:])#Dbg 131 | return colsocket.default_value[:] 132 | 133 | # else search for a color source in links of all other socket 134 | else: 135 | for input in node.inputs: 136 | if input.is_linked: 137 | for link in input.links: 138 | color = find_color_up_tree(link.from_node) 139 | if color: 140 | #print('Color found !', color) 141 | return color 142 | 143 | def get_closest_node_color(mat): 144 | if not mat.use_nodes: 145 | print('material is not node based') 146 | return 147 | #get output node 148 | nodes = mat.node_tree.nodes 149 | out = None 150 | for n in nodes: 151 | if n.type == 'OUTPUT_MATERIAL': 152 | out = n 153 | break 154 | if not out: 155 | return 156 | 157 | if not out.inputs['Surface'].is_linked: 158 | return 159 | 160 | #go up in the tree until color found (get the 'last' color found ? or color of first node ?) 161 | return find_color_up_tree(out) 162 | 163 | """# old selection scope 164 | def material_selection_scope(): 165 | '''Return a list of all material to change''' 166 | scene = bpy.context.scene 167 | if scene.mat_change_multiple: 168 | matlist=[] 169 | for ob in bpy.context.selected_objects: 170 | for slot in ob.material_slots: 171 | mat = slot.material 172 | if mat: 173 | if mat not in matlist: 174 | matlist.append(mat) 175 | print('material list', matlist) 176 | return matlist 177 | else: 178 | ob = bpy.context.active_object 179 | if ob: 180 | mat = ob.active_material 181 | if mat: 182 | return [mat] 183 | else: 184 | print('no active object') 185 | """ 186 | 187 | def material_selection_scope(): 188 | '''Return a list of all material to change''' 189 | scene = bpy.context.scene 190 | 191 | prefs = get_addon_prefs() 192 | matlist=[] 193 | 194 | # On active and selected objects 195 | active = bpy.context.object 196 | pool = [o for o in bpy.context.selected_objects] 197 | if active and active not in pool: 198 | pool = [active] + pool 199 | 200 | for ob in pool: 201 | if scene.mat_change_multiple: 202 | for slot in ob.material_slots: 203 | mat = slot.material 204 | if mat and mat not in matlist: 205 | # filter to only affect unnamed material 206 | if prefs.only_unnamed and not mat.name.startswith('Material'): 207 | continue 208 | matlist.append(mat) 209 | else: 210 | mat = ob.active_material 211 | if mat and mat not in matlist: 212 | matlist.append(mat) 213 | 214 | return matlist 215 | 216 | 217 | def match_color_viewport_from_node(variables={}): 218 | self = variables.get('self') 219 | for mat in material_selection_scope(): 220 | color = get_closest_node_color(mat) 221 | 222 | if color: 223 | # print("color", color)#Dbg 224 | mat.diffuse_color = color[:]#-1 225 | else: 226 | report('cant find color in the node tree...', self=self, mode='ERROR') 227 | 228 | 229 | def set_closest_node_color(mat, color): 230 | '''Change only fisrt connected node (not recursive)''' 231 | if not mat.use_nodes: 232 | print('material is not node based') 233 | return 234 | #get output node 235 | nodes = mat.node_tree.nodes 236 | out = None 237 | for n in nodes: 238 | if n.type == 'OUTPUT_MATERIAL': 239 | out = n 240 | break 241 | if not out: 242 | return 243 | 244 | if not out.inputs['Surface'].is_linked: 245 | return 246 | 247 | node = out.inputs['Surface'].links[0].from_node 248 | colsocket = node.inputs.get('Base Color') 249 | if not colsocket: 250 | colsocket = node.inputs.get('Color') 251 | if colsocket: 252 | colsocket.default_value = color 253 | return node 254 | 255 | def match_color_node_from_viewport(variables={}): 256 | self = variables.get('self') 257 | matlist = material_selection_scope() 258 | for i, mat in enumerate(matlist): 259 | # print( mat.name, i, '/', len(matlist) ) 260 | color = mat.diffuse_color 261 | if color: 262 | color = list(color[:-1]) + [1.0] 263 | # print("color", color)#Dbg 264 | node = set_closest_node_color(mat,color) 265 | if not node: 266 | report('cant find a node to set color in the node tree... (this try only with the first node connected to "Surface")', self=self, mode='ERROR') 267 | continue 268 | report('color changed in node', node.name, self=self) 269 | #return True -------------------------------------------------------------------------------- /set_color.py: -------------------------------------------------------------------------------- 1 | from . import fn 2 | import bpy 3 | 4 | class AM_OT_viewport_color_from_node(bpy.types.Operator): 5 | bl_idname = "materials.viewport_color_from_node" 6 | bl_label = "Material viewport color from node" 7 | bl_description = "Set the viewport color of active material with color found in node" 8 | bl_options = {"REGISTER"} 9 | 10 | from_viewport: bpy.props.BoolProperty() 11 | @classmethod 12 | def poll(cls, context): 13 | return True 14 | 15 | def execute(self, context): 16 | if self.from_viewport: 17 | fn.match_color_node_from_viewport(variables={'self':self, 'context':context}) 18 | else: 19 | fn.match_color_viewport_from_node(variables={'self':self, 'context':context}) 20 | return {"FINISHED"} 21 | 22 | def register(): 23 | bpy.utils.register_class(AM_OT_viewport_color_from_node) 24 | 25 | def unregister(): 26 | bpy.utils.unregister_class(AM_OT_viewport_color_from_node) 27 | 28 | -------------------------------------------------------------------------------- /ui.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | def Material_handle_Panel(self, context): 4 | """options for fast naming and setting materials color""" 5 | layout = self.layout 6 | # layout.use_property_split = True 7 | layout.label(text='Set Color:') 8 | 9 | split = layout.split(factor=0.5, align=True) 10 | split.operator('materials.viewport_color_from_node', text = "Viewport From Node", icon='NLA_PUSHDOWN').from_viewport = False 11 | split.operator('materials.viewport_color_from_node', text = "Node From Viewport", icon='TRIA_UP_BAR').from_viewport = True 12 | 13 | #layout.separator() 14 | split = layout.split(factor=0.25, align=False) 15 | split.label(text='Auto Name:') 16 | split.prop(context.scene, "mat_change_multiple", text='Rename All Slots') 17 | col = layout.column(align=True) 18 | row = col.row(align=True) 19 | row.operator('materials.auto_name_material', text = "Rename From Viewport", icon='RESTRICT_COLOR_ON').viewport = True 20 | row.operator('materials.auto_name_material', text = "Rename From Nodes", icon='NODETREE').viewport = False 21 | 22 | row = col.row() 23 | row.operator('materials.convert_clip_color_to_name', text = "Replace Clipboard Color By Name", icon='COLOR') 24 | 25 | def GP_Material_handle_Panel(self, context): 26 | layout = self.layout 27 | layout.use_property_split = True 28 | # split = layout.split(factor=0.5, align=False) 29 | split = layout.split(factor=0.25, align=False) 30 | split.label(text='Auto Name:') 31 | split.prop(context.scene, "mat_change_multiple", text='Rename All Slots') 32 | layout.operator('materials.auto_name_material', text = "Rename From Color", icon='RESTRICT_COLOR_ON').viewport = True 33 | 34 | def register(): 35 | bpy.types.MATERIAL_PT_viewport.append(Material_handle_Panel) 36 | if bpy.app.version >= (2,93,0): 37 | bpy.types.MATERIAL_PT_gpencil_settings.append(GP_Material_handle_Panel) 38 | else: 39 | bpy.types.MATERIAL_PT_gpencil_options.append(GP_Material_handle_Panel) 40 | 41 | def unregister(): 42 | bpy.types.MATERIAL_PT_viewport.remove(Material_handle_Panel) 43 | if bpy.app.version >= (2,93,0): 44 | bpy.types.MATERIAL_PT_gpencil_settings.remove(GP_Material_handle_Panel) 45 | else: 46 | bpy.types.MATERIAL_PT_gpencil_options.remove(GP_Material_handle_Panel) --------------------------------------------------------------------------------