[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\n!hy3dgen/texgen/custom_rasterizer/lib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# UV\n#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#uv.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control\n.pdm.toml\n.pdm-python\n.pdm-build/\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n.DS_Store\n# Cython debug symbols\ncython_debug/\ngradio_cache/\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the OS, Python version, and other tools you might need\nbuild:\n  os: ubuntu-24.04\n  tools:\n    python: \"3.13\"\n\n# Build documentation in the \"docs/\" directory with Sphinx\nsphinx:\n   configuration: docs/source/conf.py\n\n# Optionally, but recommended,\n# declare the Python requirements required to build your documentation\n# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\npython:\n   install:\n   - requirements: docs/requirements.txt\n"
  },
  {
    "path": "LICENSE",
    "content": "TENCENT HUNYUAN 3D 2.0 COMMUNITY LICENSE AGREEMENT\nTencent Hunyuan 3D 2.0 Release Date: January 21, 2025\nTHIS LICENSE AGREEMENT DOES NOT APPLY IN THE EUROPEAN UNION, UNITED KINGDOM AND SOUTH KOREA AND IS EXPRESSLY LIMITED TO THE TERRITORY, AS DEFINED BELOW.\nBy clicking to agree or by using, reproducing, modifying, distributing, performing or displaying any portion or element of the Tencent Hunyuan 3D 2.0 Works, including via any Hosted Service, You will be deemed to have recognized and accepted the content of this Agreement, which is effective immediately.\n1.\tDEFINITIONS.\na.\t“Acceptable Use Policy” shall mean the policy made available by Tencent as set forth in the Exhibit A.\nb.\t“Agreement” shall mean the terms and conditions for use, reproduction, distribution, modification, performance and displaying of Tencent Hunyuan 3D 2.0 Works or any portion or element thereof set forth herein.\nc.\t“Documentation” shall mean the specifications, manuals and documentation for Tencent Hunyuan 3D 2.0 made publicly available by Tencent.\nd.\t“Hosted Service” shall mean a hosted service offered via an application programming interface (API), web access, or any other electronic or remote means.\ne.\t“Licensee,” “You” or “Your” shall mean a natural person or legal entity exercising the rights granted by this Agreement and/or using the Tencent Hunyuan 3D 2.0 Works for any purpose and in any field of use.\nf.\t“Materials” shall mean, collectively, Tencent’s proprietary Tencent Hunyuan 3D 2.0 and Documentation (and any portion thereof) as made available by Tencent under this Agreement.\ng.\t“Model Derivatives” shall mean all: (i) modifications to Tencent Hunyuan 3D 2.0 or any Model Derivative of Tencent Hunyuan 3D 2.0; (ii) works based on Tencent Hunyuan 3D 2.0 or any Model Derivative of Tencent Hunyuan 3D 2.0; or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Tencent Hunyuan 3D 2.0 or any Model Derivative of Tencent Hunyuan 3D 2.0, to that model in order to cause that model to perform similarly to Tencent Hunyuan 3D 2.0 or a Model Derivative of Tencent Hunyuan 3D 2.0, including distillation methods, methods that use intermediate data representations, or methods based on the generation of synthetic data Outputs by Tencent Hunyuan 3D 2.0 or a Model Derivative of Tencent Hunyuan 3D 2.0 for training that model. For clarity, Outputs by themselves are not deemed Model Derivatives.\nh.\t“Output” shall mean the information and/or content output of Tencent Hunyuan 3D 2.0 or a Model Derivative that results from operating or otherwise using Tencent Hunyuan 3D 2.0 or a Model Derivative, including via a Hosted Service.\ni.\t“Tencent,” “We” or “Us” shall mean the applicable entity or entities in the Tencent corporate family that own(s) intellectual property or other rights embodied in or utilized by the Materials.\n*  Section 1.i of the previous Hunyuan License Agreement defined “Tencent,” “We” or “Us” to mean THL A29 Limited, and the copyright notices pertaining to the Materials were previously in the name of “THL A29 Limited.”  That entity has now been de-registered.  You should treat all previously distributed copies of the Materials as if Section 1.i of the Agreement defined “Tencent,” “We” or “Us” to mean “the applicable entity or entities in the Tencent corporate family that own(s) intellectual property or other rights embodied in or utilized by the Materials,” and treat the copyright notice(s) accompanying the Materials as if they were in the name of “Tencent.”  When providing a copy of any Agreement to Third Party recipients of the Tencent Hunyuan Works or products or services using them, as required by Section 3.a of the Agreement, you should provide the most current version of the Agreement, including the change of definition in Section 1.i of the Agreement.\nj.\t“Tencent Hunyuan 3D 2.0” shall mean the 3D generation models and their software and algorithms, including trained model weights, parameters (including optimizer states), machine-learning model code, inference-enabling code, training-enabling code, fine-tuning enabling code and other elements of the foregoing made publicly available by Us at https://github.com/Tencent/Hunyuan3D-2.\nk.\t“Tencent Hunyuan 3D 2.0 Works” shall mean: (i) the Materials; (ii) Model Derivatives; and (iii) all derivative works thereof.\nl.\t“Territory” shall mean the worldwide territory, excluding the territory of the European Union, United Kingdom and South Korea. \nm.\t“Third Party” or “Third Parties” shall mean individuals or legal entities that are not under common control with Us or You.\nn.\t“including” shall mean including but not limited to.\n2.\tGRANT OF RIGHTS.\nWe grant You, for the Territory only, a non-exclusive, non-transferable and royalty-free limited license under Tencent’s intellectual property or other rights owned by Us embodied in or utilized by the Materials to use, reproduce, distribute, create derivative works of (including Model Derivatives), and make modifications to the Materials, only in accordance with the terms of this Agreement and the Acceptable Use Policy, and You must not violate (or encourage or permit anyone else to violate) any term of this Agreement or the Acceptable Use Policy.\n3.\tDISTRIBUTION.\nYou may, subject to Your compliance with this Agreement, distribute or make available to Third Parties the Tencent Hunyuan 3D 2.0 Works, exclusively in the Territory, provided that You meet all of the following conditions:\na.\tYou must provide all such Third Party recipients of the Tencent Hunyuan 3D 2.0 Works or products or services using them a copy of this Agreement;\nb.\tYou must cause any modified files to carry prominent notices stating that You changed the files;\nc.\tYou are encouraged to: (i) publish at least one technology introduction blogpost or one public statement expressing Your experience of using the Tencent Hunyuan 3D 2.0 Works; and (ii) mark the products or services developed by using the Tencent Hunyuan 3D 2.0 Works to indicate that the product/service is “Powered by Tencent Hunyuan”; and\nd.\tAll distributions to Third Parties (other than through a Hosted Service) must be accompanied by a “Notice” text file that contains the following notice: “Tencent Hunyuan 3D 2.0 is licensed under the Tencent Hunyuan 3D 2.0 Community License Agreement, Copyright © 2025 Tencent. All Rights Reserved. The trademark rights of “Tencent Hunyuan” are owned by Tencent or its affiliate.”\nYou may add Your own copyright statement to Your modifications and, except as set forth in this Section and in Section 5, may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Model Derivatives as a whole, provided Your use, reproduction, modification, distribution, performance and display of the work otherwise complies with the terms and conditions of this Agreement (including as regards the Territory). If You receive Tencent Hunyuan 3D 2.0 Works from a Licensee as part of an integrated end user product, then this Section 3 of this Agreement will not apply to You.\ne.  In the event that You use, integrate, implement, or otherwise deploy the Tencent Hunyuan Works, in whole or in part, to provide, enable, or support any service, product, or functionality to third parties, You shall clearly, accurately, and prominently disclose to all end users the full legal name and entity of the actual provider of such service, product, or functionality. You shall expressly and conspicuously state that Tencent is not affiliated with, associated with, sponsoring, or endorsing any such service, product, or functionality. You shall not use or display any name, logo, trademark, trade name, or other indicia of Tencent in any manner that could be construed as, or be likely to create, confusion, deception, or a false impression regarding any relationship, affiliation, sponsorship, or endorsement by Tencent. \n4.\tADDITIONAL COMMERCIAL TERMS.\nIf, on the Tencent Hunyuan 3D 2.0 version release date, the monthly active users of all products or services made available by or for Licensee is greater than 1 million monthly active users in the preceding calendar month, You must request a license from Tencent, which Tencent may grant to You in its sole discretion, and You are not authorized to exercise any of the rights under this Agreement unless or until Tencent otherwise expressly grants You such rights. \nSubject to Tencent's written approval, you may request a license for the use of Tencent Hunyuan 3D 2.0 by submitting the following information to hunyuan3d@tencent.com:\na.\tYour company’s name and associated business sector that plans to use Tencent Hunyuan 3D 2.0.\nb.\tYour intended use case and the purpose of using Tencent Hunyuan 3D 2.0.\n5.\tRULES OF USE.\na.\tYour use of the Tencent Hunyuan 3D 2.0 Works must comply with applicable laws and regulations (including trade compliance laws and regulations) and adhere to the Acceptable Use Policy for the Tencent Hunyuan 3D 2.0 Works, which is hereby incorporated by reference into this Agreement. You must include the use restrictions referenced in these Sections 5(a) and 5(b) as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Tencent Hunyuan 3D 2.0 Works and You must provide notice to subsequent users to whom You distribute that Tencent Hunyuan 3D 2.0 Works are subject to the use restrictions in these Sections 5(a) and 5(b).\nb.\tYou must not use the Tencent Hunyuan 3D 2.0 Works or any Output or results of the Tencent Hunyuan 3D 2.0 Works to improve any other AI model (other than Tencent Hunyuan 3D 2.0 or Model Derivatives thereof).\nc.\tYou must not use, reproduce, modify, distribute, or display the Tencent Hunyuan 3D 2.0 Works, Output or results of the Tencent Hunyuan 3D 2.0 Works outside the Territory. Any such use outside the Territory is unlicensed and unauthorized under this Agreement.\n6.\tINTELLECTUAL PROPERTY.\na.\tSubject to Tencent’s ownership of Tencent Hunyuan 3D 2.0 Works made by or for Tencent and intellectual property rights therein, conditioned upon Your compliance with the terms and conditions of this Agreement, as between You and Tencent, You will be the owner of any derivative works and modifications of the Materials and any Model Derivatives that are made by or for You.\nb.\tNo trademark licenses are granted under this Agreement, and in connection with the Tencent Hunyuan 3D 2.0 Works, Licensee may not use any name or mark owned by or associated with Tencent or any of its affiliates, except as required for reasonable and customary use in describing and distributing the Tencent Hunyuan 3D 2.0 Works. Tencent hereby grants You a license to use “Tencent Hunyuan” (the “Mark”) in the Territory solely as required to comply with the provisions of Section 3(c), provided that You comply with any applicable laws related to trademark protection. All goodwill arising out of Your use of the Mark will inure to the benefit of Tencent.\nc.\tIf You commence a lawsuit or other proceedings (including a cross-claim or counterclaim in a lawsuit) against Us or any person or entity alleging that the Materials or any Output, or any portion of any of the foregoing, infringe any intellectual property or other right owned or licensable by You, then all licenses granted to You under this Agreement shall terminate as of the date such lawsuit or other proceeding is filed. You will defend, indemnify and hold harmless Us from and against any claim by any Third Party arising out of or related to Your or the Third Party’s use or distribution of the Tencent Hunyuan 3D 2.0 Works.\nd.\tTencent claims no rights in Outputs You generate. You and Your users are solely responsible for Outputs and their subsequent uses.\n7.\tDISCLAIMERS OF WARRANTY AND LIMITATIONS OF LIABILITY.\na.\tWe are not obligated to support, update, provide training for, or develop any further version of the Tencent Hunyuan 3D 2.0 Works or to grant any license thereto.\nb.\tUNLESS AND ONLY TO THE EXTENT REQUIRED BY APPLICABLE LAW, THE TENCENT HUNYUAN 3D 2.0 WORKS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED “AS IS” WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND INCLUDING ANY WARRANTIES OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, COURSE OF DEALING, USAGE OF TRADE, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR DISTRIBUTING ANY OF THE TENCENT HUNYUAN 3D 2.0 WORKS OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR OR A THIRD PARTY’S USE OR DISTRIBUTION OF ANY OF THE TENCENT HUNYUAN 3D 2.0 WORKS OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.\nc.\tTO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL TENCENT OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, FOR ANY DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO ANY OF THE TENCENT HUNYUAN 3D 2.0 WORKS OR OUTPUTS, EVEN IF TENCENT OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.\n8.\tSURVIVAL AND TERMINATION.\na.\tThe term of this Agreement shall commence upon Your acceptance of this Agreement or access to the Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein.\nb.\tWe may terminate this Agreement if You breach any of the terms or conditions of this Agreement. Upon termination of this Agreement, You must promptly delete and cease use of the Tencent Hunyuan 3D 2.0 Works. Sections 6(a), 6(c), 7 and 9 shall survive the termination of this Agreement.\n9.\tGOVERNING LAW AND JURISDICTION.\na.\tThis Agreement and any dispute arising out of or relating to it will be governed by the laws of the Hong Kong Special Administrative Region of the People’s Republic of China, without regard to conflict of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement.\nb.\tExclusive jurisdiction and venue for any dispute arising out of or relating to this Agreement will be a court of competent jurisdiction in the Hong Kong Special Administrative Region of the People’s Republic of China, and Tencent and Licensee consent to the exclusive jurisdiction of such court with respect to any such dispute.\n \nEXHIBIT A\nACCEPTABLE USE POLICY\n\nTencent reserves the right to update this Acceptable Use Policy from time to time.\nLast modified: November 5, 2024\n\nTencent endeavors to promote safe and fair use of its tools and features, including Tencent Hunyuan 3D 2.0. You agree not to use Tencent Hunyuan 3D 2.0 or Model Derivatives:\n1.\tOutside the Territory;\n2.\tIn any way that violates any applicable national, federal, state, local, international or any other law or regulation;\n3.\tTo harm Yourself or others;\n4.\tTo repurpose or distribute output from Tencent Hunyuan 3D 2.0 or any Model Derivatives to harm Yourself or others; \n5.\tTo override or circumvent the safety guardrails and safeguards We have put in place;\n6.\tFor the purpose of exploiting, harming or attempting to exploit or harm minors in any way;\n7.\tTo generate or disseminate verifiably false information and/or content with the purpose of harming others or influencing elections;\n8.\tTo generate or facilitate false online engagement, including fake reviews and other means of fake online engagement;\n9.\tTo intentionally defame, disparage or otherwise harass others;\n10.\tTo generate and/or disseminate malware (including ransomware) or any other content to be used for the purpose of harming electronic systems;\n11.\tTo generate or disseminate personal identifiable information with the purpose of harming others;\n12.\tTo generate or disseminate information (including images, code, posts, articles), and place the information in any public context (including –through the use of bot generated tweets), without expressly and conspicuously identifying that the information and/or content is machine generated;\n13.\tTo impersonate another individual without consent, authorization, or legal right;\n14.\tTo make high-stakes automated decisions in domains that affect an individual’s safety, rights or wellbeing (e.g., law enforcement, migration, medicine/health, management of critical infrastructure, safety components of products, essential services, credit, employment, housing, education, social scoring, or insurance);\n15.\tIn a manner that violates or disrespects the social ethics and moral standards of other countries or regions;\n16.\tTo perform, facilitate, threaten, incite, plan, promote or encourage violent extremism or terrorism;\n17.\tFor any use intended to discriminate against or harm individuals or groups based on protected characteristics or categories, online or offline social behavior or known or predicted personal or personality characteristics;\n18.\tTo intentionally exploit any of the vulnerabilities of a specific group of persons based on their age, social, physical or mental characteristics, in order to materially distort the behavior of a person pertaining to that group in a manner that causes or is likely to cause that person or another person physical or psychological harm;\n19.\tFor military purposes;\n20.\tTo engage in the unauthorized or unlicensed practice of any profession including, but not limited to, financial, legal, medical/health, or other professional practices.\n"
  },
  {
    "path": "NOTICE",
    "content": "Usage and Legal Notices:\n\nTencent is pleased to support the open source community by making Hunyuan 3D 2.0 available.\n\nCopyright (C) 2025 Tencent.  All rights reserved. The below software and/or models in this distribution may have been modified by Tencent (\"Tencent Modifications\"). All Tencent Modifications are Copyright (C) Tencent.\n\nHunyuan 3D 2.0 is licensed under the TENCENT HUNYUAN 3D 2.0 COMMUNITY LICENSE AGREEMENT except for the third-party components listed below, which is licensed under different terms. Hunyuan 3D 2.0 does not impose any additional limitations beyond what is outlined in the respective licenses of these third-party components. Users must comply with all terms and conditions of original licenses of these third-party components and must ensure that the usage of the third party components adheres to all relevant laws and regulations. \n\nFor avoidance of doubts, Hunyuan 3D 2.0 means inference-enabling code, parameters, and weights of this Model only, which are made publicly available by Tencent in accordance with TENCENT HUNYUAN 3D 2.0 COMMUNITY LICENSE AGREEMENT.\n\n\nOther dependencies and licenses:\n\n\nOpen Source Model Licensed under the MIT and CreativeML Open RAIL++-M License:\n--------------------------------------------------------------------\n1. Stable Diffusion\nCopyright (c) 2022 Stability AI\n\n\nTerms of the MIT and CreativeML Open RAIL++-M License:\n--------------------------------------------------------------------\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n\nCreativeML Open RAIL++-M License\ndated November 24, 2022\n\nSection I: PREAMBLE\n\nMultimodal generative models are being widely adopted and used, and have the potential to transform the way artists, among other individuals, conceive and benefit from AI or ML technologies as a tool for content creation.\n\nNotwithstanding the current and potential benefits that these artifacts can bring to society at large, there are also concerns about potential misuses of them, either due to their technical limitations or ethical considerations.\n\nIn short, this license strives for both the open and responsible downstream use of the accompanying model. When it comes to the open character, we took inspiration from open source permissive licenses regarding the grant of IP rights. Referring to the downstream responsible use, we added use-based restrictions not permitting the use of the Model in very specific scenarios, in order for the licensor to be able to enforce the license in case potential misuses of the Model may occur. At the same time, we strive to promote open and responsible research on generative models for art and content generation.\n\nEven though downstream derivative versions of the model could be released under different licensing terms, the latter will always have to include - at minimum - the same use-based restrictions as the ones in the original license (this license). We believe in the intersection between open and responsible AI development; thus, this License aims to strike a balance between both in order to enable responsible open-science in the field of AI.\n\nThis License governs the use of the model (and its derivatives) and is informed by the model card associated with the model.\n\nNOW THEREFORE, You and Licensor agree as follows:\n\n1. Definitions\n\n- \"License\" means the terms and conditions for use, reproduction, and Distribution as defined in this document.\n- \"Data\" means a collection of information and/or content extracted from the dataset used with the Model, including to train, pretrain, or otherwise evaluate the Model. The Data is not licensed under this License.\n- \"Output\" means the results of operating a Model as embodied in informational content resulting therefrom.\n- \"Model\" means any accompanying machine-learning based assemblies (including checkpoints), consisting of learnt weights, parameters (including optimizer states), corresponding to the model architecture as embodied in the Complementary Material, that have been trained or tuned, in whole or in part on the Data, using the Complementary Material.\n- \"Derivatives of the Model\" means all modifications to the Model, works based on the Model, or any other model which is created or initialized by transfer of patterns of the weights, parameters, activations or output of the Model, to the other model, in order to cause the other model to perform similarly to the Model, including - but not limited to - distillation methods entailing the use of intermediate data representations or methods based on the generation of synthetic data by the Model for training the other model.\n- \"Complementary Material\" means the accompanying source code and scripts used to define, run, load, benchmark or evaluate the Model, and used to prepare data for training or evaluation, if any. This includes any accompanying documentation, tutorials, examples, etc, if any.\n- \"Distribution\" means any transmission, reproduction, publication or other sharing of the Model or Derivatives of the Model to a third party, including providing the Model as a hosted service made available by electronic or other remote means - e.g. API-based or web access.\n- \"Licensor\" means the copyright owner or entity authorized by the copyright owner that is granting the License, including the persons or entities that may have rights in the Model and/or distributing the Model.\n- \"You\" (or \"Your\") means an individual or Legal Entity exercising permissions granted by this License and/or making use of the Model for whichever purpose and in any field of use, including usage of the Model in an end-use application - e.g. chatbot, translator, image generator.\n- \"Third Parties\" means individuals or legal entities that are not under common control with Licensor or You.\n- \"Contribution\" means any work of authorship, including the original version of the Model and any modifications or additions to that Model or Derivatives of the Model thereof, that is intentionally submitted to Licensor for inclusion in the Model by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Model, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"\n- \"Contributor\" means Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Model.\n\nSection II: INTELLECTUAL PROPERTY RIGHTS\n\nBoth copyright and patent grants apply to the Model, Derivatives of the Model and Complementary Material. The Model and Derivatives of the Model are subject to additional terms as described in Section III.\n\n2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare, publicly display, publicly perform, sublicense, and distribute the Complementary Material, the Model, and Derivatives of the Model.\n3. Grant of Patent License. Subject to the terms and conditions of this License and where and as applicable, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this paragraph) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Model and the Complementary Material, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Model to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Model and/or Complementary Material or a Contribution incorporated within the Model and/or Complementary Material constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for the Model and/or Work shall terminate as of the date such litigation is asserted or filed.\n\nSection III: CONDITIONS OF USAGE, DISTRIBUTION AND REDISTRIBUTION\n\n4. Distribution and Redistribution. You may host for Third Party remote access purposes (e.g. software-as-a-service), reproduce and distribute copies of the Model or Derivatives of the Model thereof in any medium, with or without modifications, provided that You meet the following conditions:\nUse-based restrictions as referenced in paragraph 5 MUST be included as an enforceable provision by You in any type of legal agreement (e.g. a license) governing the use and/or distribution of the Model or Derivatives of the Model, and You shall give notice to subsequent users You Distribute to, that the Model or Derivatives of the Model are subject to paragraph 5. This provision does not apply to the use of Complementary Material.\nYou must give any Third Party recipients of the Model or Derivatives of the Model a copy of this License;\nYou must cause any modified files to carry prominent notices stating that You changed the files;\nYou must retain all copyright, patent, trademark, and attribution notices excluding those notices that do not pertain to any part of the Model, Derivatives of the Model.\nYou may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions - respecting paragraph 4.a. - for use, reproduction, or Distribution of Your modifications, or for any such Derivatives of the Model as a whole, provided Your use, reproduction, and Distribution of the Model otherwise complies with the conditions stated in this License.\n5. Use-based restrictions. The restrictions set forth in Attachment A are considered Use-based restrictions. Therefore You cannot use the Model and the Derivatives of the Model for the specified restricted uses. You may use the Model subject to this License, including only for lawful purposes and in accordance with the License. Use may include creating any content with, finetuning, updating, running, training, evaluating and/or reparametrizing the Model. You shall require all of Your users who use the Model or a Derivative of the Model to comply with the terms of this paragraph (paragraph 5).\n6. The Output You Generate. Except as set forth herein, Licensor claims no rights in the Output You generate using the Model. You are accountable for the Output you generate and its subsequent uses. No use of the output can contravene any provision as stated in the License.\n\nSection IV: OTHER PROVISIONS\n\n7. Updates and Runtime Restrictions. To the maximum extent permitted by law, Licensor reserves the right to restrict (remotely or otherwise) usage of the Model in violation of this License.\n8. Trademarks and related. Nothing in this License permits You to make use of Licensors’ trademarks, trade names, logos or to otherwise suggest endorsement or misrepresent the relationship between the parties; and any rights not expressly granted herein are reserved by the Licensors.\n9. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Model and the Complementary Material (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Model, Derivatives of the Model, and the Complementary Material and assume any risks associated with Your exercise of permissions under this License.\n10. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Model and the Complementary Material (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.\n11. Accepting Warranty or Additional Liability. While redistributing the Model, Derivatives of the Model and the Complementary Material thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.\n12. If any provision of this License is held to be invalid, illegal or unenforceable, the remaining provisions shall be unaffected thereby and remain valid as if such provision had not been set forth herein.\n\nEND OF TERMS AND CONDITIONS\n\n\n\n\nAttachment A\n\nUse Restrictions\n\nYou agree not to use the Model or Derivatives of the Model:\n\n- In any way that violates any applicable national, federal, state, local or international law or regulation;\n- For the purpose of exploiting, harming or attempting to exploit or harm minors in any way;\n- To generate or disseminate verifiably false information and/or content with the purpose of harming others;\n- To generate or disseminate personal identifiable information that can be used to harm an individual;\n- To defame, disparage or otherwise harass others;\n- For fully automated decision making that adversely impacts an individual’s legal rights or otherwise creates or modifies a binding, enforceable obligation;\n- For any use intended to or which has the effect of discriminating against or harming individuals or groups based on online or offline social behavior or known or predicted personal or personality characteristics;\n- To exploit any of the vulnerabilities of a specific group of persons based on their age, social, physical or mental characteristics, in order to materially distort the behavior of a person pertaining to that group in a manner that causes or is likely to cause that person or another person physical or psychological harm;\n- For any use intended to or which has the effect of discriminating against individuals or groups based on legally protected characteristics or categories;\n- To provide medical advice and medical results interpretation;\n- To generate or disseminate information for the purpose to be used for administration of justice, law enforcement, immigration or asylum processes, such as predicting an individual will commit fraud/crime commitment (e.g. by text profiling, drawing causal relationships between assertions made in documents, indiscriminate and arbitrarily-targeted use).\n\n\n\nOpen Source Model Licensed under the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT and Other Licenses of the Third-Party Components therein:\n--------------------------------------------------------------------\n1. HunyuanDiT\nCopyright (C) 2024 Tencent.  All rights reserved. \n\n\nTerms of the TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT:\n--------------------------------------------------------------------\nTENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT\nTencent Hunyuan Release Date: 2024/5/14\nBy clicking to agree or by using, reproducing, modifying, distributing, performing or displaying any portion or element of the Tencent Hunyuan Works, including via any Hosted Service, You will be deemed to have recognized and accepted the content of this Agreement, which is effective immediately.\n1.\tDEFINITIONS.\na.\t“Acceptable Use Policy” shall mean the policy made available by Tencent as set forth in the Exhibit A.\nb.\t“Agreement” shall mean the terms and conditions for use, reproduction, distribution, modification, performance and displaying of the Hunyuan Works or any portion or element thereof set forth herein.\nc.\t“Documentation” shall mean the specifications, manuals and documentation for Tencent Hunyuan made publicly available by Tencent.\nd.\t“Hosted Service” shall mean a hosted service offered via an application programming interface (API), web access, or any other electronic or remote means.\ne.\t“Licensee,” “You” or “Your” shall mean a natural person or legal entity exercising the rights granted by this Agreement and/or using the Tencent Hunyuan Works for any purpose and in any field of use.\nf.\t“Materials” shall mean, collectively, Tencent’s proprietary Tencent Hunyuan and Documentation (and any portion thereof) as made available by Tencent under this Agreement.\ng.\t“Model Derivatives” shall mean all: (i) modifications to Tencent Hunyuan or any Model Derivative of Tencent Hunyuan; (ii) works based on Tencent Hunyuan or any Model Derivative of Tencent Hunyuan; or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Tencent Hunyuan or any Model Derivative of Tencent Hunyuan, to that model in order to cause that model to perform similarly to Tencent Hunyuan or a Model Derivative of Tencent Hunyuan, including distillation methods, methods that use intermediate data representations, or methods based on the generation of synthetic data Outputs by Tencent Hunyuan or a Model Derivative of Tencent Hunyuan for training that model. For clarity, Outputs by themselves are not deemed Model Derivatives.\nh.\t“Output” shall mean the information and/or content output of Tencent Hunyuan or a Model Derivative that results from operating or otherwise using Tencent Hunyuan or a Model Derivative, including via a Hosted Service.\ni.\t“Tencent,” “We” or “Us” shall mean Tencent.\nj.\t“Tencent Hunyuan” shall mean the large language models, image/video/audio/3D generation models, and multimodal large language models and their software and algorithms, including trained model weights, parameters (including optimizer states), machine-learning model code, inference-enabling code, training-enabling code, fine-tuning enabling code and other elements of the foregoing made publicly available by Us at  https://huggingface.co/Tencent-Hunyuan/HunyuanDiT and https://github.com/Tencent/HunyuanDiT .\nk.\t“Tencent Hunyuan Works” shall mean: (i) the Materials; (ii) Model Derivatives; and (iii) all derivative works thereof.\nl.\t“Third Party” or “Third Parties” shall mean individuals or legal entities that are not under common control with Us or You.\nm.\t“including” shall mean including but not limited to.\n2.\tGRANT OF RIGHTS.\nWe grant You a non-exclusive, worldwide, non-transferable and royalty-free limited license under Tencent’s intellectual property or other rights owned by Us embodied in or utilized by the Materials to use, reproduce, distribute, create derivative works of (including Model Derivatives), and make modifications to the Materials, only in accordance with the terms of this Agreement and the Acceptable Use Policy, and You must not violate (or encourage or permit anyone else to violate) any term of this Agreement or the Acceptable Use Policy.\n3.\tDISTRIBUTION.\nYou may, subject to Your compliance with this Agreement, distribute or make available to Third Parties the Tencent Hunyuan Works, provided that You meet all of the following conditions:\na.\tYou must provide all such Third Party recipients of the Tencent Hunyuan Works or products or services using them a copy of this Agreement;\nb.\tYou must cause any modified files to carry prominent notices stating that You changed the files;\nc.\tYou are encouraged to: (i) publish at least one technology introduction blogpost or one public statement expressing Your experience of using the Tencent Hunyuan Works; and (ii) mark the products or services developed by using the Tencent Hunyuan Works to indicate that the product/service is “Powered by Tencent Hunyuan”; and\nd.\tAll distributions to Third Parties (other than through a Hosted Service) must be accompanied by a “Notice” text file that contains the following notice: “Tencent Hunyuan is licensed under the Tencent Hunyuan Community License Agreement, Copyright © 2024 Tencent. All Rights Reserved. The trademark rights of “Tencent Hunyuan” are owned by Tencent or its affiliate.”\nYou may add Your own copyright statement to Your modifications and, except as set forth in this Section and in Section 5, may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Model Derivatives as a whole, provided Your use, reproduction, modification, distribution, performance and display of the work otherwise complies with the terms and conditions of this Agreement. If You receive Tencent Hunyuan Works from a Licensee as part of an integrated end user product, then this Section 3 of this Agreement will not apply to You.\n4.\tADDITIONAL COMMERCIAL TERMS.\nIf, on the Tencent Hunyuan version release date, the monthly active users of all products or services made available by or for Licensee is greater than 100 million monthly active users in the preceding calendar month, You must request a license from Tencent, which Tencent may grant to You in its sole discretion, and You are not authorized to exercise any of the rights under this Agreement unless or until Tencent otherwise expressly grants You such rights.\n5.\tRULES OF USE.\na.\tYour use of the Tencent Hunyuan Works must comply with applicable laws and regulations (including trade compliance laws and regulations) and adhere to the Acceptable Use Policy for the Tencent Hunyuan Works, which is hereby incorporated by reference into this Agreement. You must include the use restrictions referenced in these Sections 5(a) and 5(b) as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Tencent Hunyuan Works and You must provide notice to subsequent users to whom You distribute that Tencent Hunyuan Works are subject to the use restrictions in these Sections 5(a) and 5(b).\nb.\tYou must not use the Tencent Hunyuan Works or any Output or results of the Tencent Hunyuan Works to improve any other large language model (other than Tencent Hunyuan or Model Derivatives thereof).\n6.\tINTELLECTUAL PROPERTY.\na.\tSubject to Tencent’s ownership of Tencent Hunyuan Works made by or for Tencent and intellectual property rights therein, conditioned upon Your compliance with the terms and conditions of this Agreement, as between You and Tencent, You will be the owner of any derivative works and modifications of the Materials and any Model Derivatives that are made by or for You.\nb.\tNo trademark licenses are granted under this Agreement, and in connection with the Tencent Hunyuan Works, Licensee may not use any name or mark owned by or associated with Tencent or any of its affiliates, except as required for reasonable and customary use in describing and distributing the Tencent Hunyuan Works. Tencent hereby grants You a license to use “Tencent Hunyuan” (the “Mark”) solely as required to comply with the provisions of Section 3(c), provided that You comply with any applicable laws related to trademark protection. All goodwill arising out of Your use of the Mark will inure to the benefit of Tencent.\nc.\tIf You commence a lawsuit or other proceedings (including a cross-claim or counterclaim in a lawsuit) against Us or any person or entity alleging that the Materials or any Output, or any portion of any of the foregoing, infringe any intellectual property or other right owned or licensable by You, then all licenses granted to You under this Agreement shall terminate as of the date such lawsuit or other proceeding is filed. You will defend, indemnify and hold harmless Us from and against any claim by any Third Party arising out of or related to Your or the Third Party’s use or distribution of the Tencent Hunyuan Works.\nd.\tTencent claims no rights in Outputs You generate. You and Your users are solely responsible for Outputs and their subsequent uses.\n7.\tDISCLAIMERS OF WARRANTY AND LIMITATIONS OF LIABILITY.\na.\tWe are not obligated to support, update, provide training for, or develop any further version of the Tencent Hunyuan Works or to grant any license thereto.\nb.\tUNLESS AND ONLY TO THE EXTENT REQUIRED BY APPLICABLE LAW, THE TENCENT HUNYUAN WORKS AND ANY OUTPUT AND RESULTS THEREFROM ARE PROVIDED “AS IS” WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES OF ANY KIND INCLUDING ANY WARRANTIES OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, COURSE OF DEALING, USAGE OF TRADE, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR DISTRIBUTING ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR OR A THIRD PARTY’S USE OR DISTRIBUTION OF ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.\nc.\tTO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL TENCENT OR ITS AFFILIATES BE LIABLE UNDER ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, TORT, NEGLIGENCE, PRODUCTS LIABILITY, OR OTHERWISE, FOR ANY DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO ANY OF THE TENCENT HUNYUAN WORKS OR OUTPUTS, EVEN IF TENCENT OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF ANY OF THE FOREGOING.\n8.\tSURVIVAL AND TERMINATION.\na.\tThe term of this Agreement shall commence upon Your acceptance of this Agreement or access to the Materials and will continue in full force and effect until terminated in accordance with the terms and conditions herein.\nb.\tWe may terminate this Agreement if You breach any of the terms or conditions of this Agreement. Upon termination of this Agreement, You must promptly delete and cease use of the Tencent Hunyuan Works. Sections 6(a), 6(c), 7 and 9 shall survive the termination of this Agreement.\n9.\tGOVERNING LAW AND JURISDICTION.\na.\tThis Agreement and any dispute arising out of or relating to it will be governed by the laws of the Hong Kong Special Administrative Region of the People’s Republic of China, without regard to conflict of law principles, and the UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement.\nb.\tExclusive jurisdiction and venue for any dispute arising out of or relating to this Agreement will be a court of competent jurisdiction in the Hong Kong Special Administrative Region of the People’s Republic of China, and Tencent and Licensee consent to the exclusive jurisdiction of such court with respect to any such dispute.\n \n\nEXHIBIT A\nACCEPTABLE USE POLICY\n\nTencent reserves the right to update this Acceptable Use Policy from time to time.\nLast modified: 2024/5/14\n\nTencent endeavors to promote safe and fair use of its tools and features, including Tencent Hunyuan. You agree not to use Tencent Hunyuan or Model Derivatives:\n1.\tIn any way that violates any applicable national, federal, state, local, international or any other law or regulation;\n2.\tTo harm Yourself or others;\n3.\tTo repurpose or distribute output from Tencent Hunyuan or any Model Derivatives to harm Yourself or others; \n4.\tTo override or circumvent the safety guardrails and safeguards We have put in place;\n5.\tFor the purpose of exploiting, harming or attempting to exploit or harm minors in any way;\n6.\tTo generate or disseminate verifiably false information and/or content with the purpose of harming others or influencing elections;\n7.\tTo generate or facilitate false online engagement, including fake reviews and other means of fake online engagement;\n8.\tTo intentionally defame, disparage or otherwise harass others;\n9.\tTo generate and/or disseminate malware (including ransomware) or any other content to be used for the purpose of harming electronic systems;\n10.\tTo generate or disseminate personal identifiable information with the purpose of harming others;\n11.\tTo generate or disseminate information (including images, code, posts, articles), and place the information in any public context (including –through the use of bot generated tweets), without expressly and conspicuously identifying that the information and/or content is machine generated;\n12.\tTo impersonate another individual without consent, authorization, or legal right;\n13.\tTo make high-stakes automated decisions in domains that affect an individual’s safety, rights or wellbeing (e.g., law enforcement, migration, medicine/health, management of critical infrastructure, safety components of products, essential services, credit, employment, housing, education, social scoring, or insurance);\n14.\tIn a manner that violates or disrespects the social ethics and moral standards of other countries or regions;\n15.\tTo perform, facilitate, threaten, incite, plan, promote or encourage violent extremism or terrorism;\n16.\tFor any use intended to discriminate against or harm individuals or groups based on protected characteristics or categories, online or offline social behavior or known or predicted personal or personality characteristics;\n17.\tTo intentionally exploit any of the vulnerabilities of a specific group of persons based on their age, social, physical or mental characteristics, in order to materially distort the behavior of a person pertaining to that group in a manner that causes or is likely to cause that person or another person physical or psychological harm;\n18.\tFor military purposes;\n19.\tTo engage in the unauthorized or unlicensed practice of any profession including, but not limited to, financial, legal, medical/health, or other professional practices.\n\nFor the license of other third party components, please refer to the following URL:\nhttps://huggingface.co/Tencent-Hunyuan/HunyuanDiT/blob/main/Notice\n\n--------------------------------------------------------------------\n\nThis Model also incorporates insights from Flux's neural network architechtures (https://github.com/black-forest-labs/flux?tab=readme-ov-file). Credits are given to the orginal authors.\n"
  },
  {
    "path": "README.md",
    "content": "[中文阅读](README_zh_cn.md)\n[日本語で読む](README_ja_jp.md)\n\n<p align=\"center\"> \n  <img src=\"https://github.com/user-attachments/assets/efb402a1-0b09-41e0-a6cb-259d442e76aa\">\n\n</p>\n\n<div align=\"center\">\n  <a href=https://3d.hunyuan.tencent.com target=\"_blank\"><img src=https://img.shields.io/badge/Official%20Site-333399.svg?logo=homepage height=22px></a>\n  <a href=https://huggingface.co/spaces/tencent/Hunyuan3D-2  target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Demo-276cb4.svg height=22px></a>\n  <a href=https://huggingface.co/tencent/Hunyuan3D-2 target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Models-d96902.svg height=22px></a>\n  <a href=https://3d-models.hunyuan.tencent.com/ target=\"_blank\"><img src= https://img.shields.io/badge/Page-bb8a2e.svg?logo=github height=22px></a>\n  <a href=https://discord.gg/dNBrdrGGMa target=\"_blank\"><img src= https://img.shields.io/badge/Discord-white.svg?logo=discord height=22px></a>\n  <a href=https://arxiv.org/abs/2501.12202 target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>\n  <a href=https://x.com/TencentHunyuan target=\"_blank\"><img src=https://img.shields.io/badge/Hunyuan-black.svg?logo=x height=22px></a>\n <a href=\"#community-resources\" target=\"_blank\"><img src=https://img.shields.io/badge/Community-lavender.svg?logo=homeassistantcommunitystore height=22px></a>\n</div>\n\n[//]: # (  <a href=# target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>)\n\n[//]: # (  <a href=# target=\"_blank\"><img src= https://img.shields.io/badge/Colab-8f2628.svg?logo=googlecolab height=22px></a>)\n\n[//]: # (  <a href=\"#\"><img alt=\"PyPI - Downloads\" src=\"https://img.shields.io/pypi/v/mulankit?logo=pypi\"  height=22px></a>)\n\n<br>\n\n<p align=\"center\">\n“ Living out everyone’s imagination on creating and manipulating 3D assets.”\n</p>\n\nhttps://github.com/user-attachments/assets/a2cbc5b8-be22-49d7-b1c3-7aa2b20ba460\n\n\n## 🔥 News\n\n- July 26, 2025: 🤗 We release the first open-source, simulation-capable, immersive 3D world generation model, [HunyuanWorld-1.0](https://github.com/Tencent-Hunyuan/HunyuanWorld-1.0)!\n- June 23, 2025: 📄 Release the system technical report of [Hunyuan3D 2.5](https://arxiv.org/abs/2506.16504).\n- June 13, 2025: 🤗 Release [Hunyuan3D-2.1](https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1), fully open-sourced with new PBR model, VAE encoder, and all training code. \n- Apr 1, 2025: 🤗 Release turbo paint model [Hunyuan3D-Paint-v2-0-Turbo](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-paint-v2-0-turbo), and multiview texture generation pipeline, try it [here](examples/fast_texture_gen_multiview.py)! Stay tuned for our new texture generation model [RomanTex](https://github.com/oakshy/RomanTex) and PBR material generation [MaterialMVP](https://github.com/ZebinHe/MaterialMVP/)! \n- Mar 19, 2025: 🤗 Release turbo model [Hunyuan3D-2-Turbo](https://huggingface.co/tencent/Hunyuan3D-2/), [Hunyuan3D-2mini-Turbo](https://huggingface.co/tencent/Hunyuan3D-2mini/) and [FlashVDM](https://github.com/Tencent/FlashVDM).\n- Mar 18, 2025: 🤗 Release multiview shape model [Hunyuan3D-2mv](https://huggingface.co/tencent/Hunyuan3D-2mv) and 0.6B\n  shape model [Hunyuan3D-2mini](https://huggingface.co/tencent/Hunyuan3D-2mini).\n- Feb 14, 2025: 🛠️ Release texture enhancement module, please obtain high-definition textures\n  via [here](minimal_demo.py)!\n- Feb 3, 2025: 🐎\n  Release [Hunyuan3D-DiT-v2-0-Fast](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0-fast), our\n  guidance distillation model that could half the dit inference time, see [here](minimal_demo.py) for usage.\n- Jan 27, 2025: 🛠️ Release Blender addon for Hunyuan3D 2.0, Check it out [here](#blender-addon).\n- Jan 23, 2025: 💬 We thank community members for\n  creating [Windows installation tool](https://github.com/YanWenKun/Hunyuan3D-2-WinPortable), ComfyUI support\n  with [ComfyUI-Hunyuan3DWrapper](https://github.com/kijai/ComfyUI-Hunyuan3DWrapper)\n  and [ComfyUI-3D-Pack](https://github.com/MrForExample/ComfyUI-3D-Pack) and other\n  awesome [extensions](#community-resources).\n- Jan 21, 2025: 💬 Enjoy exciting 3D generation on our website [Hunyuan3D Studio](https://3d.hunyuan.tencent.com)!\n- Jan 21, 2025: 🤗 Release inference code and pretrained models\n  of [Hunyuan3D 2.0](https://huggingface.co/tencent/Hunyuan3D-2). Please give it a try\n  via [huggingface space](https://huggingface.co/spaces/tencent/Hunyuan3D-2) and\n  our [official site](https://3d.hunyuan.tencent.com)!\n\n> Join our **[Wechat](#)** and **[Discord](https://discord.gg/dNBrdrGGMa)** group to discuss and find help from us.\n\n| Wechat Group                                     | Xiaohongshu                                           | X                                           | Discord                                           |\n|--------------------------------------------------|-------------------------------------------------------|---------------------------------------------|---------------------------------------------------|\n| <img src=\"assets/qrcode/wechat.png\"  height=140> | <img src=\"assets/qrcode/xiaohongshu.png\"  height=140> | <img src=\"assets/qrcode/x.png\"  height=140> | <img src=\"assets/qrcode/discord.png\"  height=140> |        \n\n\n\n\n## **Abstract**\n\nWe present Hunyuan3D 2.0, an advanced large-scale 3D synthesis system for generating high-resolution textured 3D assets.\nThis system includes two foundation components: a large-scale shape generation model - Hunyuan3D-DiT, and a large-scale\ntexture synthesis model - Hunyuan3D-Paint.\nThe shape generative model, built on a scalable flow-based diffusion transformer, aims to create geometry that properly\naligns with a given condition image, laying a solid foundation for downstream applications.\nThe texture synthesis model, benefiting from strong geometric and diffusion priors, produces high-resolution and vibrant\ntexture maps for either generated or hand-crafted meshes.\nFurthermore, we build Hunyuan3D-Studio - a versatile, user-friendly production platform that simplifies the re-creation\nprocess of 3D assets. It allows both professional and amateur users to manipulate or even animate their meshes\nefficiently.\nWe systematically evaluate our models, showing that Hunyuan3D 2.0 outperforms previous state-of-the-art models,\nincluding the open-source models and closed-source models in geometry details, condition alignment, texture quality, and\ne.t.c.\n\n\n\n<p align=\"center\">\n  <img src=\"assets/images/system.jpg\">\n</p>\n\n## ☯️ **Hunyuan3D 2.0**\n\n### Architecture\n\nHunyuan3D 2.0 features a two-stage generation pipeline, starting with the creation of a bare mesh, followed by the\nsynthesis of a texture map for that mesh. This strategy is effective for decoupling the difficulties of shape and\ntexture generation and also provides flexibility for texturing either generated or handcrafted meshes.\n\n<p align=\"left\">\n  <img src=\"assets/images/arch.jpg\">\n</p>\n\n### Performance\n\nWe have evaluated Hunyuan3D 2.0 with other open-source as well as close-source 3d-generation methods.\nThe numerical results indicate that Hunyuan3D 2.0 surpasses all baselines in the quality of generated textured 3D assets\nand the condition following ability.\n\n| Model                   | CMMD(⬇)   | FID_CLIP(⬇) | FID(⬇)      | CLIP-score(⬆) |\n|-------------------------|-----------|-------------|-------------|---------------|\n| Top Open-source Model1  | 3.591     | 54.639      | 289.287     | 0.787         |\n| Top Close-source Model1 | 3.600     | 55.866      | 305.922     | 0.779         |\n| Top Close-source Model2 | 3.368     | 49.744      | 294.628     | 0.806         |\n| Top Close-source Model3 | 3.218     | 51.574      | 295.691     | 0.799         |\n| Hunyuan3D 2.0           | **3.193** | **49.165**  | **282.429** | **0.809**     |\n\nGeneration results of Hunyuan3D 2.0:\n<p align=\"left\">\n  <img src=\"assets/images/e2e-1.gif\"  height=250>\n  <img src=\"assets/images/e2e-2.gif\"  height=250>\n</p>\n\n## 🎁 Models Zoo\n\nIt takes 6 GB VRAM for shape generation and 16 GB for shape and texture generation in total.\n\nHunyuan3D-2-1 Series\n\n| Model                | Description                   | Date       | Size | Huggingface                                                                             |\n|----------------------|-------------------------------|------------|------|-----------------------------------------------------------------------------------------|\n| Hunyuan3D-DiT-v2-1   | Mini Image to Shape Model     | 2025-06-13 | 3.0B | [Download](https://huggingface.co/tencent/Hunyuan3D-2.1/tree/main/hunyuan3d-dit-v2-1)   |\n| Hunyuan3D-Paint-v2-1 | Texture Generation Model    | 2025-06-13 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2.1/tree/main/hunyuan3d-paintpbr-v2-1) |\n\nHunyuan3D-2mini Series\n\n| Model                       | Description                   | Date       | Size | Huggingface                                                                                      |\n|-----------------------------|-------------------------------|------------|------|--------------------------------------------------------------------------------------------------|\n| Hunyuan3D-DiT-v2-mini-Turbo | Step Distillation Version     | 2025-03-19 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini-turbo) |\n| Hunyuan3D-DiT-v2-mini-Fast  | Guidance Distillation Version | 2025-03-18 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini-fast)  |\n| Hunyuan3D-DiT-v2-mini       | Mini Image to Shape Model     | 2025-03-18 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini)       |\n\n\nHunyuan3D-2mv Series\n\n| Model                     | Description                    | Date       | Size | Huggingface                                                                                  |\n|---------------------------|--------------------------------|------------|------|----------------------------------------------------------------------------------------------| \n| Hunyuan3D-DiT-v2-mv-Turbo | Step Distillation Version      | 2025-03-19 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv-turbo) |\n| Hunyuan3D-DiT-v2-mv-Fast  | Guidance Distillation Version  | 2025-03-18 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv-fast)  |\n| Hunyuan3D-DiT-v2-mv       | Multiview Image to Shape Model | 2025-03-18 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv)       |\n\nHunyuan3D-2 Series\n\n| Model                      | Description                 | Date       | Size | Huggingface                                                                               |\n|----------------------------|-----------------------------|------------|------|-------------------------------------------------------------------------------------------| \n| Hunyuan3D-DiT-v2-0-Turbo   | Step Distillation Model     | 2025-03-19 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0-turbo)   |\n| Hunyuan3D-DiT-v2-0-Fast    | Guidance Distillation Model | 2025-02-03 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0-fast)    |\n| Hunyuan3D-DiT-v2-0         | Image to Shape Model        | 2025-01-21 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0)         |\n| Hunyuan3D-Paint-v2-0       | Texture Generation Model    | 2025-01-21 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-paint-v2-0)       |\n| Hunyuan3D-Paint-v2-0-Turbo | Distillation Texure Model   | 2025-04-01 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-paint-v2-0-turbo) |\n| Hunyuan3D-Delight-v2-0     | Image Delight Model         | 2025-01-21 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0)     | \n\n## 🤗 Get Started with Hunyuan3D 2.0\n\nHunyuan3D 2.0 supports Macos, Windows, Linux. You may follow the next steps to use Hunyuan3D 2.0 via:\n\n- [Code](#code-usage)\n- [Gradio App](#gradio-app)\n- [API Server](#api-server)\n- [Blender Addon](#blender-addon)\n- [Official Site](#official-site)\n\n### Install Requirements\n\nPlease install Pytorch via the [official](https://pytorch.org/) site. Then install the other requirements via\n\n```bash\npip install -r requirements.txt\npip install -e .\n# for texture\ncd hy3dgen/texgen/custom_rasterizer\npython3 setup.py install\ncd ../../..\ncd hy3dgen/texgen/differentiable_renderer\npython3 setup.py install\n```\n\n### Code Usage\n\nWe designed a diffusers-like API to use our shape generation model - Hunyuan3D-DiT and texture synthesis model -\nHunyuan3D-Paint.\n\nYou could assess **Hunyuan3D-DiT** via:\n\n```python\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n```\n\nThe output mesh is a [trimesh object](https://trimesh.org/trimesh.html), which you could save to glb/obj (or other\nformat) file.\n\nFor **Hunyuan3D-Paint**, do the following:\n\n```python\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\n# let's generate a mesh first\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n\npipeline = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(mesh, image='assets/demo.png')\n```\n\nPlease visit [examples](examples) folder for more advanced usage, such as **multiview image to 3D generation** and *\n*texture generation\nfor handcrafted mesh**.\n\n### Gradio App\n\nYou could also host a [Gradio](https://www.gradio.app/) App in your own computer via:\n\nStandard Version\n\n```bash\n# Hunyuan3D-2mini\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mini --subfolder hunyuan3d-dit-v2-mini --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n# Hunyuan3D-2mv\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mv --subfolder hunyuan3d-dit-v2-mv --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n# Hunyuan3D-2\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2 --subfolder hunyuan3d-dit-v2-0 --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n```\n\nTurbo Version\n\n```bash\n# Hunyuan3D-2mini\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mini --subfolder hunyuan3d-dit-v2-mini-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n# Hunyuan3D-2mv\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mv --subfolder hunyuan3d-dit-v2-mv-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n# Hunyuan3D-2\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2 --subfolder hunyuan3d-dit-v2-0-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n```\n\n### API Server\n\nYou could launch an API server locally, which you could post web request for Image/Text to 3D, Texturing existing mesh,\nand e.t.c.\n\n```bash\npython api_server.py --host 0.0.0.0 --port 8080\n```\n\nA demo post request for image to 3D without texture.\n\n```bash\nimg_b64_str=$(base64 -i assets/demo.png)\ncurl -X POST \"http://localhost:8080/generate\" \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n           \"image\": \"'\"$img_b64_str\"'\",\n         }' \\\n     -o test2.glb\n```\n\n### Blender Addon\n\nWith an API server launched, you could also directly use Hunyuan3D 2.0 in your blender with\nour [Blender Addon](blender_addon.py). Please follow our tutorial to install and use.\n\nhttps://github.com/user-attachments/assets/8230bfb5-32b1-4e48-91f4-a977c54a4f3e\n\n### Official Site\n\nDon't forget to visit [Hunyuan3D](https://3d.hunyuan.tencent.com) for quick use, if you don't want to host yourself.\n\n## 📑 Open-Source Plan\n\n- [x] Inference Code\n- [x] Model Checkpoints\n- [x] Technical Report\n- [x] ComfyUI\n- [x] Finetuning\n- [ ] TensorRT Version\n\n## 🔗 BibTeX\n\nIf you found this repository helpful, please cite our reports:\n\n```bibtex\n@misc{lai2025hunyuan3d25highfidelity3d,\n      title={Hunyuan3D 2.5: Towards High-Fidelity 3D Assets Generation with Ultimate Details}, \n      author={Tencent Hunyuan3D Team},\n      year={2025},\n      eprint={2506.16504},\n      archivePrefix={arXiv},\n      primaryClass={cs.CV},\n      url={https://arxiv.org/abs/2506.16504}, \n}\n\n@misc{hunyuan3d22025tencent,\n    title={Hunyuan3D 2.0: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2025},\n    eprint={2501.12202},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n\n@misc{yang2024hunyuan3d,\n    title={Hunyuan3D 1.0: A Unified Framework for Text-to-3D and Image-to-3D Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2024},\n    eprint={2411.02293},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n```\n\n## Community Resources\n\nThanks for the contributions of community members, here we have these great extensions of Hunyuan3D 2.0:\n\n- [ComfyUI-3D-Pack](https://github.com/MrForExample/ComfyUI-3D-Pack)\n- [ComfyUI-Hunyuan3DWrapper](https://github.com/kijai/ComfyUI-Hunyuan3DWrapper)\n- [Hunyuan3D-2-for-windows](https://github.com/sdbds/Hunyuan3D-2-for-windows)\n- [📦 A bundle for running on Windows | 整合包](https://github.com/YanWenKun/Hunyuan3D-2-WinPortable)\n- [Hunyuan3D-2GP](https://github.com/deepbeepmeep/Hunyuan3D-2GP)\n- [Kaggle Notebook](https://github.com/darkon12/Hunyuan3D-2GP_Kaggle)\n\n## Acknowledgements\n\nWe would like to thank the contributors to\nthe [Trellis](https://github.com/microsoft/TRELLIS),  [DINOv2](https://github.com/facebookresearch/dinov2), [Stable Diffusion](https://github.com/Stability-AI/stablediffusion), [FLUX](https://github.com/black-forest-labs/flux), [diffusers](https://github.com/huggingface/diffusers), [HuggingFace](https://huggingface.co), [CraftsMan3D](https://github.com/wyysf-98/CraftsMan3D),\nand [Michelangelo](https://github.com/NeuralCarver/Michelangelo/tree/main) repositories, for their open research and\nexploration.\n\n## Star History\n\n<a href=\"https://star-history.com/#Tencent/Hunyuan3D-2&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n </picture>\n</a>\n"
  },
  {
    "path": "README_ja_jp.md",
    "content": "[Read in English](README.md)\n[中文阅读](README_zh_cn.md)\n\n<p align=\"center\"> \n  <img src=\"./assets/images/teaser.jpg\">\n\n\n</p>\n\n<div align=\"center\">\n  <a href=https://3d.hunyuan.tencent.com target=\"_blank\"><img src=https://img.shields.io/badge/Official%20Site-black.svg?logo=homepage height=22px></a>\n  <a href=https://huggingface.co/spaces/tencent/Hunyuan3D-2  target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Demo-276cb4.svg height=22px></a>\n  <a href=https://huggingface.co/tencent/Hunyuan3D-2 target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Models-d96902.svg height=22px></a>\n  <a href=https://3d-models.hunyuan.tencent.com/ target=\"_blank\"><img src= https://img.shields.io/badge/Page-bb8a2e.svg?logo=github height=22px></a>\n  <a href=https://discord.gg/dNBrdrGGMa target=\"_blank\"><img src= https://img.shields.io/badge/Discord-white.svg?logo=discord height=22px></a>\n  <a href=https://github.com/Tencent/Hunyuan3D-2/blob/main/assets/report/Tencent_Hunyuan3D_2_0.pdf target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>\n</div>\n\n\n[//]: # (  <a href=# target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>)\n\n[//]: # (  <a href=# target=\"_blank\"><img src= https://img.shields.io/badge/Colab-8f2628.svg?logo=googlecolab height=22px></a>)\n\n[//]: # (  <a href=\"#\"><img alt=\"PyPI - Downloads\" src=\"https://img.shields.io/pypi/v/mulankit?logo=pypi\"  height=22px></a>)\n\n<br>\n<p align=\"center\">\n“ 3Dアセットの作成と操作において、すべての人の想像力を実現します。”\n</p>\n\n## 🔥 ニュース\n\n- 2025年2月14日: 🛠️ テクスチャ強化モジュールをリリースしました。HD テクスチャ生成を [体験](minimal_demo.py) してください。\n- 2025年1月21日: 💬 私たちのウェブサイト [Hunyuan3D Studio](https://3d.hunyuan.tencent.com) でエキサイティングな3D生成を楽しんでください！\n- 2025年1月21日: 💬 [Hunyuan3D 2.0](https://huggingface.co/tencent/Hunyuan3D-2) の推論コードと事前学習モデルをリリースしました。\n- 2025年1月21日: 💬 Hunyuan3D 2.0をリリースしました。 [huggingface space](https://huggingface.co/spaces/tencent/Hunyuan3D-2) や [公式サイト](https://3d.hunyuan.tencent.com) でお試しください！\n\n## **概要**\n\nHunyuan3D 2.0は、高解像度のテクスチャ付き3Dアセットを生成するための高度な大規模3D合成システムです。\nこのシステムには、2つの基盤コンポーネントが含まれています：大規模形状生成モデル - Hunyuan3D-DiT、および大規模\nテクスチャ合成モデル - Hunyuan3D-Paint。\n形状生成モデルは、スケーラブルなフローに基づく拡散トランスフォーマーに基づいて構築されており、与えられた条件画像に適切に\n一致するジオメトリを作成することを目的としており、下流のアプリケーションのための堅固な基盤を提供します。\nテクスチャ合成モデルは、強力なジオメトリおよび拡散の事前知識を活用して、生成されたまたは手作りのメッシュのために高解像度で鮮やかな\nテクスチャマップを生成します。\nさらに、Hunyuan3D-Studioを構築しました。これは、3Dアセットの再作成プロセスを簡素化する多用途で使いやすい制作プラットフォームです。\nプロフェッショナルおよびアマチュアユー���ーの両方がメッシュを効率的に操作したり、アニメーション化したりすることができます。\n私たちはモデルを体系的に評価し、Hunyuan3D 2.0が以前の最先端モデルを上回ることを示しました。\nオープンソースモデルとクローズドソースモデルの両方で、ジオメトリの詳細、条件の整合性、テクスチャの品質などの点で優れています。\n\n\n\n<p align=\"center\">\n  <img src=\"assets/images/system.jpg\">\n</p>\n\n## ☯️ **Hunyuan3D 2.0**\n\n### アーキテクチャ\n\nHunyuan3D 2.0は、ベアメッシュの作成から始まり、そのメッシュのテクスチャマップの合成に至る2段階の生成パイプライン���特徴としています。\nこの戦略は、形状とテクスチャの生成の難しさを分離するのに効果的であり、生成されたメッシュまたは手作りのメッシュのテクスチャリングに柔軟性を提供します。\n\n<p align=\"left\">\n  <img src=\"assets/images/arch.jpg\">\n</p>\n\n### パフォーマンス\n\nHunyuan3D 2.0を他のオープンソースおよびクローズドソースの3D生成方法と比較して評価しました。\n数値結果は、Hunyuan3D 2.0が生成されたテクスチャ付き3Dアセットの品質と条件の遵守能力においてすべてのベースラインを上回っていることを示しています。\n\n| モデル                   | CMMD(⬇)   | FID_CLIP(⬇) | FID(⬇)      | CLIP-score(⬆) |\n|-------------------------|-----------|-------------|-------------|---------------|\n| トップオープンソースモデル1  | 3.591     | 54.639      | 289.287     | 0.787         |\n| トップクローズドソースモデル1 | 3.600     | 55.866      | 305.922     | 0.779         |\n| トップクローズドソースモデル2 | 3.368     | 49.744      | 294.628     | 0.806         |\n| トップクローズドソースモデル3 | 3.218     | 51.574      | 295.691     | 0.799         |\n| Hunyuan3D 2.0           | **3.193** | **49.165**  | **282.429** | **0.809**     |\n\nHunyuan3D 2.0の生成結果：\n<p align=\"left\">\n  <img src=\"assets/images/e2e-1.gif\"  height=250>\n  <img src=\"assets/images/e2e-2.gif\"  height=250>\n</p>\n\n### 事前学習モデル\n\n| モデル                | 日付       | Huggingface                                            |\n|----------------------|------------|--------------------------------------------------------| \n| Hunyuan3D-DiT-v2-0   | 2025-01-21 | [ダウンロード](https://huggingface.co/tencent/Hunyuan3D-2) |\n| Hunyuan3D-Paint-v2-0 | 2025-01-21 | [ダウンロード](https://huggingface.co/tencent/Hunyuan3D-2) |\n\n## 🤗 Hunyuan3D 2.0の使い方\n\n次の手順に従って、コードまたはGradioアプリを使用してHunyuan3D 2.0を使用できます。\n\n### 必要なものをインストール\n\n公式サイトからPytorchをインストールしてください。次に、他の必要なものを以下の方法でインストールします。\n\n```bash\npip install -r requirements.txt\n# for texture\ncd hy3dgen/texgen/custom_rasterizer\npython3 setup.py install\ncd hy3dgen/texgen/differentiable_renderer\npython3 setup.py install\n```\n\n### APIの使い方\n\n形状生成モデル - Hunyuan3D-DiTおよびテクスチャ合成モデル - Hunyuan3D-Paintを使用するためのdiffusersのようなAPIを設計しました。\n\n**Hunyuan3D-DiT**にアクセスするには、次のようにします：\n\n```python\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n```\n\n出力メッシュは[trimeshオブジェクト](https://trimesh.org/trimesh.html)であり、glb/obj（または他の形式）ファイルに保存できます。\n\n**Hunyuan3D-Paint**の場合は、次のようにします：\n\n```python\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\n# まずメッシュを生成しましょう\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n\npipeline = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(mesh, image='assets/demo.png')\n```\n\nより高度な使用法については、[minimal_demo.py](minimal_demo.py)を参照してください。例えば、**テキストから3D**や**手作りメッシュのテクスチャ生成**などです。\n\n### Gradioアプリ\n\n次の方法で自分のコンピュータで[Gradio](https://www.gradio.app/)アプリをホストすることもできます：\n\n```bash\npython3 gradio_app.py\n```\n\n自分でホストしたくない場合は、[Hunyuan3D](https://3d.hunyuan.tencent.com)を訪れてすぐに使用してください。\n\n## 📑 オープンソース計画\n\n- [x] 推論コード\n- [x] モデルチェックポイント\n- [x] 技術報告書\n- [ ] ComfyUI\n- [ ] TensorRTバージョン\n\n## 🔗 BibTeX\n\nこのリポジトリが役に立った場合は、以下の方法で報告書を引用してください：\n\n```bibtex\n@misc{hunyuan3d22025tencent,\n    title={Hunyuan3D 2.0: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2025},\n    eprint={2501.12202},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n\n@misc{yang2024hunyuan3d,\n    title={Hunyuan3D 1.0: A Unified Framework for Text-to-3D and Image-to-3D Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2024},\n    eprint={2411.02293},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n```\n\n## 謝辞\n\n[DINOv2](https://github.com/facebookresearch/dinov2), [Stable Diffusion](https://github.com/Stability-AI/stablediffusion), [FLUX](https://github.com/black-forest-labs/flux), [diffusers](https://github.com/huggingface/diffusers), [HuggingFace](https://huggingface.co), [CraftsMan3D](https://github.com/wyysf-98/CraftsMan3D), and [Michelangelo](https://github.com/NeuralCarver/Michelangelo/tree/main) リポジトリの貢献者に感謝します。\n\n## スター履歴\n\n<a href=\"https://star-history.com/#Tencent/Hunyuan3D-2&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n </picture>\n</a>\n"
  },
  {
    "path": "README_zh_cn.md",
    "content": "[Read in English](README.md)\n[日本語で読む](README_ja_jp.md)\n\n<p align=\"center\">\n  <img src=\"./assets/images/teaser.jpg\">\n\n</p>\n\n<div align=\"center\">\n  <a href=https://3d.hunyuan.tencent.com target=\"_blank\"><img src=https://img.shields.io/badge/Official%20Site-333399.svg?logo=homepage height=22px></a>\n  <a href=https://huggingface.co/spaces/tencent/Hunyuan3D-2  target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Demo-276cb4.svg height=22px></a>\n  <a href=https://huggingface.co/tencent/Hunyuan3D-2 target=\"_blank\"><img src=https://img.shields.io/badge/%F0%9F%A4%97%20Models-d96902.svg height=22px></a>\n  <a href=https://3d-models.hunyuan.tencent.com/ target=\"_blank\"><img src= https://img.shields.io/badge/Page-bb8a2e.svg?logo=github height=22px></a>\n  <a href=https://discord.gg/dNBrdrGGMa target=\"_blank\"><img src= https://img.shields.io/badge/Discord-white.svg?logo=discord height=22px></a>\n  <a href=https://arxiv.org/abs/2501.12202 target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>\n  <a href=https://x.com/txhunyuan target=\"_blank\"><img src=https://img.shields.io/badge/Hunyuan-black.svg?logo=x height=22px></a>\n</div>\n\n\n[//]: # (  <a href=# target=\"_blank\"><img src=https://img.shields.io/badge/Report-b5212f.svg?logo=arxiv height=22px></a>)\n\n[//]: # (  <a href=# target=\"_blank\"><img src= https://img.shields.io/badge/Colab-8f2628.svg?logo=googlecolab height=22px></a>)\n\n[//]: # (  <a href=\"#\"><img alt=\"PyPI - Downloads\" src=\"https://img.shields.io/pypi/v/mulankit?logo=pypi\"  height=22px></a>)\n\n<br>\n\n> 新年快乐!\n\n![happynewyear](https://github.com/user-attachments/assets/69aa40a7-8657-4c2b-8efd-99eda6c26fe4)\n\n\n> 加入我们的 **[微信群](#)** and **[Discord 社区](https://discord.gg/dNBrdrGGMa)** 讨论，获取最新进展以及帮助吧.\n\n| Wechat Group                                     | Xiaohongshu                                           | X                                           | Discord                                           |\n| ------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------- | ------------------------------------------------- |\n| <img src=\"assets/qrcode/wechat.png\"  height=140> | <img src=\"assets/qrcode/xiaohongshu.png\"  height=140> | <img src=\"assets/qrcode/x.png\"  height=140> | <img src=\"assets/qrcode/discord.png\"  height=140> |\n\n---\n\n<br>\n<p align=\"center\">\n“通过 3D 创作与编辑让每个人的想象变成现实。”\n</p>\n\n## 🔥 最新消息\n- Jul 26, 2025: 🤗 我们发布了业界首个开源且兼容主流图形管线的3D世界生成模型 [HunyuanWorld-1.0](https://github.com/Tencent-Hunyuan/HunyuanWorld-1.0)!\n- Jun 13, 2025: 🤗 我们发布了业界首个完全开源支持物理渲染纹理的3D生成模型 [Hunyuan3D 2.1](https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1)!\n- Feb 14, 2025: 🛠️ 发布纹理增强模块，欢迎[体验](minimal_demo.py)高清纹理生成.\n- Jan 27, 2025: 🛠️ 发布 Blender 插件，欢迎[体验](#blender-addon).\n- Jan 23, 2025: 💬 感谢社区成员的 [Windows 安装工具](https://github.com/YanWenKun/Hunyuan3D-2-WinPortable), ComfyUI 支持 [ComfyUI-Hunyuan3DWrapper](https://github.com/kijai/ComfyUI-Hunyuan3DWrapper)， [ComfyUI-3D-Pack](https://github.com/MrForExample/ComfyUI-3D-Pack) 以及其他出色的 [扩展功能](#community-resources).\n- Jan 21, 2025: 💬 欢迎来我们的门户网站 [Hunyuan3D Studio](https://3d.hunyuan.tencent.com) 体验更多3D生成功能!\n- Jan 21, 2025: 💬 我们开源了 [Hunyuan3D 2.0](https://huggingface.co/tencent/Hunyuan3D-2)的推理代码和预训练权重.\n- Jan 21, 2025: 💬 我们发布了 [Hunyuan3D 2.0](https://huggingface.co/spaces/tencent/Hunyuan3D-2). 快来试试吧!\n\n## 概览\n\n混元 3D 2.0 是一款先进的大规模 3D 资产创作系统，它可以用于生成带有高分辨率纹理贴图的高保真度3D模型。该系统包含两个基础组件：一个大规模几何生成模型 — 混元 3D-DiT，以及一个大规模纹理生成模型 — 混元 3D-Paint。\n几何生成模型基于流扩散的扩散模型构建，旨在生成与给定条件图像精确匹配的几何模型，为下游应用奠定坚实基础。\n纹理生成模型得益于强大的几何和扩散模型先验知识，能够为AI生成的或手工制作的网格模型生成高分辨率且生动逼真的纹理贴图。\n此外，我们打造了混元 3D 功能矩阵，一个功能多样、易于使用的创作平台，简化了 3D 模型的制作以及修改过程。它使专业用户和业余爱好者都能高效地对3D模型进行操作，甚至制作动画。\n我们对该系统进行了系统评估，结果表明混元 3D 2.0 在几何细节、条件匹配、纹理质量等方面均优于以往的最先进的开源以及闭源模型。 \n\n<p align=\"center\">\n  <img src=\"assets/images/system.jpg\">\n</p>\n\n## ☯️ **Hunyuan3D 2.0**\n\n### 模型架构\n\n混元 3D 2.0 采用了一个两阶段的生成过程，它首先创建一个无纹理的几何模型，然后为该几何模型合成纹理贴图。这种策略有效地将形状生成和纹理生成的难点分离开来，同时也为生成的几何模型或手工制作的几何模型进行纹理处理提供了灵活性。\n\n<p align=\"left\">\n  <img src=\"assets/images/arch.jpg\">\n</p>\n\n### 性能评估\n\n我们将混元 3D 2.0 与其他开源及闭源的 3D 生成方法进行了评估对比。\n数值结果表明，在生成的带纹理 3D 模型的质量以及对给定条件的遵循能力方面，混元 3D 2.0 超越了所有的基准模型。\n\n| Model                   | CMMD(⬇)   | FID_CLIP(⬇) | FID(⬇)      | CLIP-score(⬆) |\n| ----------------------- | --------- | ----------- | ----------- | ------------- |\n| Top Open-source Model1  | 3.591     | 54.639      | 289.287     | 0.787         |\n| Top Close-source Model1 | 3.600     | 55.866      | 305.922     | 0.779         |\n| Top Close-source Model2 | 3.368     | 49.744      | 294.628     | 0.806         |\n| Top Close-source Model3 | 3.218     | 51.574      | 295.691     | 0.799         |\n| Hunyuan3D 2.0           | **3.193** | **49.165**  | **282.429** | **0.809**     |\n\n一些 Hunyuan3D 2.0 的生成结果:\n<p align=\"left\">\n  <img src=\"assets/images/e2e-1.gif\"  height=300>\n  <img src=\"assets/images/e2e-2.gif\"  height=300>\n</p>\n\n### 预训练模型\n\n| 模型名称               | 发布日期   | 参数 | Huggingface                                                                         |\n| ---------------------- | ---------- | ---- | ----------------------------------------------------------------------------------- |\n| Hunyuan3D-DiT-v2-0     | 2025-01-21 | 2.6B | [下载](https://huggingface.co/tencent/Hunyuan3D-2)                                  |\n| Hunyuan3D-Paint-v2-0   | 2025-01-21 | 1.3B | [下载](https://huggingface.co/tencent/Hunyuan3D-2)                                  |\n| Hunyuan3D-Delight-v2-0 | 2025-01-21 | 1.3B | [下载](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0) |\n\n## 🤗快速入门 Hunyuan3D 2.0\n\n你可以按照以下步骤，通过代码或 Gradio 来使用混元 3D 2.0。\n\n- [代码使用](#代码使用方法)\n- [Gradio](#gradio-app-使用方法)\n- [API服务器](#api-服务器)\n- [Blender插件](#blender-插件)\n- [官方网站](#官方网站)\n\n### 依赖包安装\n\n请通过官方网站安装 PyTorch。然后通过以下方式安装其他所需的依赖项。\n\n```bash\npip install -r requirements.txt\n# for texture\ncd hy3dgen/texgen/custom_rasterizer\npython3 setup.py install\ncd ../../..\ncd hy3dgen/texgen/differentiable_renderer\npython3 setup.py install\n```\n\n\n### 代码使用方法\n\n我们设计了一个类似于 diffusers 的 API 来使用我们的几何生成模型 — 混元 3D-DiT 和纹理合成模型 — 混元 3D-Paint。\n你可以通过以下方式使用 混元 3D-DiT：\n\n```python\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n```\n\n输出的网格是一个 Trimesh 对象，你可以将其保存为 glb/obj（或其他格式）文件。\n对于 混元 3D-Paint，请执行以下操作：\n\n```python\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\n# let's generate a mesh first\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n\npipeline = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(mesh, image='assets/demo.png')\n```\n\n请访问 [minimal_demo.py](minimal_demo.py) 以了解更多高级用法，例如 文本转 3D 以及 为手工制作的网格生成纹理。\n\n### Gradio App 使用方法\n\n你也可以通过以下方式在自己的计算机上托管一个Gradio应用程序：\n\n```bash\npython3 gradio_app.py\n```\n\n### API 服务器\n\n你可以在本地启动一个API服务器，通过它你可以发送将图像/文本转换为3D模型、为现有网格模型添加纹理等的网络请求。\n\n```bash\npython api_server.py --host 0.0.0.0 --port 8080\n```\nA demo post request for image to 3D without texture.\n```bash\nimg_b64_str=$(base64 -i assets/demo.png)\ncurl -X POST \"http://localhost:8080/generate\" \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n           \"image\": \"'\"$img_b64_str\"'\",\n         }' \\\n     -o test2.glb\n```\n\n### Blender 插件\n\nAPI服务器启动后，你还可以通过我们的[Blender插件](blender_addon.py)在Blender中直接使用混元3D 2.0。请按照我们的教程进行安装和使用。\n\nhttps://github.com/user-attachments/assets/8230bfb5-32b1-4e48-91f4-a977c54a4f3e\n\n\n\n### 官方网站\n\n如果你不想自己托管，别忘了访问[混元 3D](https://3d.hunyuan.tencent.com)进行快速使用。\n\n## 📑 开源计划\n\n- [x] 推理代码\n- [x] 模型权重\n- [x] 技术报告\n- [ ] ComfyUI\n- [ ] TensorRT 量化\n\n## 🔗 引用\n\n如果你发现我们的工作有帮助，你可以以下面的方式引用我们的报告：\n\n```bibtex\n@misc{hunyuan3d22025tencent,\n    title={Hunyuan3D 2.0: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2025},\n    eprint={2501.12202},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n\n@misc{yang2024hunyuan3d,\n    title={Hunyuan3D 1.0: A Unified Framework for Text-to-3D and Image-to-3D Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2024},\n    eprint={2411.02293},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n```\n\n## 致谢\n\n我们要感谢以下内容的贡献者： [DINOv2](https://github.com/facebookresearch/dinov2), [Stable Diffusion](https://github.com/Stability-AI/stablediffusion), [FLUX](https://github.com/black-forest-labs/flux), [diffusers](https://github.com/huggingface/diffusers), [HuggingFace](https://huggingface.co), [CraftsMan3D](https://github.com/wyysf-98/CraftsMan3D), 和 [Michelangelo](https://github.com/NeuralCarver/Michelangelo/tree/main) 各研究机构，感谢它们开展公开研究与探索。\n\n## Star 历史\n\n<a href=\"https://star-history.com/#Tencent/Hunyuan3D-2&Date\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=Tencent/Hunyuan3D-2&type=Date\" />\n </picture>\n</a>\n"
  },
  {
    "path": "api_server.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\"\"\"\nA model worker executes the model.\n\"\"\"\nimport argparse\nimport asyncio\nimport base64\nimport logging\nimport logging.handlers\nimport os\nimport sys\nimport tempfile\nimport threading\nimport traceback\nimport uuid\nfrom io import BytesIO\n\nimport torch\nimport trimesh\nimport uvicorn\nfrom PIL import Image\nfrom fastapi import FastAPI, Request\nfrom fastapi.responses import JSONResponse, FileResponse\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline, FloaterRemover, DegenerateFaceRemover, FaceReducer, \\\n    MeshSimplifier\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\nfrom hy3dgen.text2image import HunyuanDiTPipeline\n\nLOGDIR = '.'\n\nserver_error_msg = \"**NETWORK ERROR DUE TO HIGH TRAFFIC. PLEASE REGENERATE OR REFRESH THIS PAGE.**\"\nmoderation_msg = \"YOUR INPUT VIOLATES OUR CONTENT MODERATION GUIDELINES. PLEASE TRY AGAIN.\"\n\nhandler = None\n\n\ndef build_logger(logger_name, logger_filename):\n    global handler\n\n    formatter = logging.Formatter(\n        fmt=\"%(asctime)s | %(levelname)s | %(name)s | %(message)s\",\n        datefmt=\"%Y-%m-%d %H:%M:%S\",\n    )\n\n    # Set the format of root handlers\n    if not logging.getLogger().handlers:\n        logging.basicConfig(level=logging.INFO)\n    logging.getLogger().handlers[0].setFormatter(formatter)\n\n    # Redirect stdout and stderr to loggers\n    stdout_logger = logging.getLogger(\"stdout\")\n    stdout_logger.setLevel(logging.INFO)\n    sl = StreamToLogger(stdout_logger, logging.INFO)\n    sys.stdout = sl\n\n    stderr_logger = logging.getLogger(\"stderr\")\n    stderr_logger.setLevel(logging.ERROR)\n    sl = StreamToLogger(stderr_logger, logging.ERROR)\n    sys.stderr = sl\n\n    # Get logger\n    logger = logging.getLogger(logger_name)\n    logger.setLevel(logging.INFO)\n\n    # Add a file handler for all loggers\n    if handler is None:\n        os.makedirs(LOGDIR, exist_ok=True)\n        filename = os.path.join(LOGDIR, logger_filename)\n        handler = logging.handlers.TimedRotatingFileHandler(\n            filename, when='D', utc=True, encoding='UTF-8')\n        handler.setFormatter(formatter)\n\n        for name, item in logging.root.manager.loggerDict.items():\n            if isinstance(item, logging.Logger):\n                item.addHandler(handler)\n\n    return logger\n\n\nclass StreamToLogger(object):\n    \"\"\"\n    Fake file-like stream object that redirects writes to a logger instance.\n    \"\"\"\n\n    def __init__(self, logger, log_level=logging.INFO):\n        self.terminal = sys.stdout\n        self.logger = logger\n        self.log_level = log_level\n        self.linebuf = ''\n\n    def __getattr__(self, attr):\n        return getattr(self.terminal, attr)\n\n    def write(self, buf):\n        temp_linebuf = self.linebuf + buf\n        self.linebuf = ''\n        for line in temp_linebuf.splitlines(True):\n            # From the io.TextIOWrapper docs:\n            #   On output, if newline is None, any '\\n' characters written\n            #   are translated to the system default line separator.\n            # By default sys.stdout.write() expects '\\n' newlines and then\n            # translates them so this is still cross platform.\n            if line[-1] == '\\n':\n                self.logger.log(self.log_level, line.rstrip())\n            else:\n                self.linebuf += line\n\n    def flush(self):\n        if self.linebuf != '':\n            self.logger.log(self.log_level, self.linebuf.rstrip())\n        self.linebuf = ''\n\n\ndef pretty_print_semaphore(semaphore):\n    if semaphore is None:\n        return \"None\"\n    return f\"Semaphore(value={semaphore._value}, locked={semaphore.locked()})\"\n\n\nSAVE_DIR = 'gradio_cache'\nos.makedirs(SAVE_DIR, exist_ok=True)\n\nworker_id = str(uuid.uuid4())[:6]\nlogger = build_logger(\"controller\", f\"{SAVE_DIR}/controller.log\")\n\n\ndef load_image_from_base64(image):\n    return Image.open(BytesIO(base64.b64decode(image)))\n\n\nclass ModelWorker:\n    def __init__(self,\n                 model_path='tencent/Hunyuan3D-2mini',\n                 tex_model_path='tencent/Hunyuan3D-2',\n                 subfolder='hunyuan3d-dit-v2-mini-turbo',\n                 device='cuda',\n                 enable_tex=False):\n        self.model_path = model_path\n        self.worker_id = worker_id\n        self.device = device\n        logger.info(f\"Loading the model {model_path} on worker {worker_id} ...\")\n\n        self.rembg = BackgroundRemover()\n        self.pipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n            model_path,\n            subfolder=subfolder,\n            use_safetensors=True,\n            device=device,\n        )\n        self.pipeline.enable_flashvdm(mc_algo='mc')\n        # self.pipeline_t2i = HunyuanDiTPipeline(\n        #     'Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled',\n        #     device=device\n        # )\n        if enable_tex:\n            self.pipeline_tex = Hunyuan3DPaintPipeline.from_pretrained(tex_model_path)\n\n    def get_queue_length(self):\n        if model_semaphore is None:\n            return 0\n        else:\n            return args.limit_model_concurrency - model_semaphore._value + (len(\n                model_semaphore._waiters) if model_semaphore._waiters is not None else 0)\n\n    def get_status(self):\n        return {\n            \"speed\": 1,\n            \"queue_length\": self.get_queue_length(),\n        }\n\n    @torch.inference_mode()\n    def generate(self, uid, params):\n        if 'image' in params:\n            image = params[\"image\"]\n            image = load_image_from_base64(image)\n        else:\n            if 'text' in params:\n                text = params[\"text\"]\n                image = self.pipeline_t2i(text)\n            else:\n                raise ValueError(\"No input image or text provided\")\n\n        image = self.rembg(image)\n        params['image'] = image\n\n        if 'mesh' in params:\n            mesh = trimesh.load(BytesIO(base64.b64decode(params[\"mesh\"])), file_type='glb')\n        else:\n            seed = params.get(\"seed\", 1234)\n            params['generator'] = torch.Generator(self.device).manual_seed(seed)\n            params['octree_resolution'] = params.get(\"octree_resolution\", 128)\n            params['num_inference_steps'] = params.get(\"num_inference_steps\", 5)\n            params['guidance_scale'] = params.get('guidance_scale', 5.0)\n            params['mc_algo'] = 'mc'\n            import time\n            start_time = time.time()\n            mesh = self.pipeline(**params)[0]\n            logger.info(\"--- %s seconds ---\" % (time.time() - start_time))\n\n        if params.get('texture', False):\n            mesh = FloaterRemover()(mesh)\n            mesh = DegenerateFaceRemover()(mesh)\n            mesh = FaceReducer()(mesh, max_facenum=params.get('face_count', 40000))\n            mesh = self.pipeline_tex(mesh, image)\n\n        type = params.get('type', 'glb')\n        with tempfile.NamedTemporaryFile(suffix=f'.{type}', delete=False) as temp_file:\n            mesh.export(temp_file.name)\n            mesh = trimesh.load(temp_file.name)\n            save_path = os.path.join(SAVE_DIR, f'{str(uid)}.{type}')\n            mesh.export(save_path)\n\n        torch.cuda.empty_cache()\n        return save_path, uid\n\n\napp = FastAPI()\nfrom fastapi.middleware.cors import CORSMiddleware\n\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=[\"*\"],  # 你可以指定允许的来源\n    allow_credentials=True,\n    allow_methods=[\"*\"],  # 允许所有方法\n    allow_headers=[\"*\"],  # 允许所有头部\n)\n\n\n@app.post(\"/generate\")\nasync def generate(request: Request):\n    logger.info(\"Worker generating...\")\n    params = await request.json()\n    uid = uuid.uuid4()\n    try:\n        file_path, uid = worker.generate(uid, params)\n        return FileResponse(file_path)\n    except ValueError as e:\n        traceback.print_exc()\n        print(\"Caught ValueError:\", e)\n        ret = {\n            \"text\": server_error_msg,\n            \"error_code\": 1,\n        }\n        return JSONResponse(ret, status_code=404)\n    except torch.cuda.CudaError as e:\n        print(\"Caught torch.cuda.CudaError:\", e)\n        ret = {\n            \"text\": server_error_msg,\n            \"error_code\": 1,\n        }\n        return JSONResponse(ret, status_code=404)\n    except Exception as e:\n        print(\"Caught Unknown Error\", e)\n        traceback.print_exc()\n        ret = {\n            \"text\": server_error_msg,\n            \"error_code\": 1,\n        }\n        return JSONResponse(ret, status_code=404)\n\n\n@app.post(\"/send\")\nasync def generate(request: Request):\n    logger.info(\"Worker send...\")\n    params = await request.json()\n    uid = uuid.uuid4()\n    threading.Thread(target=worker.generate, args=(uid, params,)).start()\n    ret = {\"uid\": str(uid)}\n    return JSONResponse(ret, status_code=200)\n\n\n@app.get(\"/status/{uid}\")\nasync def status(uid: str):\n    save_file_path = os.path.join(SAVE_DIR, f'{uid}.glb')\n    print(save_file_path, os.path.exists(save_file_path))\n    if not os.path.exists(save_file_path):\n        response = {'status': 'processing'}\n        return JSONResponse(response, status_code=200)\n    else:\n        base64_str = base64.b64encode(open(save_file_path, 'rb').read()).decode()\n        response = {'status': 'completed', 'model_base64': base64_str}\n        return JSONResponse(response, status_code=200)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--host\", type=str, default=\"0.0.0.0\")\n    parser.add_argument(\"--port\", type=int, default=8081)\n    parser.add_argument(\"--model_path\", type=str, default='tencent/Hunyuan3D-2mini')\n    parser.add_argument(\"--tex_model_path\", type=str, default='tencent/Hunyuan3D-2')\n    parser.add_argument(\"--device\", type=str, default=\"cuda\")\n    parser.add_argument(\"--limit-model-concurrency\", type=int, default=5)\n    parser.add_argument('--enable_tex', action='store_true')\n    args = parser.parse_args()\n    logger.info(f\"args: {args}\")\n\n    model_semaphore = asyncio.Semaphore(args.limit_model_concurrency)\n\n    worker = ModelWorker(model_path=args.model_path, device=args.device, enable_tex=args.enable_tex,\n                         tex_model_path=args.tex_model_path)\n    uvicorn.run(app, host=args.host, port=args.port, log_level=\"info\")\n"
  },
  {
    "path": "assets/example_prompts.txt",
    "content": "一片绿色的树叶在白色背景上居中展现，清晰的纹理\n一只棕白相间的仓鼠，站在白色背景前。照片采用居中构图方式，卡通风格\n一盆绿色植物生长在红色花盆中，居中，写实\na pot of green plants grows in a red flower pot.\na lovely rabbit eating carrots\n"
  },
  {
    "path": "assets/modelviewer-template.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <!-- Import the component -->\n    <script src=\"https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js\" type=\"module\"></script>\n\n    <script>\n        document.addEventListener('DOMContentLoaded', () => {\n            const modelViewers = document.querySelectorAll('model-viewer');\n            const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n\n            modelViewers.forEach(modelViewer => {\n                modelViewer.setAttribute(\n                    \"environment-image\",\n                    \"/static/env_maps/gradient.jpg\"\n                );\n                // if (!isSafari) {\n                //     modelViewer.setAttribute(\n                //         \"environment-image\",\n                //         \"/static/env_maps/gradient.jpg\"\n                //     );\n                // } else {\n                //     modelViewer.addEventListener('load', (event) => {\n                //         const [material] = modelViewer.model.materials;\n                //         let color = [43, 44, 46, 255];\n                //         color = color.map(x => x / 255);\n                //         material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属\n                //         material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度\n                //         material.pbrMetallicRoughness.setBaseColorFactor(color);  // CornflowerBlue in RGB\n                //     });\n                // }\n                // modelViewer.addEventListener('load', (event) => {\n                //     const [material] = modelViewer.model.materials;\n                //     let color = [43, 44, 46, 255];\n                //     color = color.map(x => x / 255);\n                //     material.pbrMetallicRoughness.setMetallicFactor(0.1); // 完全金属\n                //     material.pbrMetallicRoughness.setRoughnessFactor(0.7); // 低粗糙度\n                //     material.pbrMetallicRoughness.setBaseColorFactor(color);  // CornflowerBlue in RGB\n                // });\n            });\n        });\n    </script>\n\n    <style>\n        body {\n            margin: 0;\n            font-family: Arial, sans-serif;\n        }\n\n        .centered-container {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            border-radius: 8px;\n            border-color: #e5e7eb;\n            border-style: solid;\n            border-width: 1px;\n        }\n    </style>\n</head>\n\n<body>\n<div class=\"centered-container\">\n    <div class=\"column is-mobile is-centered\">\n        <model-viewer id=\"modelviewer\" style=\"height: #height#px; width: #width#px;\"\n                      rotation-per-second=\"10deg\"\n                      src=\"#src#\" disable-tap\n                      environment-image=\"neutral\"\n                      camera-target=\"0m 0m 0m\"\n                      camera-orbit=\"0deg 90deg 8m\"\n                      orientation=\"0deg 0deg 0deg\"\n                      shadow-intensity=\".9\"\n                      ar auto-rotate\n                      camera-controls>\n        </model-viewer>\n    </div>\n</div>\n</body>\n\n</html>"
  },
  {
    "path": "assets/modelviewer-textured-template.html",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n    <!-- Import the component -->\n    <script src=\"https://cdn.jsdelivr.net/npm/@google/model-viewer@3.1.1/dist/model-viewer.min.js\"\n            type=\"module\"></script>\n    <style>\n        body {\n            margin: 0;\n            font-family: Arial, sans-serif;\n        }\n\n        .centered-container {\n            display: flex;\n            justify-content: center;\n            align-items: center;\n        }\n\n        .modelviewer-panel-button {\n            height: 30px;\n            margin: 4px 4px;\n            padding: 0px 14px;\n            background: white;\n            border-radius: 10px;\n            box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25);\n            font-size: 14px;\n            font-weight: 600;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            cursor: pointer;\n            transition: all 0.2s ease;\n        }\n\n        .modelviewer-panel-button.checked {\n            background: #6567C9;\n            color: white;\n        }\n\n        .modelviewer-panel-button:hover {\n            background-color: #e2e6ea;\n        }\n\n        .modelviewer-panel-button-container {\n            display: flex;\n            justify-content: space-around;\n        }\n\n        .centered-container {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n        }\n\n    </style>\n</head>\n\n<body>\n<div class=\"centered-container\">\n    <div class=\"centered-container\">\n        <div class=\"column is-mobile is-centered\">\n            <model-viewer id=\"modelviewer\" style=\"height: #height#px; width: #width#px;\"\n                          rotation-per-second=\"10deg\"\n                          src=\"#src#\" disable-tap\n                          environment-image=\"neutral\"\n                          camera-target=\"0m 0m 0m\"\n                          camera-orbit=\"0deg 90deg 12m\"\n                          orientation=\"0deg 0deg 0deg\"\n                          shadow-intensity=\".9\"\n                          ar auto-rotate\n                          camera-controls>\n            </model-viewer>\n        </div>\n\n        <div class=\"modelviewer-panel-button-container\">\n            <div id=\"appearance-button\" class=\"modelviewer-panel-button small checked\" onclick=\"showTexture()\">\n                Appearance\n            </div>\n            <div id=\"geometry-button\" class=\"modelviewer-panel-button small\" onclick=\"hideTexture()\">Geometry</div>\n        </div>\n    </div>\n</div>\n\n<script>\n    document.addEventListener('DOMContentLoaded', () => {\n        const modelViewers = document.querySelectorAll('model-viewer');\n\n        modelViewers.forEach(modelViewer => {\n            modelViewer.addEventListener('load', (event) => {\n                const [material] = modelViewer.model.materials;\n                material.pbrMetallicRoughness.setMetallicFactor(0.1);\n                material.pbrMetallicRoughness.setRoughnessFactor(0.5);\n            });\n        });\n    });\n\n    var window_state = {};\n\n    function hideTexture() {\n        let appearanceButton = document.getElementById('appearance-button');\n        let geometryButton = document.getElementById('geometry-button');\n        appearanceButton.classList.remove('checked');\n        geometryButton.classList.add('checked');\n        let modelViewer = document.getElementById('modelviewer');\n        if (modelViewer.model.materials[0].pbrMetallicRoughness.baseColorTexture.texture === null) return;\n        window_state.textures = [];\n        for (let i = 0; i < modelViewer.model.materials.length; i++) {\n            window_state.textures.push(modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.texture);\n        }\n        window_state.exposure = modelViewer.exposure;\n        modelViewer.environmentImage = '/static/env_maps/gradient.jpg';\n        for (let i = 0; i < modelViewer.model.materials.length; i++) {\n            modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.setTexture(null);\n        }\n        modelViewer.exposure = 4;\n    }\n\n    function showTexture() {\n        let appearanceButton = document.getElementById('appearance-button');\n        let geometryButton = document.getElementById('geometry-button');\n        appearanceButton.classList.add('checked');\n        geometryButton.classList.remove('checked');\n        let modelViewer = document.getElementById('modelviewer');\n        if (modelViewer.model.materials[0].pbrMetallicRoughness.baseColorTexture.texture !== null) return;\n        modelViewer.environmentImage = '/static/env_maps/white.jpg';\n        for (let i = 0; i < modelViewer.model.materials.length; i++) {\n            modelViewer.model.materials[i].pbrMetallicRoughness.baseColorTexture.setTexture(window_state.textures[i]);\n        }\n        modelViewer.exposure = window_state.exposure;\n    }\n\n</script>\n</body>\n\n</html>"
  },
  {
    "path": "blender_addon.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nbl_info = {\n    \"name\": \"Hunyuan3D-2 Generator\",\n    \"author\": \"Tencent Hunyuan3D\",\n    \"version\": (1, 0),\n    \"blender\": (3, 0, 0),\n    \"location\": \"View3D > Sidebar > Hunyuan3D-2 3D Generator\",\n    \"description\": \"Generate/Texturing 3D models from text descriptions or images\",\n    \"category\": \"3D View\",\n}\nimport base64\nimport os\nimport tempfile\nimport threading\n\nimport bpy\nimport requests\nfrom bpy.props import StringProperty, BoolProperty, IntProperty, FloatProperty\n\n\nclass Hunyuan3DProperties(bpy.types.PropertyGroup):\n    prompt: StringProperty(\n        name=\"Text Prompt\",\n        description=\"Describe what you want to generate\",\n        default=\"\"\n    )\n    api_url: StringProperty(\n        name=\"API URL\",\n        description=\"URL of the Text-to-3D API service\",\n        default=\"http://localhost:8080\"\n    )\n    is_processing: BoolProperty(\n        name=\"Processing\",\n        default=False\n    )\n    job_id: StringProperty(\n        name=\"Job ID\",\n        default=\"\"\n    )\n    status_message: StringProperty(\n        name=\"Status Message\",\n        default=\"\"\n    )\n    # 添加图片路径属性\n    image_path: StringProperty(\n        name=\"Image\",\n        description=\"Select an image to upload\",\n        subtype='FILE_PATH'\n    )\n    # 修改后的 octree_resolution 属性\n    octree_resolution: IntProperty(\n        name=\"Octree Resolution\",\n        description=\"Octree resolution for the 3D generation\",\n        default=256,\n        min=128,\n        max=512,\n    )\n    num_inference_steps: IntProperty(\n        name=\"Number of Inference Steps\",\n        description=\"Number of inference steps for the 3D generation\",\n        default=20,\n        min=20,\n        max=50\n    )\n    guidance_scale: FloatProperty(\n        name=\"Guidance Scale\",\n        description=\"Guidance scale for the 3D generation\",\n        default=5.5,\n        min=1.0,\n        max=10.0\n    )\n    # 添加 texture 属性\n    texture: BoolProperty(\n        name=\"Generate Texture\",\n        description=\"Whether to generate texture for the 3D model\",\n        default=False\n    )\n\n\nclass Hunyuan3DOperator(bpy.types.Operator):\n    bl_idname = \"object.generate_3d\"\n    bl_label = \"Generate 3D Model\"\n    bl_description = \"Generate a 3D model from text description, an image or a selected mesh\"\n\n    job_id = ''\n    prompt = \"\"\n    api_url = \"\"\n    image_path = \"\"\n    octree_resolution = 256\n    num_inference_steps = 20\n    guidance_scale = 5.5\n    texture = False  # 新增属性\n    selected_mesh_base64 = \"\"\n    selected_mesh = None  # 新增属性，用于存储选中的 mesh\n\n    thread = None\n    task_finished = False\n\n    def modal(self, context, event):\n        if event.type in {'RIGHTMOUSE', 'ESC'}:\n            return {'CANCELLED'}\n\n        if self.task_finished:\n            print(\"Threaded task completed\")\n            self.task_finished = False\n            props = context.scene.gen_3d_props\n            props.is_processing = False\n\n        return {'PASS_THROUGH'}\n\n    def invoke(self, context, event):\n        # 启动线程\n        props = context.scene.gen_3d_props\n        self.prompt = props.prompt\n        self.api_url = props.api_url\n        self.image_path = props.image_path\n        self.octree_resolution = props.octree_resolution\n        self.num_inference_steps = props.num_inference_steps\n        self.guidance_scale = props.guidance_scale\n        self.texture = props.texture  # 获取 texture 属性的值\n\n        if self.prompt == \"\" and self.image_path == \"\":\n            self.report({'WARNING'}, \"Please enter some text or select an image first.\")\n            return {'FINISHED'}\n\n        # 保存选中的 mesh 对象引用\n        for obj in context.selected_objects:\n            if obj.type == 'MESH':\n                self.selected_mesh = obj\n                break\n\n        if self.selected_mesh:\n            temp_glb_file = tempfile.NamedTemporaryFile(delete=False, suffix=\".glb\")\n            temp_glb_file.close()\n            bpy.ops.export_scene.gltf(filepath=temp_glb_file.name, use_selection=True)\n            with open(temp_glb_file.name, \"rb\") as file:\n                mesh_data = file.read()\n            mesh_b64_str = base64.b64encode(mesh_data).decode()\n            os.unlink(temp_glb_file.name)\n            self.selected_mesh_base64 = mesh_b64_str\n\n        props.is_processing = True\n\n        # 将相对路径转换为相对于 Blender 文件所在目录的绝对路径\n        blend_file_dir = os.path.dirname(bpy.data.filepath)\n        self.report({'INFO'}, f\"blend_file_dir {blend_file_dir}\")\n        self.report({'INFO'}, f\"image_path {self.image_path}\")\n        if self.image_path.startswith('//'):\n            self.image_path = self.image_path[2:]\n            self.image_path = os.path.join(blend_file_dir, self.image_path)\n\n        if self.selected_mesh and self.texture:\n            props.status_message = \"Texturing Selected Mesh...\\n\" \\\n                                   \"This may take several minutes depending \\n on your GPU power.\"\n        else:\n            mesh_type = 'Textured Mesh' if self.texture else 'White Mesh'\n            prompt_type = 'Text Prompt' if self.prompt else 'Image'\n            props.status_message = f\"Generating {mesh_type} with {prompt_type}...\\n\" \\\n                                   \"This may take several minutes depending \\n on your GPU power.\"\n\n        self.thread = threading.Thread(target=self.generate_model, args=[context])\n        self.thread.start()\n\n        wm = context.window_manager\n        wm.modal_handler_add(self)\n        return {'RUNNING_MODAL'}\n\n    def generate_model(self, context):\n        self.report({'INFO'}, f\"Generation Start\")\n        base_url = self.api_url.rstrip('/')\n\n        try:\n            if self.selected_mesh_base64 and self.texture:\n                # Texturing the selected mesh\n                if self.image_path and os.path.exists(self.image_path):\n                    self.report({'INFO'}, f\"Post Texturing with Image\")\n                    # 打开图片文件并以二进制模式读取\n                    with open(self.image_path, \"rb\") as file:\n                        # 读取文件内容\n                        image_data = file.read()\n                    # 对图片数据进行 Base64 编码\n                    img_b64_str = base64.b64encode(image_data).decode()\n                    response = requests.post(\n                        f\"{base_url}/generate\",\n                        json={\n                            \"mesh\": self.selected_mesh_base64,\n                            \"image\": img_b64_str,\n                            \"octree_resolution\": self.octree_resolution,\n                            \"num_inference_steps\": self.num_inference_steps,\n                            \"guidance_scale\": self.guidance_scale,\n                            \"texture\": self.texture  # 传递 texture 参数\n                        },\n                    )\n                else:\n                    self.report({'INFO'}, f\"Post Texturing with Text\")\n                    response = requests.post(\n                        f\"{base_url}/generate\",\n                        json={\n                            \"mesh\": self.selected_mesh_base64,\n                            \"text\": self.prompt,\n                            \"octree_resolution\": self.octree_resolution,\n                            \"num_inference_steps\": self.num_inference_steps,\n                            \"guidance_scale\": self.guidance_scale,\n                            \"texture\": self.texture  # 传递 texture 参数\n                        },\n                    )\n            else:\n                if self.image_path:\n                    if not os.path.exists(self.image_path):\n                        self.report({'ERROR'}, f\"Image path does not exist {self.image_path}\")\n                        raise Exception(f'Image path does not exist {self.image_path}')\n                    self.report({'INFO'}, f\"Post Start Image to 3D\")\n                    # 打开图片文件并以二进制模式读取\n                    with open(self.image_path, \"rb\") as file:\n                        # 读取文件内容\n                        image_data = file.read()\n                    # 对图片数据进行 Base64 编码\n                    img_b64_str = base64.b64encode(image_data).decode()\n                    response = requests.post(\n                        f\"{base_url}/generate\",\n                        json={\n                            \"image\": img_b64_str,\n                            \"octree_resolution\": self.octree_resolution,\n                            \"num_inference_steps\": self.num_inference_steps,\n                            \"guidance_scale\": self.guidance_scale,\n                            \"texture\": self.texture  # 传递 texture 参数\n                        },\n                    )\n                else:\n                    self.report({'INFO'}, f\"Post Start Text to 3D\")\n                    response = requests.post(\n                        f\"{base_url}/generate\",\n                        json={\n                            \"text\": self.prompt,\n                            \"octree_resolution\": self.octree_resolution,\n                            \"num_inference_steps\": self.num_inference_steps,\n                            \"guidance_scale\": self.guidance_scale,\n                            \"texture\": self.texture  # 传递 texture 参数\n                        },\n                    )\n            self.report({'INFO'}, f\"Post Done\")\n            self.task_finished = True\n            props = context.scene.gen_3d_props\n            props.is_processing = False\n\n            if response.status_code != 200:\n                self.report({'ERROR'}, f\"Generation failed: {response.text}\")\n                return\n\n            # Decode base64 and save to temporary file\n            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=\".glb\")\n            temp_file.write(response.content)\n            temp_file.close()\n\n            # Import the GLB file in the main thread\n            def import_handler():\n                bpy.ops.import_scene.gltf(filepath=temp_file.name)\n                os.unlink(temp_file.name)\n\n                # 获取新导入的 mesh\n                new_obj = bpy.context.selected_objects[0] if bpy.context.selected_objects else None\n                if new_obj and self.selected_mesh and self.texture:\n                    # 应用选中 mesh 的位置、旋转和缩放\n                    new_obj.location = self.selected_mesh.location\n                    new_obj.rotation_euler = self.selected_mesh.rotation_euler\n                    new_obj.scale = self.selected_mesh.scale\n\n                    # 隐藏原来的 mesh\n                    self.selected_mesh.hide_set(True)\n                    self.selected_mesh.hide_render = True\n\n                return None\n\n            bpy.app.timers.register(import_handler)\n\n        except Exception as e:\n            self.report({'ERROR'}, f\"Error: {str(e)}\")\n\n        finally:\n            self.task_finished = True\n            props = context.scene.gen_3d_props\n            props.is_processing = False\n            self.selected_mesh_base64 = \"\"\n\n\nclass Hunyuan3DPanel(bpy.types.Panel):\n    bl_space_type = 'VIEW_3D'\n    bl_region_type = 'UI'\n    bl_category = 'Hunyuan3D-2'\n    bl_label = 'Hunyuan3D-2 3D Generator'\n\n    def draw(self, context):\n        layout = self.layout\n        props = context.scene.gen_3d_props\n\n        layout.prop(props, \"api_url\")\n        layout.prop(props, \"prompt\")\n        # 添加图片选择器\n        layout.prop(props, \"image_path\")\n        # 添加新属性的 UI 元素\n        layout.prop(props, \"octree_resolution\")\n        layout.prop(props, \"num_inference_steps\")\n        layout.prop(props, \"guidance_scale\")\n        # 添加 texture 属性的 UI 元素\n        layout.prop(props, \"texture\")\n\n        row = layout.row()\n        row.enabled = not props.is_processing\n        row.operator(\"object.generate_3d\")\n\n        if props.is_processing:\n            if props.status_message:\n                for line in props.status_message.split(\"\\n\"):\n                    layout.label(text=line)\n            else:\n                layout.label(\"Processing...\")\n\n\nclasses = (\n    Hunyuan3DProperties,\n    Hunyuan3DOperator,\n    Hunyuan3DPanel,\n)\n\n\ndef register():\n    for cls in classes:\n        bpy.utils.register_class(cls)\n    bpy.types.Scene.gen_3d_props = bpy.props.PointerProperty(type=Hunyuan3DProperties)\n\n\ndef unregister():\n    for cls in reversed(classes):\n        bpy.utils.unregister_class(cls)\n    del bpy.types.Scene.gen_3d_props\n\n\nif __name__ == \"__main__\":\n    register()\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = source\nBUILDDIR      = build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Documentation\n\nSource of the documentation.\n\n- Build html.\n\n```bash\nmake html\n```\n\n- Preview at `build/html/index.html`\n\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=source\nset BUILDDIR=build\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.https://www.sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\n\n:end\npopd\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "myst-parser\nsphinx-rtd-theme\nfuro\nsphinx-copybutton\nsphinx-inline-tabs\nnbsphinx\nnbsphinx_link\nlinkify-it-py\nlinkify\nipython\n\ntorch\nimageio\nscikit_image\nmatplotlib\nmunch\ntfpnp\ncvxpy\ntorchlights\ntensorboardX\ntermcolor\nproximal\nopencv-python\nhuggingface_hub\ntorchvision"
  },
  {
    "path": "docs/source/_static/css/custom.css",
    "content": "\n/*.sidebar-logo {*/\n/*    display: block;*/\n/*    margin: 0;*/\n/*    max-width: 50%;*/\n/*}*/\n\n.nbsphinx-gallery {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n    gap: 5px;\n    margin-top: 1em;\n    margin-bottom: 1em;\n}\n\nh1 {\n    font-size: 2em\n}\n\nh2 {\n    font-size: 1.3em\n}\n\nh3 {\n    font-size: 1.25em\n}\n\nh4 {\n    font-size: 1.125em\n}\n\nh5 {\n    font-size: 1.07em\n}\n\nh6 {\n    font-size: 1em\n}"
  },
  {
    "path": "docs/source/citation.md",
    "content": "# Citation\n\nThe following publications discuss the ideas behind Hunyuan3D-2:\n\n> **Unleashing Vecset Diffusion Model for Fast Shape Generation** </br>\n> Technical Report, 2025.\n\n> **Hunyuan3D 2.0: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation** </br>\n> Technical Report, 2024.\n\n> **Hunyuan3D 1.0: A Unified Framework for Text-to-3D and Image-to-3D Generation** </br>\n> Technical Report, 2024.\n\nIf you found this repository helpful, please cite our reports:\n\n```bibtex\n@misc{lai2025flashvdm,\n      title={Unleashing Vecset Diffusion Model for Fast Shape Generation}, \n      author={Zeqiang Lai and Yunfei Zhao and Zibo Zhao and Haolin Liu and Fuyun Wang and Huiwen Shi and Xianghui Yang and Qinxiang Lin and Jinwei Huang and Yuhong Liu and Jie Jiang and Chunchao Guo and Xiangyu Yue},\n      year={2025},\n      eprint={2503.16302},\n      archivePrefix={arXiv},\n      primaryClass={cs.CV},\n      url={https://arxiv.org/abs/2503.16302}, \n}\n@misc{hunyuan3d22025tencent,\n    title={Hunyuan3D 2.0: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2025},\n    eprint={2501.12202},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n\n@misc{yang2024hunyuan3d,\n    title={Hunyuan3D 1.0: A Unified Framework for Text-to-3D and Image-to-3D Generation},\n    author={Tencent Hunyuan3D Team},\n    year={2024},\n    eprint={2411.02293},\n    archivePrefix={arXiv},\n    primaryClass={cs.CV}\n}\n```\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath(\".\"))\nsys.path.insert(0, os.path.abspath(\"../../\"))\n\n# -- Project information -----------------------------------------------------\n\nproject = 'Hunyuan3D-2'\ncopyright = '2025, Tencent Hunyuan3D'\nauthor = 'Hunyuan3D Team'\n\n# The full version, including alpha/beta/rc tags\nrelease = '0.0.1'\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'myst_parser',\n    'nbsphinx',\n    'nbsphinx_link',\n    # \"myst_nb\",\n    'sphinx_copybutton',\n    # \"sphinx_inline_tabs\",\n    # https://sphinx-codeautolink.readthedocs.io/en/latest/examples.html\n    'sphinx.ext.autodoc',\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.extlinks\",\n    'sphinx.ext.autosummary',\n    'sphinx.ext.doctest',\n    'sphinx.ext.todo',\n    'sphinx.ext.coverage',\n    'sphinx.ext.mathjax',\n    'sphinx.ext.viewcode',\n    'sphinx.ext.napoleon',\n]\n\n# -- Options for extlinks ----------------------------------------------------\n#\n\nextlinks = {\n    \"pypi\": (\"https://pypi.org/project/%s/\", \"%s\"),\n}\n\n# -- Options for intersphinx -------------------------------------------------\n#\n\nintersphinx_mapping = {\n    \"python\": (\"https://docs.python.org/3\", None),\n    \"sphinx\": (\"https://www.sphinx-doc.org/en/master\", None),\n    'torch': ('https://pytorch.org/docs/master/', None)\n}\n\nnapoleon_preprocess_types = True\n\nmyst_enable_extensions = [\n    \"amsmath\",\n    \"colon_fence\",\n    \"deflist\",\n    \"dollarmath\",\n    \"fieldlist\",\n    \"html_admonition\",\n    \"html_image\",\n    \"linkify\",\n    \"replacements\",\n    \"smartquotes\",\n    \"strikethrough\",\n    \"substitution\",\n    \"tasklist\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns = []\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# -- Options for HTML output -------------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\n# html_theme = 'alabaster'\n# html_theme = 'sphinx_rtd_theme'\nhtml_theme = \"furo\"\nhtml_title = \"Hunyuan3D-2\"\nlanguage = \"en\"\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\nhtml_theme_options = {\n    \"light_css_variables\": {\n        \"font-stack\": \"Arial,Noto Sans,sans-serif\",\n        \"font-stack--monospace\": \"IBM Plex Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace\",\n    },\n    \"announcement\": 'Release 🤗<a href=\"https://huggingface.co/spaces/tencent/Hunyuan3D-2mini-Turbo\">Turbo Series</a> and <a href=\"https://github.com/Tencent/FlashVDM\">FlashVDM</a>, Fast Shape Generation within 1 Second Right Now!',\n}\n\n#\n# -- Options for TODOs -------------------------------------------------------\n#\ntodo_include_todos = True\n\n#\n# -- Options for Markdown files ----------------------------------------------\n#\nmyst_admonition_enable = True\nmyst_deflist_enable = True\nmyst_heading_anchors = 3\n\nhtml_favicon = '_static/favicon.ico'\n\npygments_style = \"default\"\npygments_dark_style = \"github-dark\"\n\nhtml_css_files = [\n    'css/custom.css',\n]\n"
  },
  {
    "path": "docs/source/index.md",
    "content": "---\nhide-toc: true\n---\n\n# Welcome to Hunyuan3D\n\n```{toctree}\n:maxdepth: 3\n:hidden: \n\nInstallation <installation/index>\nGet Started <started/index>\nModel Zoo <modelzoo>\ncitation\n```\n\n```{toctree}\n:caption: Useful Links\n:hidden:\nPyPI Page <https://pypi.org/project/hy3dgen/>\nGitHub Repository <https://github.com/Tencent/Hunyuan3D-2>\nPaper <https://arxiv.org/abs/2501.12202>\n```\n\n<br/>\n\n☯️ Hunyuan3D 2.0 is an advanced large-scale 3D synthesis system for generating high-resolution textured 3D assets.\n\nThe system includes the following foundation components:\n\n1. [Hunyuan3D-DiT](): a large-scale shape generation model.\n2. [Hunyuan3D-Paint](): a large-scale texture synthesis model.\n3. [Hunyuan3D-Studio](): a versatile, user-friendly production platform that simplifies the re-creation process of 3D\n   assets. It allows both professional and amateur users to manipulate or even animate their meshes efficiently.\n4. [FlashVDM](): a universal acceleration framework.\n\n```{nbgallery}\n```\n\n<div class=\"toctree-wrapper compound\">\n<div class=\"nbsphinx-gallery\">\n<a class=\"reference internal\" href=\"started/quicktour.html\">\n  <b>Quicktour</b>\n  <p style=\"color:var(--color-content-foreground)\">Learn the fundamentals of using ∇-Prox. We recommend starting here if you are using 🎉 ∇-Prox for the first time! </p>\n</a>\n<a class=\"reference internal\" href=\"tutorials/index.html\">\n  <b>Tutorials</b>\n  <p style=\"color:var(--color-content-foreground)\">Understand the design of the library and the mathematics behind the code. </p>\n</a>\n<a class=\"reference internal\" href=\"api/index.html\">\n  <b>API Documentation</b>\n  <p style=\"color:var(--color-content-foreground)\">Explore the complete reference guide. Useful if you want to develop programs with ∇-Prox. </p>\n</a>\n</div>\n</div>"
  },
  {
    "path": "docs/source/installation/index.md",
    "content": "# Installation\n\n```{toctree}\n:hidden:\n```\n\n∇-Prox works with PyTorch. To install Pytorch, please follow the [PyTorch installation instructions](https://pytorch.org/get-started/locally/).\n\n\n**Install with pip**\n\n```bash\npip install dprox\n```\n\n**Install from source**\n\n```bash\npip install git+https://github.com/princeton-computational-imaging/Delta-Prox.git\n```\n\n**Editable installation**\n\nYou will need an editable install if you would like to:\n\n1. Use the main version of the source code.\n2. Need to test changes in the code.\n\nTo do so, clone the repository and install 🎉 Delta Prox with the following commands:\n\n```\ngit clone git+https://github.com/princeton-computational-imaging/Delta-Prox.git\ncd DeltaProx\npip install -e .\n```\n\n```{caution}\nNote that you must keep the DeltaProx folder for editable installation if you want to keep using the library.\n```\n\n"
  },
  {
    "path": "docs/source/modelzoo.md",
    "content": "# Model Zoo\n\nIt takes 6 GB VRAM for shape generation and 24.5 GB for shape and texture generation in total.\n\n## Hunyuan3D-2mini Series\n\n| Model                       | Description                   | Date       | Size | Huggingface                                                                                      |\n|-----------------------------|-------------------------------|------------|------|--------------------------------------------------------------------------------------------------|\n| Hunyuan3D-DiT-v2-mini-Turbo | Step Distillation Version     | 2025-03-19 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini-turbo) |\n| Hunyuan3D-DiT-v2-mini-Fast  | Guidance Distillation Version | 2025-03-18 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini-fast)  |\n| Hunyuan3D-DiT-v2-mini       | Mini Image to Shape Model     | 2025-03-18 | 0.6B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mini/tree/main/hunyuan3d-dit-v2-mini)       |\n\n\n## Hunyuan3D-2mv Series\n\n| Model                     | Description                    | Date       | Size | Huggingface                                                                                  |\n|---------------------------|--------------------------------|------------|------|----------------------------------------------------------------------------------------------| \n| Hunyuan3D-DiT-v2-mv-Turbo | Step Distillation Version      | 2025-03-19 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv-turbo) |\n| Hunyuan3D-DiT-v2-mv-Fast  | Guidance Distillation Version  | 2025-03-18 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv-fast)  |\n| Hunyuan3D-DiT-v2-mv       | Multiview Image to Shape Model | 2025-03-18 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2mv/tree/main/hunyuan3d-dit-v2-mv)       |\n\n## Hunyuan3D-2 Series\n\n| Model                    | Description                 | Date       | Size | Huggingface                                                                               |\n|--------------------------|-----------------------------|------------|------|-------------------------------------------------------------------------------------------| \n| Hunyuan3D-DiT-v2-0-Turbo | Step Distillation Model     | 2025-03-19 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0-turbo) |\n| Hunyuan3D-DiT-v2-0-Fast  | Guidance Distillation Model | 2025-02-03 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0-fast)  |\n| Hunyuan3D-DiT-v2-0       | Image to Shape Model        | 2025-01-21 | 1.1B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-dit-v2-0)       |\n| Hunyuan3D-Paint-v2-0     | Texture Generation Model    | 2025-01-21 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-paint-v2-0)     |\n| Hunyuan3D-Delight-v2-0   | Image Delight Model         | 2025-01-21 | 1.3B | [Download](https://huggingface.co/tencent/Hunyuan3D-2/tree/main/hunyuan3d-delight-v2-0)   | \n"
  },
  {
    "path": "docs/source/started/api.md",
    "content": "# API\n\nYou could launch an API server locally, which you could post web request for Image/Text to 3D, Texturing existing mesh,\nand e.t.c.\n\n```bash\npython api_server.py --host 0.0.0.0 --port 8080\n```\n\nA demo post request for image to 3D without texture.\n\n```bash\nimg_b64_str=$(base64 -i assets/demo.png)\ncurl -X POST \"http://localhost:8080/generate\" \\\n     -H \"Content-Type: application/json\" \\\n     -d '{\n           \"image\": \"'\"$img_b64_str\"'\",\n         }' \\\n     -o test2.glb\n```\n"
  },
  {
    "path": "docs/source/started/blender.md",
    "content": "# Blender Addon\n\n\nWith an API server launched, you could also directly use Hunyuan3D 2.0 in your blender with\nour [Blender Addon](blender_addon.py). Please follow our tutorial to install and use.\n\nhttps://github.com/user-attachments/assets/8230bfb5-32b1-4e48-91f4-a977c54a4f3e\n"
  },
  {
    "path": "docs/source/started/code.md",
    "content": "# Code\n\n\nWe designed a diffusers-like API to use our shape generation model - Hunyuan3D-DiT and texture synthesis model -\nHunyuan3D-Paint.\n\nYou could assess **Hunyuan3D-DiT** via:\n\n```python\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n```\n\nThe output mesh is a [trimesh object](https://trimesh.org/trimesh.html), which you could save to glb/obj (or other\nformat) file.\n\nFor **Hunyuan3D-Paint**, do the following:\n\n```python\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\n# let's generate a mesh first\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(image='assets/demo.png')[0]\n\npipeline = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\nmesh = pipeline(mesh, image='assets/demo.png')\n```\n\nPlease visit [examples](examples) folder for more advanced usage, such as **multiview image to 3D generation** and *\n*texture generation\nfor handcrafted mesh**."
  },
  {
    "path": "docs/source/started/comfyui.md",
    "content": "# Comfyui\n\ncomfyui"
  },
  {
    "path": "docs/source/started/gradio.md",
    "content": "# Gradio APP\n\nYou could also host a [Gradio](https://www.gradio.app/) App in your own computer via:\n\nStandard Version\n\n```bash\n# Hunyuan3D-2mini\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mini --subfolder hunyuan3d-dit-v2-mini --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n# Hunyuan3D-2mv\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mv --subfolder hunyuan3d-dit-v2-mv --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n# Hunyuan3D-2\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2 --subfolder hunyuan3d-dit-v2-0 --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode\n```\n\nTurbo Version\n\n```bash\n# Hunyuan3D-2mini\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mini --subfolder hunyuan3d-dit-v2-mini-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n# Hunyuan3D-2mv\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2mv --subfolder hunyuan3d-dit-v2-mv-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n# Hunyuan3D-2\npython3 gradio_app.py --model_path tencent/Hunyuan3D-2 --subfolder hunyuan3d-dit-v2-0-turbo --texgen_model_path tencent/Hunyuan3D-2 --low_vram_mode --enable_flashvdm\n```\n"
  },
  {
    "path": "docs/source/started/index.md",
    "content": "# Get Started\n\n```{toctree}\n:hidden:\n\ngradio\ncomfyui\napi\nblender\ncode\nstudio\n```\n\n## Installation\n\nTo get started with ∇-Prox, please follow the [Installation Documentation](install) for detailed instructions on how to install the library.\n\n## Quick Tour\n\n- Take a [Quick Tour](quicktour) to get familiar with the features and functionalities of ∇-Prox.\n\n- Explore the [API Reference](../api/index) for a complete list of classes and functions.\n\n- For advanced topics and best practices, refer to the [tutorials](../tutorials/index).\n\n\nHappy coding with ∇-Prox! 🎉"
  },
  {
    "path": "docs/source/started/studio.md",
    "content": "# Official Website\n\nDon't forget to visit [Hunyuan3D](https://3d.hunyuan.tencent.com) for quick use, if you don't want to host yourself.\n"
  },
  {
    "path": "examples/fast_shape_gen_multiview.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\nimages = {\n    \"front\": \"assets/example_mv_images/1/front.png\",\n    \"left\": \"assets/example_mv_images/1/left.png\",\n    \"back\": \"assets/example_mv_images/1/back.png\"\n}\n\nfor key in images:\n    image = Image.open(images[key]).convert(\"RGBA\")\n    if image.mode == 'RGB':\n        rembg = BackgroundRemover()\n        image = rembg(image)\n    images[key] = image\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mv',\n    subfolder='hunyuan3d-dit-v2-mv-turbo',\n    variant='fp16'\n)\npipeline.enable_flashvdm()\nstart_time = time.time()\nmesh = pipeline(\n    image=images,\n    num_inference_steps=5,\n    octree_resolution=380,\n    num_chunks=20000,\n    generator=torch.manual_seed(12345),\n    output_type='trimesh'\n)[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo_mv3.glb')\n"
  },
  {
    "path": "examples/fast_shape_gen_with_flashvdm.py",
    "content": "# HY3DGEN_DEBUG=1 USE_SAGEATTN=1 python3 examples/fast_shape_gen_with_flashvdm.py\n# HY3DGEN_DEBUG=1 USE_SAGEATTN=0 python3 examples/fast_shape_gen_with_flashvdm.py\n\nimport os\nimport time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2',\n    subfolder='hunyuan3d-dit-v2-0-turbo',\n    use_safetensors=True,\n)\npipeline.enable_flashvdm()\n# pipeline.compile()\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\n\ndef run():\n    return pipeline(\n        image=image,\n        num_inference_steps=5,\n        octree_resolution=380,\n        num_chunks=200000,\n        generator=torch.manual_seed(12345),\n        output_type='trimesh'\n    )[0]\n\n\nsave_dir = 'tmp/results/'\nos.makedirs(save_dir, exist_ok=True)\n\nfor it in range(2):\n    start_time = time.time()\n    mesh = run()\n    print(\"--- %s seconds ---\" % (time.time() - start_time))\n    mesh.export(f'{save_dir}/run_{it}.glb')\n"
  },
  {
    "path": "examples/fast_texture_gen_multiview.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\nimport trimesh\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\n\nimages_path = [\n    \"assets/example_mv_images/1/front.png\",\n    \"assets/example_mv_images/1/left.png\",\n    \"assets/example_mv_images/1/back.png\"\n]\n\nimages = []\nfor image_path in images_path:\n    image = Image.open(image_path)\n    if image.mode == 'RGB':\n        rembg = BackgroundRemover()\n        image = rembg(image)\n    images.append(image)\n\npipeline = Hunyuan3DPaintPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2',\n    subfolder='hunyuan3d-paint-v2-0-turbo'\n)\n\nmesh = trimesh.load('assets/1.glb')\n\nmesh = pipeline(mesh, image=images)\nmesh.export('demo_textured.glb')"
  },
  {
    "path": "examples/faster_shape_gen_with_flashvdm_mini_turbo.py",
    "content": "# HY3DGEN_DEBUG=1 USE_SAGEATTN=1 python3 examples/faster_shape_gen_with_flashvdm_mini_turbo.py\n# HY3DGEN_DEBUG=1 USE_SAGEATTN=0 python3 examples/faster_shape_gen_with_flashvdm_mini_turbo.py\n\nimport os\nimport time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\ndevice = 'cuda'\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mini',\n    subfolder='hunyuan3d-dit-v2-mini-turbo',\n    use_safetensors=False,\n    device=device\n)\npipeline.enable_flashvdm(topk_mode='merge')\n# pipeline.compile()\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\n\ndef run():\n    return pipeline(\n        image=image,\n        num_inference_steps=5,\n        octree_resolution=380,\n        num_chunks=20000,\n        generator=torch.manual_seed(12345),\n        output_type='trimesh'\n    )[0]\n\n\nsave_dir = 'tmp/results/'\nos.makedirs(save_dir, exist_ok=True)\n\nfor it in range(2):\n    start_time = time.time()\n    mesh = run()\n    print(\"--- %s seconds ---\" % (time.time() - start_time))\n    mesh.export(f'{save_dir}/run_{it}.glb')\n"
  },
  {
    "path": "examples/shape_gen.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2',\n    subfolder='hunyuan3d-dit-v2-0',\n    variant='fp16'\n)\n\nstart_time = time.time()\nmesh = pipeline(image=image,\n                num_inference_steps=50,\n                octree_resolution=380,\n                num_chunks=20000,\n                generator=torch.manual_seed(12345),\n                output_type='trimesh'\n                )[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo.glb')\n"
  },
  {
    "path": "examples/shape_gen_mini.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mini',\n    subfolder='hunyuan3d-dit-v2-mini',\n    variant='fp16'\n)\n\nstart_time = time.time()\nmesh = pipeline(\n    image=image,\n    num_inference_steps=50,\n    octree_resolution=380,\n    num_chunks=20000,\n    generator=torch.manual_seed(12345),\n    output_type='trimesh'\n)[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo_mini.glb')\n"
  },
  {
    "path": "examples/shape_gen_multiview.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\nimages = {\n    \"front\": \"assets/example_mv_images/1/front.png\",\n    \"left\": \"assets/example_mv_images/1/left.png\",\n    \"back\": \"assets/example_mv_images/1/back.png\"\n}\n\nfor key in images:\n    image = Image.open(images[key]).convert(\"RGBA\")\n    if image.mode == 'RGB':\n        rembg = BackgroundRemover()\n        image = rembg(image)\n    images[key] = image\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mv',\n    subfolder='hunyuan3d-dit-v2-mv',\n    variant='fp16'\n)\n\nstart_time = time.time()\nmesh = pipeline(\n    image=images,\n    num_inference_steps=50,\n    octree_resolution=380,\n    num_chunks=20000,\n    generator=torch.manual_seed(12345),\n    output_type='trimesh'\n)[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo_mv.glb')\n"
  },
  {
    "path": "examples/shape_gen_v2_1.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\n\nmodel_path = 'tencent/Hunyuan3D-2.1'\npipeline_shapegen = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    model_path,\n    subfolder='hunyuan3d-dit-v2-1'\n)\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\nmesh = pipeline_shapegen(image=image)[0]\nmesh.export('demo.glb')\n"
  },
  {
    "path": "examples/textured_shape_gen.py",
    "content": "from PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\n\nmodel_path = 'tencent/Hunyuan3D-2'\npipeline_shapegen = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(model_path)\npipeline_texgen = Hunyuan3DPaintPipeline.from_pretrained(model_path)\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\nmesh = pipeline_shapegen(image=image)[0]\nmesh = pipeline_texgen(mesh, image=image)\nmesh.export('demo.glb')\n"
  },
  {
    "path": "examples/textured_shape_gen_mini.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mini',\n    subfolder='hunyuan3d-dit-v2-mini',\n    variant='fp16'\n)\npipeline_texgen = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\n\nstart_time = time.time()\nmesh = pipeline(\n    image=image,\n    num_inference_steps=50,\n    octree_resolution=380,\n    num_chunks=20000,\n    generator=torch.manual_seed(12345),\n    output_type='trimesh'\n)[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo_mini.glb')\n\nmesh = pipeline_texgen(mesh, image=image)\nmesh.export('demo_textured_mini.glb')\n"
  },
  {
    "path": "examples/textured_shape_gen_multiview.py",
    "content": "import time\n\nimport torch\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\n\nimages = {\n    \"front\": \"assets/example_mv_images/1/front.png\",\n    \"left\": \"assets/example_mv_images/1/left.png\",\n    \"back\": \"assets/example_mv_images/1/back.png\"\n}\n\nfor key in images:\n    image = Image.open(images[key]).convert(\"RGBA\")\n    if image.mode == 'RGB':\n        rembg = BackgroundRemover()\n        image = rembg(image)\n    images[key] = image\n\npipeline = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n    'tencent/Hunyuan3D-2mv',\n    subfolder='hunyuan3d-dit-v2-mv',\n    variant='fp16'\n)\npipeline_texgen = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')\n\nstart_time = time.time()\nmesh = pipeline(\n    image=images,\n    num_inference_steps=50,\n    octree_resolution=380,\n    num_chunks=20000,\n    generator=torch.manual_seed(12345),\n    output_type='trimesh'\n)[0]\nprint(\"--- %s seconds ---\" % (time.time() - start_time))\nmesh.export(f'demo_white_mesh_mv.glb')\n\nmesh = pipeline_texgen(mesh, image=images[\"front\"])\nmesh.export('demo_textured_mv.glb')\n"
  },
  {
    "path": "gradio_app.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport os\nimport random\nimport shutil\nimport time\nfrom glob import glob\nfrom pathlib import Path\n\nimport gradio as gr\nimport torch\nimport trimesh\nimport uvicorn\nfrom fastapi import FastAPI\nfrom fastapi.staticfiles import StaticFiles\nimport uuid\n\nfrom hy3dgen.shapegen.utils import logger\n\nMAX_SEED = int(1e7)\n\n\ndef get_example_img_list():\n    print('Loading example img list ...')\n    return sorted(glob('./assets/example_images/**/*.png', recursive=True))\n\n\ndef get_example_txt_list():\n    print('Loading example txt list ...')\n    txt_list = list()\n    for line in open('./assets/example_prompts.txt', encoding='utf-8'):\n        txt_list.append(line.strip())\n    return txt_list\n\n\ndef get_example_mv_list():\n    print('Loading example mv list ...')\n    mv_list = list()\n    root = './assets/example_mv_images'\n    for mv_dir in os.listdir(root):\n        view_list = []\n        for view in ['front', 'back', 'left', 'right']:\n            path = os.path.join(root, mv_dir, f'{view}.png')\n            if os.path.exists(path):\n                view_list.append(path)\n            else:\n                view_list.append(None)\n        mv_list.append(view_list)\n    return mv_list\n\n\ndef gen_save_folder(max_size=200):\n    os.makedirs(SAVE_DIR, exist_ok=True)\n\n    # 获取所有文件夹路径\n    dirs = [f for f in Path(SAVE_DIR).iterdir() if f.is_dir()]\n\n    # 如果文件夹数量超过 max_size，删除创建时间最久的文件夹\n    if len(dirs) >= max_size:\n        # 按创建时间排序，最久的排在前面\n        oldest_dir = min(dirs, key=lambda x: x.stat().st_ctime)\n        shutil.rmtree(oldest_dir)\n        print(f\"Removed the oldest folder: {oldest_dir}\")\n\n    # 生成一个新的 uuid 文件夹名称\n    new_folder = os.path.join(SAVE_DIR, str(uuid.uuid4()))\n    os.makedirs(new_folder, exist_ok=True)\n    print(f\"Created new folder: {new_folder}\")\n\n    return new_folder\n\n\ndef export_mesh(mesh, save_folder, textured=False, type='glb'):\n    if textured:\n        path = os.path.join(save_folder, f'textured_mesh.{type}')\n    else:\n        path = os.path.join(save_folder, f'white_mesh.{type}')\n    if type not in ['glb', 'obj']:\n        mesh.export(path)\n    else:\n        mesh.export(path, include_normals=textured)\n    return path\n\n\ndef randomize_seed_fn(seed: int, randomize_seed: bool) -> int:\n    if randomize_seed:\n        seed = random.randint(0, MAX_SEED)\n    return seed\n\n\ndef build_model_viewer_html(save_folder, height=660, width=790, textured=False):\n    # Remove first folder from path to make relative path\n    if textured:\n        related_path = f\"./textured_mesh.glb\"\n        template_name = './assets/modelviewer-textured-template.html'\n        output_html_path = os.path.join(save_folder, f'textured_mesh.html')\n    else:\n        related_path = f\"./white_mesh.glb\"\n        template_name = './assets/modelviewer-template.html'\n        output_html_path = os.path.join(save_folder, f'white_mesh.html')\n    offset = 50 if textured else 10\n    with open(os.path.join(CURRENT_DIR, template_name), 'r', encoding='utf-8') as f:\n        template_html = f.read()\n\n    with open(output_html_path, 'w', encoding='utf-8') as f:\n        template_html = template_html.replace('#height#', f'{height - offset}')\n        template_html = template_html.replace('#width#', f'{width}')\n        template_html = template_html.replace('#src#', f'{related_path}/')\n        f.write(template_html)\n\n    rel_path = os.path.relpath(output_html_path, SAVE_DIR)\n    iframe_tag = f'<iframe src=\"/static/{rel_path}\" height=\"{height}\" width=\"100%\" frameborder=\"0\"></iframe>'\n    print(\n        f'Find html file {output_html_path}, {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}')\n\n    return f\"\"\"\n        <div style='height: {height}; width: 100%;'>\n        {iframe_tag}\n        </div>\n    \"\"\"\n\n\ndef _gen_shape(\n    caption=None,\n    image=None,\n    mv_image_front=None,\n    mv_image_back=None,\n    mv_image_left=None,\n    mv_image_right=None,\n    steps=50,\n    guidance_scale=7.5,\n    seed=1234,\n    octree_resolution=256,\n    check_box_rembg=False,\n    num_chunks=200000,\n    randomize_seed: bool = False,\n):\n    if not MV_MODE and image is None and caption is None:\n        raise gr.Error(\"Please provide either a caption or an image.\")\n    if MV_MODE:\n        if mv_image_front is None and mv_image_back is None and mv_image_left is None and mv_image_right is None:\n            raise gr.Error(\"Please provide at least one view image.\")\n        image = {}\n        if mv_image_front:\n            image['front'] = mv_image_front\n        if mv_image_back:\n            image['back'] = mv_image_back\n        if mv_image_left:\n            image['left'] = mv_image_left\n        if mv_image_right:\n            image['right'] = mv_image_right\n\n    seed = int(randomize_seed_fn(seed, randomize_seed))\n\n    octree_resolution = int(octree_resolution)\n    if caption: print('prompt is', caption)\n    save_folder = gen_save_folder()\n    stats = {\n        'model': {\n            'shapegen': f'{args.model_path}/{args.subfolder}',\n            'texgen': f'{args.texgen_model_path}',\n        },\n        'params': {\n            'caption': caption,\n            'steps': steps,\n            'guidance_scale': guidance_scale,\n            'seed': seed,\n            'octree_resolution': octree_resolution,\n            'check_box_rembg': check_box_rembg,\n            'num_chunks': num_chunks,\n        }\n    }\n    time_meta = {}\n\n    if image is None:\n        start_time = time.time()\n        try:\n            image = t2i_worker(caption)\n        except Exception as e:\n            raise gr.Error(f\"Text to 3D is disable. Please enable it by `python gradio_app.py --enable_t23d`.\")\n        time_meta['text2image'] = time.time() - start_time\n\n    # remove disk io to make responding faster, uncomment at your will.\n    # image.save(os.path.join(save_folder, 'input.png'))\n    if MV_MODE:\n        start_time = time.time()\n        for k, v in image.items():\n            if check_box_rembg or v.mode == \"RGB\":\n                img = rmbg_worker(v.convert('RGB'))\n                image[k] = img\n        time_meta['remove background'] = time.time() - start_time\n    else:\n        if check_box_rembg or image.mode == \"RGB\":\n            start_time = time.time()\n            image = rmbg_worker(image.convert('RGB'))\n            time_meta['remove background'] = time.time() - start_time\n\n    # remove disk io to make responding faster, uncomment at your will.\n    # image.save(os.path.join(save_folder, 'rembg.png'))\n\n    # image to white model\n    start_time = time.time()\n\n    generator = torch.Generator()\n    generator = generator.manual_seed(int(seed))\n    outputs = i23d_worker(\n        image=image,\n        num_inference_steps=steps,\n        guidance_scale=guidance_scale,\n        generator=generator,\n        octree_resolution=octree_resolution,\n        num_chunks=num_chunks,\n        output_type='mesh'\n    )\n    time_meta['shape generation'] = time.time() - start_time\n    logger.info(\"---Shape generation takes %s seconds ---\" % (time.time() - start_time))\n\n    tmp_start = time.time()\n    mesh = export_to_trimesh(outputs)[0]\n    time_meta['export to trimesh'] = time.time() - tmp_start\n\n    stats['number_of_faces'] = mesh.faces.shape[0]\n    stats['number_of_vertices'] = mesh.vertices.shape[0]\n\n    stats['time'] = time_meta\n    main_image = image if not MV_MODE else image['front']\n    return mesh, main_image, save_folder, stats, seed\n\n\ndef generation_all(\n    caption=None,\n    image=None,\n    mv_image_front=None,\n    mv_image_back=None,\n    mv_image_left=None,\n    mv_image_right=None,\n    steps=50,\n    guidance_scale=7.5,\n    seed=1234,\n    octree_resolution=256,\n    check_box_rembg=False,\n    num_chunks=200000,\n    randomize_seed: bool = False,\n):\n    start_time_0 = time.time()\n    mesh, image, save_folder, stats, seed = _gen_shape(\n        caption,\n        image,\n        mv_image_front=mv_image_front,\n        mv_image_back=mv_image_back,\n        mv_image_left=mv_image_left,\n        mv_image_right=mv_image_right,\n        steps=steps,\n        guidance_scale=guidance_scale,\n        seed=seed,\n        octree_resolution=octree_resolution,\n        check_box_rembg=check_box_rembg,\n        num_chunks=num_chunks,\n        randomize_seed=randomize_seed,\n    )\n    path = export_mesh(mesh, save_folder, textured=False)\n\n    # tmp_time = time.time()\n    # mesh = floater_remove_worker(mesh)\n    # mesh = degenerate_face_remove_worker(mesh)\n    # logger.info(\"---Postprocessing takes %s seconds ---\" % (time.time() - tmp_time))\n    # stats['time']['postprocessing'] = time.time() - tmp_time\n\n    tmp_time = time.time()\n    mesh = face_reduce_worker(mesh)\n    logger.info(\"---Face Reduction takes %s seconds ---\" % (time.time() - tmp_time))\n    stats['time']['face reduction'] = time.time() - tmp_time\n\n    tmp_time = time.time()\n    textured_mesh = texgen_worker(mesh, image)\n    logger.info(\"---Texture Generation takes %s seconds ---\" % (time.time() - tmp_time))\n    stats['time']['texture generation'] = time.time() - tmp_time\n    stats['time']['total'] = time.time() - start_time_0\n\n    textured_mesh.metadata['extras'] = stats\n    path_textured = export_mesh(textured_mesh, save_folder, textured=True)\n    model_viewer_html_textured = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,\n                                                         textured=True)\n    if args.low_vram_mode:\n        torch.cuda.empty_cache()\n    return (\n        gr.update(value=path),\n        gr.update(value=path_textured),\n        model_viewer_html_textured,\n        stats,\n        seed,\n    )\n\n\ndef shape_generation(\n    caption=None,\n    image=None,\n    mv_image_front=None,\n    mv_image_back=None,\n    mv_image_left=None,\n    mv_image_right=None,\n    steps=50,\n    guidance_scale=7.5,\n    seed=1234,\n    octree_resolution=256,\n    check_box_rembg=False,\n    num_chunks=200000,\n    randomize_seed: bool = False,\n):\n    start_time_0 = time.time()\n    mesh, image, save_folder, stats, seed = _gen_shape(\n        caption,\n        image,\n        mv_image_front=mv_image_front,\n        mv_image_back=mv_image_back,\n        mv_image_left=mv_image_left,\n        mv_image_right=mv_image_right,\n        steps=steps,\n        guidance_scale=guidance_scale,\n        seed=seed,\n        octree_resolution=octree_resolution,\n        check_box_rembg=check_box_rembg,\n        num_chunks=num_chunks,\n        randomize_seed=randomize_seed,\n    )\n    stats['time']['total'] = time.time() - start_time_0\n    mesh.metadata['extras'] = stats\n\n    path = export_mesh(mesh, save_folder, textured=False)\n    model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH)\n    if args.low_vram_mode:\n        torch.cuda.empty_cache()\n    return (\n        gr.update(value=path),\n        model_viewer_html,\n        stats,\n        seed,\n    )\n\n\ndef build_app():\n    title = 'Hunyuan3D-2: High Resolution Textured 3D Assets Generation'\n    if MV_MODE:\n        title = 'Hunyuan3D-2mv: Image to 3D Generation with 1-4 Views'\n    if 'mini' in args.subfolder:\n        title = 'Hunyuan3D-2mini: Strong 0.6B Image to Shape Generator'\n    if TURBO_MODE:\n        title = title.replace(':', '-Turbo: Fast ')\n\n    title_html = f\"\"\"\n    <div style=\"font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px\">\n\n    {title}\n    </div>\n    <div align=\"center\">\n    Tencent Hunyuan3D Team\n    </div>\n    <div align=\"center\">\n      <a href=\"https://github.com/tencent/Hunyuan3D-2\">Github</a> &ensp; \n      <a href=\"http://3d-models.hunyuan.tencent.com\">Homepage</a> &ensp;\n      <a href=\"https://3d.hunyuan.tencent.com\">Hunyuan3D Studio</a> &ensp;\n      <a href=\"#\">Technical Report</a> &ensp;\n      <a href=\"https://huggingface.co/Tencent/Hunyuan3D-2\"> Pretrained Models</a> &ensp;\n    </div>\n    \"\"\"\n    custom_css = \"\"\"\n    .app.svelte-wpkpf6.svelte-wpkpf6:not(.fill_width) {\n        max-width: 1480px;\n    }\n    .mv-image button .wrap {\n        font-size: 10px;\n    }\n\n    .mv-image .icon-wrap {\n        width: 20px;\n    }\n\n    \"\"\"\n\n    with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.0', analytics_enabled=False, css=custom_css) as demo:\n        gr.HTML(title_html)\n\n        with gr.Row():\n            with gr.Column(scale=3):\n                with gr.Tabs(selected='tab_img_prompt') as tabs_prompt:\n                    with gr.Tab('Image Prompt', id='tab_img_prompt', visible=not MV_MODE) as tab_ip:\n                        image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290)\n\n                    with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I and not MV_MODE) as tab_tp:\n                        caption = gr.Textbox(label='Text Prompt',\n                                             placeholder='HunyuanDiT will be used to generate image.',\n                                             info='Example: A 3D model of a cute cat, white background')\n                    with gr.Tab('MultiView Prompt', visible=MV_MODE) as tab_mv:\n                        # gr.Label('Please upload at least one front image.')\n                        with gr.Row():\n                            mv_image_front = gr.Image(label='Front', type='pil', image_mode='RGBA', height=140,\n                                                      min_width=100, elem_classes='mv-image')\n                            mv_image_back = gr.Image(label='Back', type='pil', image_mode='RGBA', height=140,\n                                                     min_width=100, elem_classes='mv-image')\n                        with gr.Row():\n                            mv_image_left = gr.Image(label='Left', type='pil', image_mode='RGBA', height=140,\n                                                     min_width=100, elem_classes='mv-image')\n                            mv_image_right = gr.Image(label='Right', type='pil', image_mode='RGBA', height=140,\n                                                      min_width=100, elem_classes='mv-image')\n\n                with gr.Row():\n                    btn = gr.Button(value='Gen Shape', variant='primary', min_width=100)\n                    btn_all = gr.Button(value='Gen Textured Shape',\n                                        variant='primary',\n                                        visible=HAS_TEXTUREGEN,\n                                        min_width=100)\n\n                with gr.Group():\n                    file_out = gr.File(label=\"File\", visible=False)\n                    file_out2 = gr.File(label=\"File\", visible=False)\n\n                with gr.Tabs(selected='tab_options' if TURBO_MODE else 'tab_export'):\n                    with gr.Tab(\"Options\", id='tab_options', visible=TURBO_MODE):\n                        gen_mode = gr.Radio(label='Generation Mode',\n                                            info='Recommendation: Turbo for most cases, Fast for very complex cases, Standard seldom use.',\n                                            choices=['Turbo', 'Fast', 'Standard'], value='Turbo')\n                        decode_mode = gr.Radio(label='Decoding Mode',\n                                               info='The resolution for exporting mesh from generated vectset',\n                                               choices=['Low', 'Standard', 'High'],\n                                               value='Standard')\n                    with gr.Tab('Advanced Options', id='tab_advanced_options'):\n                        with gr.Row():\n                            check_box_rembg = gr.Checkbox(value=True, label='Remove Background', min_width=100)\n                            randomize_seed = gr.Checkbox(label=\"Randomize seed\", value=True, min_width=100)\n                        seed = gr.Slider(\n                            label=\"Seed\",\n                            minimum=0,\n                            maximum=MAX_SEED,\n                            step=1,\n                            value=1234,\n                            min_width=100,\n                        )\n                        with gr.Row():\n                            num_steps = gr.Slider(maximum=100,\n                                                  minimum=1,\n                                                  value=5 if 'turbo' in args.subfolder else 30,\n                                                  step=1, label='Inference Steps')\n                            octree_resolution = gr.Slider(maximum=512, minimum=16, value=256, label='Octree Resolution')\n                        with gr.Row():\n                            cfg_scale = gr.Number(value=5.0, label='Guidance Scale', min_width=100)\n                            num_chunks = gr.Slider(maximum=5000000, minimum=1000, value=8000,\n                                                   label='Number of Chunks', min_width=100)\n                    with gr.Tab(\"Export\", id='tab_export'):\n                        with gr.Row():\n                            file_type = gr.Dropdown(label='File Type', choices=SUPPORTED_FORMATS,\n                                                    value='glb', min_width=100)\n                            reduce_face = gr.Checkbox(label='Simplify Mesh', value=False, min_width=100)\n                            export_texture = gr.Checkbox(label='Include Texture', value=False,\n                                                         visible=False, min_width=100)\n                        target_face_num = gr.Slider(maximum=1000000, minimum=100, value=10000,\n                                                    label='Target Face Number')\n                        with gr.Row():\n                            confirm_export = gr.Button(value=\"Transform\", min_width=100)\n                            file_export = gr.DownloadButton(label=\"Download\", variant='primary',\n                                                            interactive=False, min_width=100)\n\n            with gr.Column(scale=6):\n                with gr.Tabs(selected='gen_mesh_panel') as tabs_output:\n                    with gr.Tab('Generated Mesh', id='gen_mesh_panel'):\n                        html_gen_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')\n                    with gr.Tab('Exporting Mesh', id='export_mesh_panel'):\n                        html_export_mesh = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')\n                    with gr.Tab('Mesh Statistic', id='stats_panel'):\n                        stats = gr.Json({}, label='Mesh Stats')\n\n            with gr.Column(scale=3 if MV_MODE else 2):\n                with gr.Tabs(selected='tab_img_gallery') as gallery:\n                    with gr.Tab('Image to 3D Gallery', id='tab_img_gallery', visible=not MV_MODE) as tab_gi:\n                        with gr.Row():\n                            gr.Examples(examples=example_is, inputs=[image],\n                                        label=None, examples_per_page=18)\n\n                    with gr.Tab('Text to 3D Gallery', id='tab_txt_gallery', visible=HAS_T2I and not MV_MODE) as tab_gt:\n                        with gr.Row():\n                            gr.Examples(examples=example_ts, inputs=[caption],\n                                        label=None, examples_per_page=18)\n                    with gr.Tab('MultiView to 3D Gallery', id='tab_mv_gallery', visible=MV_MODE) as tab_mv:\n                        with gr.Row():\n                            gr.Examples(examples=example_mvs,\n                                        inputs=[mv_image_front, mv_image_back, mv_image_left, mv_image_right],\n                                        label=None, examples_per_page=6)\n\n        gr.HTML(f\"\"\"\n        <div align=\"center\">\n        Activated Model - Shape Generation ({args.model_path}/{args.subfolder}) ; Texture Generation ({'Hunyuan3D-2' if HAS_TEXTUREGEN else 'Unavailable'})\n        </div>\n        \"\"\")\n        if not HAS_TEXTUREGEN:\n            gr.HTML(\"\"\"\n            <div style=\"margin-top: 5px;\"  align=\"center\">\n                <b>Warning: </b>\n                Texture synthesis is disable due to missing requirements,\n                 please install requirements following <a href=\"https://github.com/Tencent/Hunyuan3D-2?tab=readme-ov-file#install-requirements\">README.md</a>to activate it.\n            </div>\n            \"\"\")\n        if not args.enable_t23d:\n            gr.HTML(\"\"\"\n            <div style=\"margin-top: 5px;\"  align=\"center\">\n                <b>Warning: </b>\n                Text to 3D is disable. To activate it, please run `python gradio_app.py --enable_t23d`.\n            </div>\n            \"\"\")\n\n        tab_ip.select(fn=lambda: gr.update(selected='tab_img_gallery'), outputs=gallery)\n        if HAS_T2I:\n            tab_tp.select(fn=lambda: gr.update(selected='tab_txt_gallery'), outputs=gallery)\n\n        btn.click(\n            shape_generation,\n            inputs=[\n                caption,\n                image,\n                mv_image_front,\n                mv_image_back,\n                mv_image_left,\n                mv_image_right,\n                num_steps,\n                cfg_scale,\n                seed,\n                octree_resolution,\n                check_box_rembg,\n                num_chunks,\n                randomize_seed,\n            ],\n            outputs=[file_out, html_gen_mesh, stats, seed]\n        ).then(\n            lambda: (gr.update(visible=False, value=False), gr.update(interactive=True), gr.update(interactive=True),\n                     gr.update(interactive=False)),\n            outputs=[export_texture, reduce_face, confirm_export, file_export],\n        ).then(\n            lambda: gr.update(selected='gen_mesh_panel'),\n            outputs=[tabs_output],\n        )\n\n        btn_all.click(\n            generation_all,\n            inputs=[\n                caption,\n                image,\n                mv_image_front,\n                mv_image_back,\n                mv_image_left,\n                mv_image_right,\n                num_steps,\n                cfg_scale,\n                seed,\n                octree_resolution,\n                check_box_rembg,\n                num_chunks,\n                randomize_seed,\n            ],\n            outputs=[file_out, file_out2, html_gen_mesh, stats, seed]\n        ).then(\n            lambda: (gr.update(visible=True, value=True), gr.update(interactive=False), gr.update(interactive=True),\n                     gr.update(interactive=False)),\n            outputs=[export_texture, reduce_face, confirm_export, file_export],\n        ).then(\n            lambda: gr.update(selected='gen_mesh_panel'),\n            outputs=[tabs_output],\n        )\n\n        def on_gen_mode_change(value):\n            if value == 'Turbo':\n                return gr.update(value=5)\n            elif value == 'Fast':\n                return gr.update(value=10)\n            else:\n                return gr.update(value=30)\n\n        gen_mode.change(on_gen_mode_change, inputs=[gen_mode], outputs=[num_steps])\n\n        def on_decode_mode_change(value):\n            if value == 'Low':\n                return gr.update(value=196)\n            elif value == 'Standard':\n                return gr.update(value=256)\n            else:\n                return gr.update(value=384)\n\n        decode_mode.change(on_decode_mode_change, inputs=[decode_mode], outputs=[octree_resolution])\n\n        def on_export_click(file_out, file_out2, file_type, reduce_face, export_texture, target_face_num):\n            if file_out is None:\n                raise gr.Error('Please generate a mesh first.')\n\n            print(f'exporting {file_out}')\n            print(f'reduce face to {target_face_num}')\n            if export_texture:\n                mesh = trimesh.load(file_out2)\n                save_folder = gen_save_folder()\n                path = export_mesh(mesh, save_folder, textured=True, type=file_type)\n\n                # for preview\n                save_folder = gen_save_folder()\n                _ = export_mesh(mesh, save_folder, textured=True)\n                model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,\n                                                            textured=True)\n            else:\n                mesh = trimesh.load(file_out)\n                mesh = floater_remove_worker(mesh)\n                mesh = degenerate_face_remove_worker(mesh)\n                if reduce_face:\n                    mesh = face_reduce_worker(mesh, target_face_num)\n                save_folder = gen_save_folder()\n                path = export_mesh(mesh, save_folder, textured=False, type=file_type)\n\n                # for preview\n                save_folder = gen_save_folder()\n                _ = export_mesh(mesh, save_folder, textured=False)\n                model_viewer_html = build_model_viewer_html(save_folder, height=HTML_HEIGHT, width=HTML_WIDTH,\n                                                            textured=False)\n            print(f'export to {path}')\n            return model_viewer_html, gr.update(value=path, interactive=True)\n\n        confirm_export.click(\n            lambda: gr.update(selected='export_mesh_panel'),\n            outputs=[tabs_output],\n        ).then(\n            on_export_click,\n            inputs=[file_out, file_out2, file_type, reduce_face, export_texture, target_face_num],\n            outputs=[html_export_mesh, file_export]\n        )\n\n    return demo\n\n\nif __name__ == '__main__':\n    import argparse\n\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--model_path\", type=str, default='tencent/Hunyuan3D-2mini')\n    parser.add_argument(\"--subfolder\", type=str, default='hunyuan3d-dit-v2-mini-turbo')\n    parser.add_argument(\"--texgen_model_path\", type=str, default='tencent/Hunyuan3D-2')\n    parser.add_argument('--port', type=int, default=8080)\n    parser.add_argument('--host', type=str, default='0.0.0.0')\n    parser.add_argument('--device', type=str, default='cuda')\n    parser.add_argument('--mc_algo', type=str, default='mc')\n    parser.add_argument('--cache-path', type=str, default='gradio_cache')\n    parser.add_argument('--enable_t23d', action='store_true')\n    parser.add_argument('--disable_tex', action='store_true')\n    parser.add_argument('--enable_flashvdm', action='store_true')\n    parser.add_argument('--compile', action='store_true')\n    parser.add_argument('--low_vram_mode', action='store_true')\n    args = parser.parse_args()\n\n    SAVE_DIR = args.cache_path\n    os.makedirs(SAVE_DIR, exist_ok=True)\n\n    CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\n    MV_MODE = 'mv' in args.model_path\n    TURBO_MODE = 'turbo' in args.subfolder\n\n    HTML_HEIGHT = 690 if MV_MODE else 650\n    HTML_WIDTH = 500\n    HTML_OUTPUT_PLACEHOLDER = f\"\"\"\n    <div style='height: {650}px; width: 100%; border-radius: 8px; border-color: #e5e7eb; border-style: solid; border-width: 1px; display: flex; justify-content: center; align-items: center;'>\n      <div style='text-align: center; font-size: 16px; color: #6b7280;'>\n        <p style=\"color: #8d8d8d;\">Welcome to Hunyuan3D!</p>\n        <p style=\"color: #8d8d8d;\">No mesh here.</p>\n      </div>\n    </div>\n    \"\"\"\n\n    INPUT_MESH_HTML = \"\"\"\n    <div style='height: 490px; width: 100%; border-radius: 8px; \n    border-color: #e5e7eb; order-style: solid; border-width: 1px;'>\n    </div>\n    \"\"\"\n    example_is = get_example_img_list()\n    example_ts = get_example_txt_list()\n    example_mvs = get_example_mv_list()\n\n    SUPPORTED_FORMATS = ['glb', 'obj', 'ply', 'stl']\n\n    HAS_TEXTUREGEN = False\n    if not args.disable_tex:\n        try:\n            from hy3dgen.texgen import Hunyuan3DPaintPipeline\n\n            texgen_worker = Hunyuan3DPaintPipeline.from_pretrained(args.texgen_model_path)\n            if args.low_vram_mode:\n                texgen_worker.enable_model_cpu_offload()\n            # Not help much, ignore for now.\n            # if args.compile:\n            #     texgen_worker.models['delight_model'].pipeline.unet.compile()\n            #     texgen_worker.models['delight_model'].pipeline.vae.compile()\n            #     texgen_worker.models['multiview_model'].pipeline.unet.compile()\n            #     texgen_worker.models['multiview_model'].pipeline.vae.compile()\n            HAS_TEXTUREGEN = True\n        except Exception as e:\n            print(e)\n            print(\"Failed to load texture generator.\")\n            print('Please try to install requirements by following README.md')\n            HAS_TEXTUREGEN = False\n\n    HAS_T2I = True\n    if args.enable_t23d:\n        from hy3dgen.text2image import HunyuanDiTPipeline\n\n        t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled', device=args.device)\n        HAS_T2I = True\n\n    from hy3dgen.shapegen import FaceReducer, FloaterRemover, DegenerateFaceRemover, MeshSimplifier, \\\n        Hunyuan3DDiTFlowMatchingPipeline\n    from hy3dgen.shapegen.pipelines import export_to_trimesh\n    from hy3dgen.rembg import BackgroundRemover\n\n    rmbg_worker = BackgroundRemover()\n    i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(\n        args.model_path,\n        subfolder=args.subfolder,\n        use_safetensors=True,\n        device=args.device,\n    )\n    if args.enable_flashvdm:\n        mc_algo = 'mc' if args.device in ['cpu', 'mps'] else args.mc_algo\n        i23d_worker.enable_flashvdm(mc_algo=mc_algo)\n    if args.compile:\n        i23d_worker.compile()\n\n    floater_remove_worker = FloaterRemover()\n    degenerate_face_remove_worker = DegenerateFaceRemover()\n    face_reduce_worker = FaceReducer()\n\n    # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2\n    # create a FastAPI app\n    app = FastAPI()\n    # create a static directory to store the static files\n    static_dir = Path(SAVE_DIR).absolute()\n    static_dir.mkdir(parents=True, exist_ok=True)\n    app.mount(\"/static\", StaticFiles(directory=static_dir, html=True), name=\"static\")\n    shutil.copytree('./assets/env_maps', os.path.join(static_dir, 'env_maps'), dirs_exist_ok=True)\n\n    if args.low_vram_mode:\n        torch.cuda.empty_cache()\n    demo = build_app()\n    app = gr.mount_gradio_app(app, demo, path=\"/\")\n    uvicorn.run(app, host=args.host, port=args.port, workers=1)\n"
  },
  {
    "path": "hy3dgen/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT."
  },
  {
    "path": "hy3dgen/rembg.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom PIL import Image\nfrom rembg import remove, new_session\n\n\nclass BackgroundRemover():\n    def __init__(self):\n        self.session = new_session()\n\n    def __call__(self, image: Image.Image):\n        output = remove(image, session=self.session, bgcolor=[255, 255, 255, 0])\n        return output\n"
  },
  {
    "path": "hy3dgen/shapegen/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom .pipelines import Hunyuan3DDiTPipeline, Hunyuan3DDiTFlowMatchingPipeline\nfrom .postprocessors import FaceReducer, FloaterRemover, DegenerateFaceRemover, MeshSimplifier\nfrom .preprocessors import ImageProcessorV2, IMAGE_PROCESSORS, DEFAULT_IMAGEPROCESSOR\n"
  },
  {
    "path": "hy3dgen/shapegen/models/__init__.py",
    "content": "# Open Source Model Licensed under the Apache License Version 2.0\n# and Other Licenses of the Third-Party Components therein:\n# The below Model in this distribution may have been modified by THL A29 Limited\n# (\"Tencent Modifications\"). All Tencent Modifications are Copyright (C) 2024 THL A29 Limited.\n\n# Copyright (C) 2024 THL A29 Limited, a Tencent company.  All rights reserved.\n# The below software and/or models in this distribution may have been\n# modified by THL A29 Limited (\"Tencent Modifications\").\n# All Tencent Modifications are Copyright (C) THL A29 Limited.\n\n# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nfrom .autoencoders import ShapeVAE\nfrom .conditioner import DualImageEncoder, SingleImageEncoder, DinoImageEncoder, CLIPImageEncoder\nfrom .denoisers import Hunyuan3DDiT\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom .attention_blocks import CrossAttentionDecoder\nfrom .attention_processors import FlashVDMCrossAttentionProcessor, CrossAttentionProcessor, \\\n    FlashVDMTopMCrossAttentionProcessor\nfrom .model import ShapeVAE, VectsetVAE\nfrom .surface_extractors import SurfaceExtractors, MCSurfaceExtractor, DMCSurfaceExtractor, Latent2MeshOutput\nfrom .volume_decoders import HierarchicalVolumeDecoding, FlashVDMVolumeDecoding, VanillaVolumeDecoder\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/attention_blocks.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nimport os\nfrom typing import Optional, Union, List\n\nimport torch\nimport torch.nn as nn\nfrom einops import rearrange\nfrom torch import Tensor\n\nfrom .attention_processors import CrossAttentionProcessor\nfrom ...utils import logger\n\nscaled_dot_product_attention = nn.functional.scaled_dot_product_attention\n\nif os.environ.get('USE_SAGEATTN', '0') == '1':\n    try:\n        from sageattention import sageattn\n    except ImportError:\n        raise ImportError('Please install the package \"sageattention\" to use this USE_SAGEATTN.')\n    scaled_dot_product_attention = sageattn\n\n\nclass FourierEmbedder(nn.Module):\n    \"\"\"The sin/cosine positional embedding. Given an input tensor `x` of shape [n_batch, ..., c_dim], it converts\n    each feature dimension of `x[..., i]` into:\n        [\n            sin(x[..., i]),\n            sin(f_1*x[..., i]),\n            sin(f_2*x[..., i]),\n            ...\n            sin(f_N * x[..., i]),\n            cos(x[..., i]),\n            cos(f_1*x[..., i]),\n            cos(f_2*x[..., i]),\n            ...\n            cos(f_N * x[..., i]),\n            x[..., i]     # only present if include_input is True.\n        ], here f_i is the frequency.\n\n    Denote the space is [0 / num_freqs, 1 / num_freqs, 2 / num_freqs, 3 / num_freqs, ..., (num_freqs - 1) / num_freqs].\n    If logspace is True, then the frequency f_i is [2^(0 / num_freqs), ..., 2^(i / num_freqs), ...];\n    Otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1)].\n\n    Args:\n        num_freqs (int): the number of frequencies, default is 6;\n        logspace (bool): If logspace is True, then the frequency f_i is [..., 2^(i / num_freqs), ...],\n            otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1)];\n        input_dim (int): the input dimension, default is 3;\n        include_input (bool): include the input tensor or not, default is True.\n\n    Attributes:\n        frequencies (torch.Tensor): If logspace is True, then the frequency f_i is [..., 2^(i / num_freqs), ...],\n                otherwise, the frequencies are linearly spaced between [1.0, 2^(num_freqs - 1);\n\n        out_dim (int): the embedding size, if include_input is True, it is input_dim * (num_freqs * 2 + 1),\n            otherwise, it is input_dim * num_freqs * 2.\n\n    \"\"\"\n\n    def __init__(self,\n                 num_freqs: int = 6,\n                 logspace: bool = True,\n                 input_dim: int = 3,\n                 include_input: bool = True,\n                 include_pi: bool = True) -> None:\n\n        \"\"\"The initialization\"\"\"\n\n        super().__init__()\n\n        if logspace:\n            frequencies = 2.0 ** torch.arange(\n                num_freqs,\n                dtype=torch.float32\n            )\n        else:\n            frequencies = torch.linspace(\n                1.0,\n                2.0 ** (num_freqs - 1),\n                num_freqs,\n                dtype=torch.float32\n            )\n\n        if include_pi:\n            frequencies *= torch.pi\n\n        self.register_buffer(\"frequencies\", frequencies, persistent=False)\n        self.include_input = include_input\n        self.num_freqs = num_freqs\n\n        self.out_dim = self.get_dims(input_dim)\n\n    def get_dims(self, input_dim):\n        temp = 1 if self.include_input or self.num_freqs == 0 else 0\n        out_dim = input_dim * (self.num_freqs * 2 + temp)\n\n        return out_dim\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\" Forward process.\n\n        Args:\n            x: tensor of shape [..., dim]\n\n        Returns:\n            embedding: an embedding of `x` of shape [..., dim * (num_freqs * 2 + temp)]\n                where temp is 1 if include_input is True and 0 otherwise.\n        \"\"\"\n\n        if self.num_freqs > 0:\n            embed = (x[..., None].contiguous() * self.frequencies).view(*x.shape[:-1], -1)\n            if self.include_input:\n                return torch.cat((x, embed.sin(), embed.cos()), dim=-1)\n            else:\n                return torch.cat((embed.sin(), embed.cos()), dim=-1)\n        else:\n            return x\n\n\nclass DropPath(nn.Module):\n    \"\"\"Drop paths (Stochastic Depth) per sample  (when applied in main path of residual blocks).\n    \"\"\"\n\n    def __init__(self, drop_prob: float = 0., scale_by_keep: bool = True):\n        super(DropPath, self).__init__()\n        self.drop_prob = drop_prob\n        self.scale_by_keep = scale_by_keep\n\n    def forward(self, x):\n        \"\"\"Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).\n\n        This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,\n        the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...\n        See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for\n        changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use\n        'survival rate' as the argument.\n\n        \"\"\"\n        if self.drop_prob == 0. or not self.training:\n            return x\n        keep_prob = 1 - self.drop_prob\n        shape = (x.shape[0],) + (1,) * (x.ndim - 1)  # work with diff dim tensors, not just 2D ConvNets\n        random_tensor = x.new_empty(shape).bernoulli_(keep_prob)\n        if keep_prob > 0.0 and self.scale_by_keep:\n            random_tensor.div_(keep_prob)\n        return x * random_tensor\n\n    def extra_repr(self):\n        return f'drop_prob={round(self.drop_prob, 3):0.3f}'\n\n\nclass MLP(nn.Module):\n    def __init__(\n        self, *,\n        width: int,\n        expand_ratio: int = 4,\n        output_width: int = None,\n        drop_path_rate: float = 0.0\n    ):\n        super().__init__()\n        self.width = width\n        self.c_fc = nn.Linear(width, width * expand_ratio)\n        self.c_proj = nn.Linear(width * expand_ratio, output_width if output_width is not None else width)\n        self.gelu = nn.GELU()\n        self.drop_path = DropPath(drop_path_rate) if drop_path_rate > 0. else nn.Identity()\n\n    def forward(self, x):\n        return self.drop_path(self.c_proj(self.gelu(self.c_fc(x))))\n\n\nclass QKVMultiheadCrossAttention(nn.Module):\n    def __init__(\n        self,\n        *,\n        heads: int,\n        n_data: Optional[int] = None,\n        width=None,\n        qk_norm=False,\n        norm_layer=nn.LayerNorm\n    ):\n        super().__init__()\n        self.heads = heads\n        self.n_data = n_data\n        self.q_norm = norm_layer(width // heads, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.k_norm = norm_layer(width // heads, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n\n        self.attn_processor = CrossAttentionProcessor()\n\n    def forward(self, q, kv):\n        _, n_ctx, _ = q.shape\n        bs, n_data, width = kv.shape\n        attn_ch = width // self.heads // 2\n        q = q.view(bs, n_ctx, self.heads, -1)\n        kv = kv.view(bs, n_data, self.heads, -1)\n        k, v = torch.split(kv, attn_ch, dim=-1)\n\n        q = self.q_norm(q)\n        k = self.k_norm(k)\n        q, k, v = map(lambda t: rearrange(t, 'b n h d -> b h n d', h=self.heads), (q, k, v))\n        out = self.attn_processor(self, q, k, v)\n        out = out.transpose(1, 2).reshape(bs, n_ctx, -1)\n        return out\n\n\nclass MultiheadCrossAttention(nn.Module):\n    def __init__(\n        self,\n        *,\n        width: int,\n        heads: int,\n        qkv_bias: bool = True,\n        n_data: Optional[int] = None,\n        data_width: Optional[int] = None,\n        norm_layer=nn.LayerNorm,\n        qk_norm: bool = False,\n        kv_cache: bool = False,\n    ):\n        super().__init__()\n        self.n_data = n_data\n        self.width = width\n        self.heads = heads\n        self.data_width = width if data_width is None else data_width\n        self.c_q = nn.Linear(width, width, bias=qkv_bias)\n        self.c_kv = nn.Linear(self.data_width, width * 2, bias=qkv_bias)\n        self.c_proj = nn.Linear(width, width)\n        self.attention = QKVMultiheadCrossAttention(\n            heads=heads,\n            n_data=n_data,\n            width=width,\n            norm_layer=norm_layer,\n            qk_norm=qk_norm\n        )\n        self.kv_cache = kv_cache\n        self.data = None\n\n    def forward(self, x, data):\n        x = self.c_q(x)\n        if self.kv_cache:\n            if self.data is None:\n                self.data = self.c_kv(data)\n                logger.info('Save kv cache,this should be called only once for one mesh')\n            data = self.data\n        else:\n            data = self.c_kv(data)\n        x = self.attention(x, data)\n        x = self.c_proj(x)\n        return x\n\n\nclass ResidualCrossAttentionBlock(nn.Module):\n    def __init__(\n        self,\n        *,\n        n_data: Optional[int] = None,\n        width: int,\n        heads: int,\n        mlp_expand_ratio: int = 4,\n        data_width: Optional[int] = None,\n        qkv_bias: bool = True,\n        norm_layer=nn.LayerNorm,\n        qk_norm: bool = False\n    ):\n        super().__init__()\n\n        if data_width is None:\n            data_width = width\n\n        self.attn = MultiheadCrossAttention(\n            n_data=n_data,\n            width=width,\n            heads=heads,\n            data_width=data_width,\n            qkv_bias=qkv_bias,\n            norm_layer=norm_layer,\n            qk_norm=qk_norm\n        )\n        self.ln_1 = norm_layer(width, elementwise_affine=True, eps=1e-6)\n        self.ln_2 = norm_layer(data_width, elementwise_affine=True, eps=1e-6)\n        self.ln_3 = norm_layer(width, elementwise_affine=True, eps=1e-6)\n        self.mlp = MLP(width=width, expand_ratio=mlp_expand_ratio)\n\n    def forward(self, x: torch.Tensor, data: torch.Tensor):\n        x = x + self.attn(self.ln_1(x), self.ln_2(data))\n        x = x + self.mlp(self.ln_3(x))\n        return x\n\n\nclass QKVMultiheadAttention(nn.Module):\n    def __init__(\n        self,\n        *,\n        heads: int,\n        n_ctx: int,\n        width=None,\n        qk_norm=False,\n        norm_layer=nn.LayerNorm\n    ):\n        super().__init__()\n        self.heads = heads\n        self.n_ctx = n_ctx\n        self.q_norm = norm_layer(width // heads, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.k_norm = norm_layer(width // heads, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n\n    def forward(self, qkv):\n        bs, n_ctx, width = qkv.shape\n        attn_ch = width // self.heads // 3\n        qkv = qkv.view(bs, n_ctx, self.heads, -1)\n        q, k, v = torch.split(qkv, attn_ch, dim=-1)\n\n        q = self.q_norm(q)\n        k = self.k_norm(k)\n\n        q, k, v = map(lambda t: rearrange(t, 'b n h d -> b h n d', h=self.heads), (q, k, v))\n        out = scaled_dot_product_attention(q, k, v).transpose(1, 2).reshape(bs, n_ctx, -1)\n        return out\n\n\nclass MultiheadAttention(nn.Module):\n    def __init__(\n        self,\n        *,\n        n_ctx: int,\n        width: int,\n        heads: int,\n        qkv_bias: bool,\n        norm_layer=nn.LayerNorm,\n        qk_norm: bool = False,\n        drop_path_rate: float = 0.0\n    ):\n        super().__init__()\n        self.n_ctx = n_ctx\n        self.width = width\n        self.heads = heads\n        self.c_qkv = nn.Linear(width, width * 3, bias=qkv_bias)\n        self.c_proj = nn.Linear(width, width)\n        self.attention = QKVMultiheadAttention(\n            heads=heads,\n            n_ctx=n_ctx,\n            width=width,\n            norm_layer=norm_layer,\n            qk_norm=qk_norm\n        )\n        self.drop_path = DropPath(drop_path_rate) if drop_path_rate > 0. else nn.Identity()\n\n    def forward(self, x):\n        x = self.c_qkv(x)\n        x = self.attention(x)\n        x = self.drop_path(self.c_proj(x))\n        return x\n\n\nclass ResidualAttentionBlock(nn.Module):\n    def __init__(\n        self,\n        *,\n        n_ctx: int,\n        width: int,\n        heads: int,\n        qkv_bias: bool = True,\n        norm_layer=nn.LayerNorm,\n        qk_norm: bool = False,\n        drop_path_rate: float = 0.0,\n    ):\n        super().__init__()\n        self.attn = MultiheadAttention(\n            n_ctx=n_ctx,\n            width=width,\n            heads=heads,\n            qkv_bias=qkv_bias,\n            norm_layer=norm_layer,\n            qk_norm=qk_norm,\n            drop_path_rate=drop_path_rate\n        )\n        self.ln_1 = norm_layer(width, elementwise_affine=True, eps=1e-6)\n        self.mlp = MLP(width=width, drop_path_rate=drop_path_rate)\n        self.ln_2 = norm_layer(width, elementwise_affine=True, eps=1e-6)\n\n    def forward(self, x: torch.Tensor):\n        x = x + self.attn(self.ln_1(x))\n        x = x + self.mlp(self.ln_2(x))\n        return x\n\n\nclass Transformer(nn.Module):\n    def __init__(\n        self,\n        *,\n        n_ctx: int,\n        width: int,\n        layers: int,\n        heads: int,\n        qkv_bias: bool = True,\n        norm_layer=nn.LayerNorm,\n        qk_norm: bool = False,\n        drop_path_rate: float = 0.0\n    ):\n        super().__init__()\n        self.n_ctx = n_ctx\n        self.width = width\n        self.layers = layers\n        self.resblocks = nn.ModuleList(\n            [\n                ResidualAttentionBlock(\n                    n_ctx=n_ctx,\n                    width=width,\n                    heads=heads,\n                    qkv_bias=qkv_bias,\n                    norm_layer=norm_layer,\n                    qk_norm=qk_norm,\n                    drop_path_rate=drop_path_rate\n                )\n                for _ in range(layers)\n            ]\n        )\n\n    def forward(self, x: torch.Tensor):\n        for block in self.resblocks:\n            x = block(x)\n        return x\n\n\nclass CrossAttentionDecoder(nn.Module):\n\n    def __init__(\n        self,\n        *,\n        num_latents: int,\n        out_channels: int,\n        fourier_embedder: FourierEmbedder,\n        width: int,\n        heads: int,\n        mlp_expand_ratio: int = 4,\n        downsample_ratio: int = 1,\n        enable_ln_post: bool = True,\n        qkv_bias: bool = True,\n        qk_norm: bool = False,\n        label_type: str = \"binary\"\n    ):\n        super().__init__()\n\n        self.enable_ln_post = enable_ln_post\n        self.fourier_embedder = fourier_embedder\n        self.downsample_ratio = downsample_ratio\n        self.query_proj = nn.Linear(self.fourier_embedder.out_dim, width)\n        if self.downsample_ratio != 1:\n            self.latents_proj = nn.Linear(width * downsample_ratio, width)\n        if self.enable_ln_post == False:\n            qk_norm = False\n        self.cross_attn_decoder = ResidualCrossAttentionBlock(\n            n_data=num_latents,\n            width=width,\n            mlp_expand_ratio=mlp_expand_ratio,\n            heads=heads,\n            qkv_bias=qkv_bias,\n            qk_norm=qk_norm\n        )\n\n        if self.enable_ln_post:\n            self.ln_post = nn.LayerNorm(width)\n        self.output_proj = nn.Linear(width, out_channels)\n        self.label_type = label_type\n        self.count = 0\n\n    def set_cross_attention_processor(self, processor):\n        self.cross_attn_decoder.attn.attention.attn_processor = processor\n\n    def set_default_cross_attention_processor(self):\n        self.cross_attn_decoder.attn.attention.attn_processor = CrossAttentionProcessor\n\n    def forward(self, queries=None, query_embeddings=None, latents=None):\n        if query_embeddings is None:\n            query_embeddings = self.query_proj(self.fourier_embedder(queries).to(latents.dtype))\n        self.count += query_embeddings.shape[1]\n        if self.downsample_ratio != 1:\n            latents = self.latents_proj(latents)\n        x = self.cross_attn_decoder(query_embeddings, latents)\n        if self.enable_ln_post:\n            x = self.ln_post(x)\n        occ = self.output_proj(x)\n        return occ\n\n\ndef fps(\n    src: torch.Tensor,\n    batch: Optional[Tensor] = None,\n    ratio: Optional[Union[Tensor, float]] = None,\n    random_start: bool = True,\n    batch_size: Optional[int] = None,\n    ptr: Optional[Union[Tensor, List[int]]] = None,\n):\n    src = src.float()\n    from torch_cluster import fps as fps_fn\n    output = fps_fn(src, batch, ratio, random_start, batch_size, ptr)\n    return output\n\n\nclass PointCrossAttentionEncoder(nn.Module):\n\n    def __init__(\n        self, *,\n        num_latents: int,\n        downsample_ratio: float,\n        pc_size: int,\n        pc_sharpedge_size: int,\n        fourier_embedder: FourierEmbedder,\n        point_feats: int,\n        width: int,\n        heads: int,\n        layers: int,\n        normal_pe: bool = False,\n        qkv_bias: bool = True,\n        use_ln_post: bool = False,\n        use_checkpoint: bool = False,\n        qk_norm: bool = False\n    ):\n\n        super().__init__()\n\n        self.use_checkpoint = use_checkpoint\n        self.num_latents = num_latents\n        self.downsample_ratio = downsample_ratio\n        self.point_feats = point_feats\n        self.normal_pe = normal_pe\n\n        if pc_sharpedge_size == 0:\n            print(\n                f'PointCrossAttentionEncoder INFO: pc_sharpedge_size is not given, using pc_size as pc_sharpedge_size')\n        else:\n            print(\n                f'PointCrossAttentionEncoder INFO: pc_sharpedge_size is given, using pc_size={pc_size}, pc_sharpedge_size={pc_sharpedge_size}')\n\n        self.pc_size = pc_size\n        self.pc_sharpedge_size = pc_sharpedge_size\n\n        self.fourier_embedder = fourier_embedder\n\n        self.input_proj = nn.Linear(self.fourier_embedder.out_dim + point_feats, width)\n        self.cross_attn = ResidualCrossAttentionBlock(\n            width=width,\n            heads=heads,\n            qkv_bias=qkv_bias,\n            qk_norm=qk_norm\n        )\n\n        self.self_attn = None\n        if layers > 0:\n            self.self_attn = Transformer(\n                n_ctx=num_latents,\n                width=width,\n                layers=layers,\n                heads=heads,\n                qkv_bias=qkv_bias,\n                qk_norm=qk_norm\n            )\n\n        if use_ln_post:\n            self.ln_post = nn.LayerNorm(width)\n        else:\n            self.ln_post = None\n\n    def sample_points_and_latents(self, pc: torch.FloatTensor, feats: Optional[torch.FloatTensor] = None):\n        B, N, D = pc.shape\n        num_pts = self.num_latents * self.downsample_ratio\n\n        # Compute number of latents\n        num_latents = int(num_pts / self.downsample_ratio)\n\n        # Compute the number of random and sharpedge latents\n        num_random_query = self.pc_size / (self.pc_size + self.pc_sharpedge_size) * num_latents\n        num_sharpedge_query = num_latents - num_random_query\n\n        # Split random and sharpedge surface points\n        random_pc, sharpedge_pc = torch.split(pc, [self.pc_size, self.pc_sharpedge_size], dim=1)\n        assert random_pc.shape[1] <= self.pc_size, \"Random surface points size must be less than or equal to pc_size\"\n        assert sharpedge_pc.shape[\n                   1] <= self.pc_sharpedge_size, \"Sharpedge surface points size must be less than or equal to pc_sharpedge_size\"\n\n        # Randomly select random surface points and random query points\n        input_random_pc_size = int(num_random_query * self.downsample_ratio)\n        random_query_ratio = num_random_query / input_random_pc_size\n        idx_random_pc = torch.randperm(random_pc.shape[1], device=random_pc.device)[:input_random_pc_size]\n        input_random_pc = random_pc[:, idx_random_pc, :]\n        flatten_input_random_pc = input_random_pc.view(B * input_random_pc_size, D)\n        N_down = int(flatten_input_random_pc.shape[0] / B)\n        batch_down = torch.arange(B).to(pc.device)\n        batch_down = torch.repeat_interleave(batch_down, N_down)\n        idx_query_random = fps(flatten_input_random_pc, batch_down, ratio=random_query_ratio)\n        query_random_pc = flatten_input_random_pc[idx_query_random].view(B, -1, D)\n\n        # Randomly select sharpedge surface points and sharpedge query points\n        input_sharpedge_pc_size = int(num_sharpedge_query * self.downsample_ratio)\n        if input_sharpedge_pc_size == 0:\n            input_sharpedge_pc = torch.zeros(B, 0, D, dtype=input_random_pc.dtype).to(pc.device)\n            query_sharpedge_pc = torch.zeros(B, 0, D, dtype=query_random_pc.dtype).to(pc.device)\n        else:\n            sharpedge_query_ratio = num_sharpedge_query / input_sharpedge_pc_size\n            idx_sharpedge_pc = torch.randperm(sharpedge_pc.shape[1], device=sharpedge_pc.device)[\n                               :input_sharpedge_pc_size]\n            input_sharpedge_pc = sharpedge_pc[:, idx_sharpedge_pc, :]\n            flatten_input_sharpedge_surface_points = input_sharpedge_pc.view(B * input_sharpedge_pc_size, D)\n            N_down = int(flatten_input_sharpedge_surface_points.shape[0] / B)\n            batch_down = torch.arange(B).to(pc.device)\n            batch_down = torch.repeat_interleave(batch_down, N_down)\n            idx_query_sharpedge = fps(flatten_input_sharpedge_surface_points, batch_down, ratio=sharpedge_query_ratio)\n            query_sharpedge_pc = flatten_input_sharpedge_surface_points[idx_query_sharpedge].view(B, -1, D)\n\n        # Concatenate random and sharpedge surface points and query points\n        query_pc = torch.cat([query_random_pc, query_sharpedge_pc], dim=1)\n        input_pc = torch.cat([input_random_pc, input_sharpedge_pc], dim=1)\n\n        # PE\n        query = self.fourier_embedder(query_pc)\n        data = self.fourier_embedder(input_pc)\n\n        # Concat normal if given\n        if self.point_feats != 0:\n\n            random_surface_feats, sharpedge_surface_feats = torch.split(feats, [self.pc_size, self.pc_sharpedge_size],\n                                                                        dim=1)\n            input_random_surface_feats = random_surface_feats[:, idx_random_pc, :]\n            flatten_input_random_surface_feats = input_random_surface_feats.view(B * input_random_pc_size, -1)\n            query_random_feats = flatten_input_random_surface_feats[idx_query_random].view(B, -1,\n                                                                                           flatten_input_random_surface_feats.shape[\n                                                                                               -1])\n\n            if input_sharpedge_pc_size == 0:\n                input_sharpedge_surface_feats = torch.zeros(B, 0, self.point_feats,\n                                                            dtype=input_random_surface_feats.dtype).to(pc.device)\n                query_sharpedge_feats = torch.zeros(B, 0, self.point_feats, dtype=query_random_feats.dtype).to(\n                    pc.device)\n            else:\n                input_sharpedge_surface_feats = sharpedge_surface_feats[:, idx_sharpedge_pc, :]\n                flatten_input_sharpedge_surface_feats = input_sharpedge_surface_feats.view(B * input_sharpedge_pc_size,\n                                                                                           -1)\n                query_sharpedge_feats = flatten_input_sharpedge_surface_feats[idx_query_sharpedge].view(B, -1,\n                                                                                                        flatten_input_sharpedge_surface_feats.shape[\n                                                                                                            -1])\n\n            query_feats = torch.cat([query_random_feats, query_sharpedge_feats], dim=1)\n            input_feats = torch.cat([input_random_surface_feats, input_sharpedge_surface_feats], dim=1)\n\n            if self.normal_pe:\n                query_normal_pe = self.fourier_embedder(query_feats[..., :3])\n                input_normal_pe = self.fourier_embedder(input_feats[..., :3])\n                query_feats = torch.cat([query_normal_pe, query_feats[..., 3:]], dim=-1)\n                input_feats = torch.cat([input_normal_pe, input_feats[..., 3:]], dim=-1)\n\n            query = torch.cat([query, query_feats], dim=-1)\n            data = torch.cat([data, input_feats], dim=-1)\n\n        if input_sharpedge_pc_size == 0:\n            query_sharpedge_pc = torch.zeros(B, 1, D).to(pc.device)\n            input_sharpedge_pc = torch.zeros(B, 1, D).to(pc.device)\n\n        return query.view(B, -1, query.shape[-1]), data.view(B, -1, data.shape[-1]), [query_pc, input_pc,\n                                                                                      query_random_pc, input_random_pc,\n                                                                                      query_sharpedge_pc,\n                                                                                      input_sharpedge_pc]\n\n    def forward(self, pc, feats):\n        \"\"\"\n\n        Args:\n            pc (torch.FloatTensor): [B, N, 3]\n            feats (torch.FloatTensor or None): [B, N, C]\n\n        Returns:\n\n        \"\"\"\n\n        query, data, pc_infos = self.sample_points_and_latents(pc, feats)\n\n        query = self.input_proj(query)\n        query = query\n        data = self.input_proj(data)\n        data = data\n\n        latents = self.cross_attn(query, data)\n        if self.self_attn is not None:\n            latents = self.self_attn(latents)\n\n        if self.ln_post is not None:\n            latents = self.ln_post(latents)\n\n        return latents, pc_infos\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/attention_processors.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport os\n\nimport torch\nimport torch.nn.functional as F\n\nscaled_dot_product_attention = F.scaled_dot_product_attention\nif os.environ.get('CA_USE_SAGEATTN', '0') == '1':\n    try:\n        from sageattention import sageattn\n    except ImportError:\n        raise ImportError('Please install the package \"sageattention\" to use this USE_SAGEATTN.')\n    scaled_dot_product_attention = sageattn\n\n\nclass CrossAttentionProcessor:\n    def __call__(self, attn, q, k, v):\n        out = scaled_dot_product_attention(q, k, v)\n        return out\n\n\nclass FlashVDMCrossAttentionProcessor:\n    def __init__(self, topk=None):\n        self.topk = topk\n\n    def __call__(self, attn, q, k, v):\n        if k.shape[-2] == 3072:\n            topk = 1024\n        elif k.shape[-2] == 512:\n            topk = 256\n        else:\n            topk = k.shape[-2] // 3\n\n        if self.topk is True:\n            q1 = q[:, :, ::100, :]\n            sim = q1 @ k.transpose(-1, -2)\n            sim = torch.mean(sim, -2)\n            topk_ind = torch.topk(sim, dim=-1, k=topk).indices.squeeze(-2).unsqueeze(-1)\n            topk_ind = topk_ind.expand(-1, -1, -1, v.shape[-1])\n            v0 = torch.gather(v, dim=-2, index=topk_ind)\n            k0 = torch.gather(k, dim=-2, index=topk_ind)\n            out = scaled_dot_product_attention(q, k0, v0)\n        elif self.topk is False:\n            out = scaled_dot_product_attention(q, k, v)\n        else:\n            idx, counts = self.topk\n            start = 0\n            outs = []\n            for grid_coord, count in zip(idx, counts):\n                end = start + count\n                q_chunk = q[:, :, start:end, :]\n                k0, v0 = self.select_topkv(q_chunk, k, v, topk)\n                out = scaled_dot_product_attention(q_chunk, k0, v0)\n                outs.append(out)\n                start += count\n            out = torch.cat(outs, dim=-2)\n        self.topk = False\n        return out\n\n    def select_topkv(self, q_chunk, k, v, topk):\n        q1 = q_chunk[:, :, ::50, :]\n        sim = q1 @ k.transpose(-1, -2)\n        sim = torch.mean(sim, -2)\n        topk_ind = torch.topk(sim, dim=-1, k=topk).indices.squeeze(-2).unsqueeze(-1)\n        topk_ind = topk_ind.expand(-1, -1, -1, v.shape[-1])\n        v0 = torch.gather(v, dim=-2, index=topk_ind)\n        k0 = torch.gather(k, dim=-2, index=topk_ind)\n        return k0, v0\n\n\nclass FlashVDMTopMCrossAttentionProcessor(FlashVDMCrossAttentionProcessor):\n    def select_topkv(self, q_chunk, k, v, topk):\n        q1 = q_chunk[:, :, ::30, :]\n        sim = q1 @ k.transpose(-1, -2)\n        # sim = sim.to(torch.float32)\n        sim = sim.softmax(-1)\n        sim = torch.mean(sim, 1)\n        activated_token = torch.where(sim > 1e-6)[2]\n        index = torch.unique(activated_token, return_counts=True)[0].unsqueeze(0).unsqueeze(0).unsqueeze(-1)\n        index = index.expand(-1, v.shape[1], -1, v.shape[-1])\n        v0 = torch.gather(v, dim=-2, index=index)\n        k0 = torch.gather(k, dim=-2, index=index)\n        return k0, v0\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/model.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nimport os\nfrom typing import Union, List\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport yaml\n\nfrom .attention_blocks import FourierEmbedder, Transformer, CrossAttentionDecoder, PointCrossAttentionEncoder\nfrom .surface_extractors import MCSurfaceExtractor, SurfaceExtractors\nfrom .volume_decoders import VanillaVolumeDecoder, FlashVDMVolumeDecoding, HierarchicalVolumeDecoding\nfrom ...utils import logger, synchronize_timer, smart_load_model\n\n\nclass DiagonalGaussianDistribution(object):\n    def __init__(self, parameters: Union[torch.Tensor, List[torch.Tensor]], deterministic=False, feat_dim=1):\n        self.feat_dim = feat_dim\n        self.parameters = parameters\n\n        if isinstance(parameters, list):\n            self.mean = parameters[0]\n            self.logvar = parameters[1]\n        else:\n            self.mean, self.logvar = torch.chunk(parameters, 2, dim=feat_dim)\n\n        self.logvar = torch.clamp(self.logvar, -30.0, 20.0)\n        self.deterministic = deterministic\n        self.std = torch.exp(0.5 * self.logvar)\n        self.var = torch.exp(self.logvar)\n        if self.deterministic:\n            self.var = self.std = torch.zeros_like(self.mean)\n\n    def sample(self):\n        x = self.mean + self.std * torch.randn_like(self.mean)\n        return x\n\n    def kl(self, other=None, dims=(1, 2, 3)):\n        if self.deterministic:\n            return torch.Tensor([0.])\n        else:\n            if other is None:\n                return 0.5 * torch.mean(torch.pow(self.mean, 2)\n                                        + self.var - 1.0 - self.logvar,\n                                        dim=dims)\n            else:\n                return 0.5 * torch.mean(\n                    torch.pow(self.mean - other.mean, 2) / other.var\n                    + self.var / other.var - 1.0 - self.logvar + other.logvar,\n                    dim=dims)\n\n    def nll(self, sample, dims=(1, 2, 3)):\n        if self.deterministic:\n            return torch.Tensor([0.])\n        logtwopi = np.log(2.0 * np.pi)\n        return 0.5 * torch.sum(\n            logtwopi + self.logvar + torch.pow(sample - self.mean, 2) / self.var,\n            dim=dims)\n\n    def mode(self):\n        return self.mean\n\n\nclass VectsetVAE(nn.Module):\n\n    @classmethod\n    @synchronize_timer('VectsetVAE Model Loading')\n    def from_single_file(\n        cls,\n        ckpt_path,\n        config_path,\n        device='cuda',\n        dtype=torch.float16,\n        use_safetensors=None,\n        **kwargs,\n    ):\n        # load config\n        with open(config_path, 'r') as f:\n            config = yaml.safe_load(f)\n\n        # load ckpt\n        if use_safetensors:\n            ckpt_path = ckpt_path.replace('.ckpt', '.safetensors')\n        if not os.path.exists(ckpt_path):\n            raise FileNotFoundError(f\"Model file {ckpt_path} not found\")\n\n        logger.info(f\"Loading model from {ckpt_path}\")\n        if use_safetensors:\n            import safetensors.torch\n            ckpt = safetensors.torch.load_file(ckpt_path, device='cpu')\n        else:\n            ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True)\n\n        model_kwargs = config['params']\n        model_kwargs.update(kwargs)\n\n        model = cls(**model_kwargs)\n        model.load_state_dict(ckpt, strict=False)\n        model.to(device=device, dtype=dtype)\n        return model\n\n    @classmethod\n    def from_pretrained(\n        cls,\n        model_path,\n        device='cuda',\n        dtype=torch.float16,\n        use_safetensors=True,\n        variant='fp16',\n        subfolder='hunyuan3d-vae-v2-0',\n        **kwargs,\n    ):\n        config_path, ckpt_path = smart_load_model(\n            model_path,\n            subfolder=subfolder,\n            use_safetensors=use_safetensors,\n            variant=variant\n        )\n\n        return cls.from_single_file(\n            ckpt_path,\n            config_path,\n            device=device,\n            dtype=dtype,\n            use_safetensors=use_safetensors,\n            **kwargs\n        )\n\n    def init_from_ckpt(self, path, ignore_keys=()):\n        state_dict = torch.load(path, map_location=\"cpu\")\n        state_dict = state_dict.get(\"state_dict\", state_dict)\n        keys = list(state_dict.keys())\n        for k in keys:\n            for ik in ignore_keys:\n                if k.startswith(ik):\n                    print(\"Deleting key {} from state_dict.\".format(k))\n                    del state_dict[k]\n        missing, unexpected = self.load_state_dict(state_dict, strict=False)\n        print(f\"Restored from {path} with {len(missing)} missing and {len(unexpected)} unexpected keys\")\n        if len(missing) > 0:\n            print(f\"Missing Keys: {missing}\")\n            print(f\"Unexpected Keys: {unexpected}\")\n\n    def __init__(\n        self,\n        volume_decoder=None,\n        surface_extractor=None\n    ):\n        super().__init__()\n        if volume_decoder is None:\n            volume_decoder = VanillaVolumeDecoder()\n        if surface_extractor is None:\n            surface_extractor = MCSurfaceExtractor()\n        self.volume_decoder = volume_decoder\n        self.surface_extractor = surface_extractor\n\n    def latents2mesh(self, latents: torch.FloatTensor, **kwargs):\n        with synchronize_timer('Volume decoding'):\n            grid_logits = self.volume_decoder(latents, self.geo_decoder, **kwargs)\n        with synchronize_timer('Surface extraction'):\n            outputs = self.surface_extractor(grid_logits, **kwargs)\n        return outputs\n\n    def enable_flashvdm_decoder(\n        self,\n        enabled: bool = True,\n        adaptive_kv_selection=True,\n        topk_mode='mean',\n        mc_algo='dmc',\n    ):\n        if enabled:\n            if adaptive_kv_selection:\n                self.volume_decoder = FlashVDMVolumeDecoding(topk_mode)\n            else:\n                self.volume_decoder = HierarchicalVolumeDecoding()\n            if mc_algo not in SurfaceExtractors.keys():\n                raise ValueError(f'Unsupported mc_algo {mc_algo}, available: {list(SurfaceExtractors.keys())}')\n            self.surface_extractor = SurfaceExtractors[mc_algo]()\n        else:\n            self.volume_decoder = VanillaVolumeDecoder()\n            self.surface_extractor = MCSurfaceExtractor()\n\n\nclass ShapeVAE(VectsetVAE):\n    def __init__(\n        self,\n        *,\n        num_latents: int,\n        embed_dim: int,\n        width: int,\n        heads: int,\n        num_decoder_layers: int,\n        num_encoder_layers: int = 8,\n        pc_size: int = 5120,\n        pc_sharpedge_size: int = 5120,\n        point_feats: int = 3,\n        downsample_ratio: int = 20,\n        geo_decoder_downsample_ratio: int = 1,\n        geo_decoder_mlp_expand_ratio: int = 4,\n        geo_decoder_ln_post: bool = True,\n        num_freqs: int = 8,\n        include_pi: bool = True,\n        qkv_bias: bool = True,\n        qk_norm: bool = False,\n        label_type: str = \"binary\",\n        drop_path_rate: float = 0.0,\n        scale_factor: float = 1.0,\n        use_ln_post: bool = True,\n        ckpt_path=None\n    ):\n        super().__init__()\n        self.geo_decoder_ln_post = geo_decoder_ln_post\n        self.downsample_ratio = downsample_ratio\n\n        self.fourier_embedder = FourierEmbedder(num_freqs=num_freqs, include_pi=include_pi)\n\n        self.encoder = PointCrossAttentionEncoder(\n            fourier_embedder=self.fourier_embedder,\n            num_latents=num_latents,\n            downsample_ratio=self.downsample_ratio,\n            pc_size=pc_size,\n            pc_sharpedge_size=pc_sharpedge_size,\n            point_feats=point_feats,\n            width=width,\n            heads=heads,\n            layers=num_encoder_layers,\n            qkv_bias=qkv_bias,\n            use_ln_post=use_ln_post,\n            qk_norm=qk_norm\n        )\n\n        self.pre_kl = nn.Linear(width, embed_dim * 2)\n        self.post_kl = nn.Linear(embed_dim, width)\n\n        self.transformer = Transformer(\n            n_ctx=num_latents,\n            width=width,\n            layers=num_decoder_layers,\n            heads=heads,\n            qkv_bias=qkv_bias,\n            qk_norm=qk_norm,\n            drop_path_rate=drop_path_rate\n        )\n\n        self.geo_decoder = CrossAttentionDecoder(\n            fourier_embedder=self.fourier_embedder,\n            out_channels=1,\n            num_latents=num_latents,\n            mlp_expand_ratio=geo_decoder_mlp_expand_ratio,\n            downsample_ratio=geo_decoder_downsample_ratio,\n            enable_ln_post=self.geo_decoder_ln_post,\n            width=width // geo_decoder_downsample_ratio,\n            heads=heads // geo_decoder_downsample_ratio,\n            qkv_bias=qkv_bias,\n            qk_norm=qk_norm,\n            label_type=label_type,\n        )\n\n        self.scale_factor = scale_factor\n        self.latent_shape = (num_latents, embed_dim)\n\n        if ckpt_path is not None:\n            self.init_from_ckpt(ckpt_path)\n\n    def forward(self, latents):\n        latents = self.post_kl(latents)\n        latents = self.transformer(latents)\n        return latents\n\n    def encode(self, surface, sample_posterior=True):\n        pc, feats = surface[:, :, :3], surface[:, :, 3:]\n        latents, _ = self.encoder(pc, feats)\n        moments = self.pre_kl(latents)\n        posterior = DiagonalGaussianDistribution(moments, feat_dim=-1)\n        if sample_posterior:\n            latents = posterior.sample()\n        else:\n            latents = posterior.mode()\n        return latents\n\n    def decode(self, latents):\n        latents = self.post_kl(latents)\n        latents = self.transformer(latents)\n        return latents\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/surface_extractors.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom typing import Union, Tuple, List\n\nimport numpy as np\nimport torch\nfrom skimage import measure\n\n\nclass Latent2MeshOutput:\n\n    def __init__(self, mesh_v=None, mesh_f=None):\n        self.mesh_v = mesh_v\n        self.mesh_f = mesh_f\n\n\ndef center_vertices(vertices):\n    \"\"\"Translate the vertices so that bounding box is centered at zero.\"\"\"\n    vert_min = vertices.min(dim=0)[0]\n    vert_max = vertices.max(dim=0)[0]\n    vert_center = 0.5 * (vert_min + vert_max)\n    return vertices - vert_center\n\n\nclass SurfaceExtractor:\n    def _compute_box_stat(self, bounds: Union[Tuple[float], List[float], float], octree_resolution: int):\n        if isinstance(bounds, float):\n            bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]\n\n        bbox_min, bbox_max = np.array(bounds[0:3]), np.array(bounds[3:6])\n        bbox_size = bbox_max - bbox_min\n        grid_size = [int(octree_resolution) + 1, int(octree_resolution) + 1, int(octree_resolution) + 1]\n        return grid_size, bbox_min, bbox_size\n\n    def run(self, *args, **kwargs):\n        return NotImplementedError\n\n    def __call__(self, grid_logits, **kwargs):\n        outputs = []\n        for i in range(grid_logits.shape[0]):\n            try:\n                vertices, faces = self.run(grid_logits[i], **kwargs)\n                vertices = vertices.astype(np.float32)\n                faces = np.ascontiguousarray(faces)\n                outputs.append(Latent2MeshOutput(mesh_v=vertices, mesh_f=faces))\n\n            except Exception:\n                import traceback\n                traceback.print_exc()\n                outputs.append(None)\n\n        return outputs\n\n\nclass MCSurfaceExtractor(SurfaceExtractor):\n    def run(self, grid_logit, *, mc_level, bounds, octree_resolution, **kwargs):\n        vertices, faces, normals, _ = measure.marching_cubes(\n            grid_logit.cpu().numpy(),\n            mc_level,\n            method=\"lewiner\"\n        )\n        grid_size, bbox_min, bbox_size = self._compute_box_stat(bounds, octree_resolution)\n        vertices = vertices / grid_size * bbox_size + bbox_min\n        return vertices, faces\n\n\nclass DMCSurfaceExtractor(SurfaceExtractor):\n    def run(self, grid_logit, *, octree_resolution, **kwargs):\n        device = grid_logit.device\n        if not hasattr(self, 'dmc'):\n            try:\n                from diso import DiffDMC\n            except:\n                raise ImportError(\"Please install diso via `pip install diso`, or set mc_algo to 'mc'\")\n            self.dmc = DiffDMC(dtype=torch.float32).to(device)\n        sdf = -grid_logit / octree_resolution\n        sdf = sdf.to(torch.float32).contiguous()\n        verts, faces = self.dmc(sdf, deform=None, return_quads=False, normalize=True)\n        verts = center_vertices(verts)\n        vertices = verts.detach().cpu().numpy()\n        faces = faces.detach().cpu().numpy()[:, ::-1]\n        return vertices, faces\n\n\nSurfaceExtractors = {\n    'mc': MCSurfaceExtractor,\n    'dmc': DMCSurfaceExtractor,\n}\n"
  },
  {
    "path": "hy3dgen/shapegen/models/autoencoders/volume_decoders.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom typing import Union, Tuple, List, Callable\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom einops import repeat\nfrom tqdm import tqdm\n\nfrom .attention_blocks import CrossAttentionDecoder\nfrom .attention_processors import FlashVDMCrossAttentionProcessor, FlashVDMTopMCrossAttentionProcessor\nfrom ...utils import logger\n\n\ndef extract_near_surface_volume_fn(input_tensor: torch.Tensor, alpha: float):\n    device = input_tensor.device\n    D = input_tensor.shape[0]\n    signed_val = 0.0\n\n    # 添加偏移并处理无效值\n    val = input_tensor + alpha\n    valid_mask = val > -9000  # 假设-9000是无效值\n\n    # 改进的邻居获取函数（保持维度一致）\n    def get_neighbor(t, shift, axis):\n        \"\"\"根据指定轴进行位移并保持维度一致\"\"\"\n        if shift == 0:\n            return t.clone()\n\n        # 确定填充轴（输入为[D, D, D]对应z,y,x轴）\n        pad_dims = [0, 0, 0, 0, 0, 0]  # 格式：[x前，x后，y前，y后，z前，z后]\n\n        # 根据轴类型设置填充\n        if axis == 0:  # x轴（最后一个维度）\n            pad_idx = 0 if shift > 0 else 1\n            pad_dims[pad_idx] = abs(shift)\n        elif axis == 1:  # y轴（中间维度）\n            pad_idx = 2 if shift > 0 else 3\n            pad_dims[pad_idx] = abs(shift)\n        elif axis == 2:  # z轴（第一个维度）\n            pad_idx = 4 if shift > 0 else 5\n            pad_dims[pad_idx] = abs(shift)\n\n        # 执行填充（添加batch和channel维度适配F.pad）\n        padded = F.pad(t.unsqueeze(0).unsqueeze(0), pad_dims[::-1], mode='replicate')  # 反转顺序适配F.pad\n\n        # 构建动态切片索引\n        slice_dims = [slice(None)] * 3  # 初始化为全切片\n        if axis == 0:  # x轴（dim=2）\n            if shift > 0:\n                slice_dims[0] = slice(shift, None)\n            else:\n                slice_dims[0] = slice(None, shift)\n        elif axis == 1:  # y轴（dim=1）\n            if shift > 0:\n                slice_dims[1] = slice(shift, None)\n            else:\n                slice_dims[1] = slice(None, shift)\n        elif axis == 2:  # z轴（dim=0）\n            if shift > 0:\n                slice_dims[2] = slice(shift, None)\n            else:\n                slice_dims[2] = slice(None, shift)\n\n        # 应用切片并恢复维度\n        padded = padded.squeeze(0).squeeze(0)\n        sliced = padded[slice_dims]\n        return sliced\n\n    # 获取各方向邻居（确保维度一致）\n    left = get_neighbor(val, 1, axis=0)  # x方向\n    right = get_neighbor(val, -1, axis=0)\n    back = get_neighbor(val, 1, axis=1)  # y方向\n    front = get_neighbor(val, -1, axis=1)\n    down = get_neighbor(val, 1, axis=2)  # z方向\n    up = get_neighbor(val, -1, axis=2)\n\n    # 处理边界无效值（使用where保持维度一致）\n    def safe_where(neighbor):\n        return torch.where(neighbor > -9000, neighbor, val)\n\n    left = safe_where(left)\n    right = safe_where(right)\n    back = safe_where(back)\n    front = safe_where(front)\n    down = safe_where(down)\n    up = safe_where(up)\n\n    # 计算符号一致性（转换为float32确保精度）\n    sign = torch.sign(val.to(torch.float32))\n    neighbors_sign = torch.stack([\n        torch.sign(left.to(torch.float32)),\n        torch.sign(right.to(torch.float32)),\n        torch.sign(back.to(torch.float32)),\n        torch.sign(front.to(torch.float32)),\n        torch.sign(down.to(torch.float32)),\n        torch.sign(up.to(torch.float32))\n    ], dim=0)\n\n    # 检查所有符号是否一致\n    same_sign = torch.all(neighbors_sign == sign, dim=0)\n\n    # 生成最终掩码\n    mask = (~same_sign).to(torch.int32)\n    return mask * valid_mask.to(torch.int32)\n\n\ndef generate_dense_grid_points(\n    bbox_min: np.ndarray,\n    bbox_max: np.ndarray,\n    octree_resolution: int,\n    indexing: str = \"ij\",\n):\n    length = bbox_max - bbox_min\n    num_cells = octree_resolution\n\n    x = np.linspace(bbox_min[0], bbox_max[0], int(num_cells) + 1, dtype=np.float32)\n    y = np.linspace(bbox_min[1], bbox_max[1], int(num_cells) + 1, dtype=np.float32)\n    z = np.linspace(bbox_min[2], bbox_max[2], int(num_cells) + 1, dtype=np.float32)\n    [xs, ys, zs] = np.meshgrid(x, y, z, indexing=indexing)\n    xyz = np.stack((xs, ys, zs), axis=-1)\n    grid_size = [int(num_cells) + 1, int(num_cells) + 1, int(num_cells) + 1]\n\n    return xyz, grid_size, length\n\n\nclass VanillaVolumeDecoder:\n    @torch.no_grad()\n    def __call__(\n        self,\n        latents: torch.FloatTensor,\n        geo_decoder: Callable,\n        bounds: Union[Tuple[float], List[float], float] = 1.01,\n        num_chunks: int = 10000,\n        octree_resolution: int = None,\n        enable_pbar: bool = True,\n        **kwargs,\n    ):\n        device = latents.device\n        dtype = latents.dtype\n        batch_size = latents.shape[0]\n\n        # 1. generate query points\n        if isinstance(bounds, float):\n            bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]\n\n        bbox_min, bbox_max = np.array(bounds[0:3]), np.array(bounds[3:6])\n        xyz_samples, grid_size, length = generate_dense_grid_points(\n            bbox_min=bbox_min,\n            bbox_max=bbox_max,\n            octree_resolution=octree_resolution,\n            indexing=\"ij\"\n        )\n        xyz_samples = torch.from_numpy(xyz_samples).to(device, dtype=dtype).contiguous().reshape(-1, 3)\n\n        # 2. latents to 3d volume\n        batch_logits = []\n        for start in tqdm(range(0, xyz_samples.shape[0], num_chunks), desc=f\"Volume Decoding\",\n                          disable=not enable_pbar):\n            chunk_queries = xyz_samples[start: start + num_chunks, :]\n            chunk_queries = repeat(chunk_queries, \"p c -> b p c\", b=batch_size)\n            logits = geo_decoder(queries=chunk_queries, latents=latents)\n            batch_logits.append(logits)\n\n        grid_logits = torch.cat(batch_logits, dim=1)\n        grid_logits = grid_logits.view((batch_size, *grid_size)).float()\n\n        return grid_logits\n\n\nclass HierarchicalVolumeDecoding:\n    @torch.no_grad()\n    def __call__(\n        self,\n        latents: torch.FloatTensor,\n        geo_decoder: Callable,\n        bounds: Union[Tuple[float], List[float], float] = 1.01,\n        num_chunks: int = 10000,\n        mc_level: float = 0.0,\n        octree_resolution: int = None,\n        min_resolution: int = 63,\n        enable_pbar: bool = True,\n        **kwargs,\n    ):\n        device = latents.device\n        dtype = latents.dtype\n\n        resolutions = []\n        if octree_resolution < min_resolution:\n            resolutions.append(octree_resolution)\n        while octree_resolution >= min_resolution:\n            resolutions.append(octree_resolution)\n            octree_resolution = octree_resolution // 2\n        resolutions.reverse()\n\n        # 1. generate query points\n        if isinstance(bounds, float):\n            bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]\n        bbox_min = np.array(bounds[0:3])\n        bbox_max = np.array(bounds[3:6])\n        bbox_size = bbox_max - bbox_min\n\n        xyz_samples, grid_size, length = generate_dense_grid_points(\n            bbox_min=bbox_min,\n            bbox_max=bbox_max,\n            octree_resolution=resolutions[0],\n            indexing=\"ij\"\n        )\n\n        dilate = nn.Conv3d(1, 1, 3, padding=1, bias=False, device=device, dtype=dtype)\n        dilate.weight = torch.nn.Parameter(torch.ones(dilate.weight.shape, dtype=dtype, device=device))\n\n        grid_size = np.array(grid_size)\n        xyz_samples = torch.from_numpy(xyz_samples).to(device, dtype=dtype).contiguous().reshape(-1, 3)\n\n        # 2. latents to 3d volume\n        batch_logits = []\n        batch_size = latents.shape[0]\n        for start in tqdm(range(0, xyz_samples.shape[0], num_chunks),\n                          desc=f\"Hierarchical Volume Decoding [r{resolutions[0] + 1}]\"):\n            queries = xyz_samples[start: start + num_chunks, :]\n            batch_queries = repeat(queries, \"p c -> b p c\", b=batch_size)\n            logits = geo_decoder(queries=batch_queries, latents=latents)\n            batch_logits.append(logits)\n\n        grid_logits = torch.cat(batch_logits, dim=1).view((batch_size, grid_size[0], grid_size[1], grid_size[2]))\n\n        for octree_depth_now in resolutions[1:]:\n            grid_size = np.array([octree_depth_now + 1] * 3)\n            resolution = bbox_size / octree_depth_now\n            next_index = torch.zeros(tuple(grid_size), dtype=dtype, device=device)\n            next_logits = torch.full(next_index.shape, -10000., dtype=dtype, device=device)\n            curr_points = extract_near_surface_volume_fn(grid_logits.squeeze(0), mc_level)\n            curr_points += grid_logits.squeeze(0).abs() < 0.95\n\n            if octree_depth_now == resolutions[-1]:\n                expand_num = 0\n            else:\n                expand_num = 1\n            for i in range(expand_num):\n                curr_points = dilate(curr_points.unsqueeze(0).to(dtype)).squeeze(0)\n            (cidx_x, cidx_y, cidx_z) = torch.where(curr_points > 0)\n            next_index[cidx_x * 2, cidx_y * 2, cidx_z * 2] = 1\n            for i in range(2 - expand_num):\n                next_index = dilate(next_index.unsqueeze(0)).squeeze(0)\n            nidx = torch.where(next_index > 0)\n\n            next_points = torch.stack(nidx, dim=1)\n            next_points = (next_points * torch.tensor(resolution, dtype=next_points.dtype, device=device) +\n                           torch.tensor(bbox_min, dtype=next_points.dtype, device=device))\n            batch_logits = []\n            for start in tqdm(range(0, next_points.shape[0], num_chunks),\n                              desc=f\"Hierarchical Volume Decoding [r{octree_depth_now + 1}]\"):\n                queries = next_points[start: start + num_chunks, :]\n                batch_queries = repeat(queries, \"p c -> b p c\", b=batch_size)\n                logits = geo_decoder(queries=batch_queries.to(latents.dtype), latents=latents)\n                batch_logits.append(logits)\n            grid_logits = torch.cat(batch_logits, dim=1)\n            next_logits[nidx] = grid_logits[0, ..., 0]\n            grid_logits = next_logits.unsqueeze(0)\n        grid_logits[grid_logits == -10000.] = float('nan')\n\n        return grid_logits\n\n\nclass FlashVDMVolumeDecoding:\n    def __init__(self, topk_mode='mean'):\n        if topk_mode not in ['mean', 'merge']:\n            raise ValueError(f'Unsupported topk_mode {topk_mode}, available: {[\"mean\", \"merge\"]}')\n\n        if topk_mode == 'mean':\n            self.processor = FlashVDMCrossAttentionProcessor()\n        else:\n            self.processor = FlashVDMTopMCrossAttentionProcessor()\n\n    @torch.no_grad()\n    def __call__(\n        self,\n        latents: torch.FloatTensor,\n        geo_decoder: CrossAttentionDecoder,\n        bounds: Union[Tuple[float], List[float], float] = 1.01,\n        num_chunks: int = 10000,\n        mc_level: float = 0.0,\n        octree_resolution: int = None,\n        min_resolution: int = 63,\n        mini_grid_num: int = 4,\n        enable_pbar: bool = True,\n        **kwargs,\n    ):\n        processor = self.processor\n        geo_decoder.set_cross_attention_processor(processor)\n\n        device = latents.device\n        dtype = latents.dtype\n\n        resolutions = []\n        if octree_resolution < min_resolution:\n            resolutions.append(octree_resolution)\n        while octree_resolution >= min_resolution:\n            resolutions.append(octree_resolution)\n            octree_resolution = octree_resolution // 2\n        resolutions.reverse()\n        resolutions[0] = round(resolutions[0] / mini_grid_num) * mini_grid_num - 1\n        for i, resolution in enumerate(resolutions[1:]):\n            resolutions[i + 1] = resolutions[0] * 2 ** (i + 1)\n\n        logger.info(f\"FlashVDMVolumeDecoding Resolution: {resolutions}\")\n\n        # 1. generate query points\n        if isinstance(bounds, float):\n            bounds = [-bounds, -bounds, -bounds, bounds, bounds, bounds]\n        bbox_min = np.array(bounds[0:3])\n        bbox_max = np.array(bounds[3:6])\n        bbox_size = bbox_max - bbox_min\n\n        xyz_samples, grid_size, length = generate_dense_grid_points(\n            bbox_min=bbox_min,\n            bbox_max=bbox_max,\n            octree_resolution=resolutions[0],\n            indexing=\"ij\"\n        )\n\n        dilate = nn.Conv3d(1, 1, 3, padding=1, bias=False, device=device, dtype=dtype)\n        dilate.weight = torch.nn.Parameter(torch.ones(dilate.weight.shape, dtype=dtype, device=device))\n\n        grid_size = np.array(grid_size)\n\n        # 2. latents to 3d volume\n        xyz_samples = torch.from_numpy(xyz_samples).to(device, dtype=dtype)\n        batch_size = latents.shape[0]\n        mini_grid_size = xyz_samples.shape[0] // mini_grid_num\n        xyz_samples = xyz_samples.view(\n            mini_grid_num, mini_grid_size,\n            mini_grid_num, mini_grid_size,\n            mini_grid_num, mini_grid_size, 3\n        ).permute(\n            0, 2, 4, 1, 3, 5, 6\n        ).reshape(\n            -1, mini_grid_size * mini_grid_size * mini_grid_size, 3\n        )\n        batch_logits = []\n        num_batchs = max(num_chunks // xyz_samples.shape[1], 1)\n        for start in tqdm(range(0, xyz_samples.shape[0], num_batchs),\n                          desc=f\"FlashVDM Volume Decoding\", disable=not enable_pbar):\n            queries = xyz_samples[start: start + num_batchs, :]\n            batch = queries.shape[0]\n            batch_latents = repeat(latents.squeeze(0), \"p c -> b p c\", b=batch)\n            processor.topk = True\n            logits = geo_decoder(queries=queries, latents=batch_latents)\n            batch_logits.append(logits)\n        grid_logits = torch.cat(batch_logits, dim=0).reshape(\n            mini_grid_num, mini_grid_num, mini_grid_num,\n            mini_grid_size, mini_grid_size,\n            mini_grid_size\n        ).permute(0, 3, 1, 4, 2, 5).contiguous().view(\n            (batch_size, grid_size[0], grid_size[1], grid_size[2])\n        )\n\n        for octree_depth_now in resolutions[1:]:\n            grid_size = np.array([octree_depth_now + 1] * 3)\n            resolution = bbox_size / octree_depth_now\n            next_index = torch.zeros(tuple(grid_size), dtype=dtype, device=device)\n            next_logits = torch.full(next_index.shape, -10000., dtype=dtype, device=device)\n            curr_points = extract_near_surface_volume_fn(grid_logits.squeeze(0), mc_level)\n            curr_points += grid_logits.squeeze(0).abs() < 0.95\n\n            if octree_depth_now == resolutions[-1]:\n                expand_num = 0\n            else:\n                expand_num = 1\n            for i in range(expand_num):\n                curr_points = dilate(curr_points.unsqueeze(0).to(dtype)).squeeze(0)\n            (cidx_x, cidx_y, cidx_z) = torch.where(curr_points > 0)\n\n            next_index[cidx_x * 2, cidx_y * 2, cidx_z * 2] = 1\n            for i in range(2 - expand_num):\n                next_index = dilate(next_index.unsqueeze(0)).squeeze(0)\n            nidx = torch.where(next_index > 0)\n\n            next_points = torch.stack(nidx, dim=1)\n            next_points = (next_points * torch.tensor(resolution, dtype=torch.float32, device=device) +\n                           torch.tensor(bbox_min, dtype=torch.float32, device=device))\n\n            query_grid_num = 6\n            min_val = next_points.min(axis=0).values\n            max_val = next_points.max(axis=0).values\n            vol_queries_index = (next_points - min_val) / (max_val - min_val) * (query_grid_num - 0.001)\n            index = torch.floor(vol_queries_index).long()\n            index = index[..., 0] * (query_grid_num ** 2) + index[..., 1] * query_grid_num + index[..., 2]\n            index = index.sort()\n            next_points = next_points[index.indices].unsqueeze(0).contiguous()\n            unique_values = torch.unique(index.values, return_counts=True)\n            grid_logits = torch.zeros((next_points.shape[1]), dtype=latents.dtype, device=latents.device)\n            input_grid = [[], []]\n            logits_grid_list = []\n            start_num = 0\n            sum_num = 0\n            for grid_index, count in zip(unique_values[0].cpu().tolist(), unique_values[1].cpu().tolist()):\n                if sum_num + count < num_chunks or sum_num == 0:\n                    sum_num += count\n                    input_grid[0].append(grid_index)\n                    input_grid[1].append(count)\n                else:\n                    processor.topk = input_grid\n                    logits_grid = geo_decoder(queries=next_points[:, start_num:start_num + sum_num], latents=latents)\n                    start_num = start_num + sum_num\n                    logits_grid_list.append(logits_grid)\n                    input_grid = [[grid_index], [count]]\n                    sum_num = count\n            if sum_num > 0:\n                processor.topk = input_grid\n                logits_grid = geo_decoder(queries=next_points[:, start_num:start_num + sum_num], latents=latents)\n                logits_grid_list.append(logits_grid)\n            logits_grid = torch.cat(logits_grid_list, dim=1)\n            grid_logits[index.indices] = logits_grid.squeeze(0).squeeze(-1)\n            next_logits[nidx] = grid_logits\n            grid_logits = next_logits.unsqueeze(0)\n\n        grid_logits[grid_logits == -10000.] = float('nan')\n\n        return grid_logits\n"
  },
  {
    "path": "hy3dgen/shapegen/models/conditioner.py",
    "content": "# Open Source Model Licensed under the Apache License Version 2.0\n# and Other Licenses of the Third-Party Components therein:\n# The below Model in this distribution may have been modified by THL A29 Limited\n# (\"Tencent Modifications\"). All Tencent Modifications are Copyright (C) 2024 THL A29 Limited.\n\n# Copyright (C) 2024 THL A29 Limited, a Tencent company.  All rights reserved.\n# The below software and/or models in this distribution may have been\n# modified by THL A29 Limited (\"Tencent Modifications\").\n# All Tencent Modifications are Copyright (C) THL A29 Limited.\n\n# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torchvision import transforms\nfrom transformers import (\n    CLIPVisionModelWithProjection,\n    CLIPVisionConfig,\n    Dinov2Model,\n    Dinov2Config,\n)\n\n\ndef get_1d_sincos_pos_embed_from_grid(embed_dim, pos):\n    \"\"\"\n    embed_dim: output dimension for each position\n    pos: a list of positions to be encoded: size (M,)\n    out: (M, D)\n    \"\"\"\n    assert embed_dim % 2 == 0\n    omega = np.arange(embed_dim // 2, dtype=np.float64)\n    omega /= embed_dim / 2.\n    omega = 1. / 10000 ** omega  # (D/2,)\n\n    pos = pos.reshape(-1)  # (M,)\n    out = np.einsum('m,d->md', pos, omega)  # (M, D/2), outer product\n\n    emb_sin = np.sin(out)  # (M, D/2)\n    emb_cos = np.cos(out)  # (M, D/2)\n\n    return np.concatenate([emb_sin, emb_cos], axis=1)\n\n\nclass ImageEncoder(nn.Module):\n    def __init__(\n        self,\n        version=None,\n        config=None,\n        use_cls_token=True,\n        image_size=224,\n        **kwargs,\n    ):\n        super().__init__()\n\n        if config is None:\n            self.model = self.MODEL_CLASS.from_pretrained(version)\n        else:\n            self.model = self.MODEL_CLASS(self.MODEL_CONFIG_CLASS.from_dict(config))\n        self.model.eval()\n        self.model.requires_grad_(False)\n        self.use_cls_token = use_cls_token\n        self.size = image_size // 14\n        self.num_patches = (image_size // 14) ** 2\n        if self.use_cls_token:\n            self.num_patches += 1\n\n        self.transform = transforms.Compose(\n            [\n                transforms.Resize(image_size, transforms.InterpolationMode.BILINEAR, antialias=True),\n                transforms.CenterCrop(image_size),\n                transforms.Normalize(\n                    mean=self.mean,\n                    std=self.std,\n                ),\n            ]\n        )\n\n    def forward(self, image, mask=None, value_range=(-1, 1), **kwargs):\n        if value_range is not None:\n            low, high = value_range\n            image = (image - low) / (high - low)\n\n        image = image.to(self.model.device, dtype=self.model.dtype)\n        inputs = self.transform(image)\n        outputs = self.model(inputs)\n\n        last_hidden_state = outputs.last_hidden_state\n        if not self.use_cls_token:\n            last_hidden_state = last_hidden_state[:, 1:, :]\n\n        return last_hidden_state\n\n    def unconditional_embedding(self, batch_size, **kwargs):\n        device = next(self.model.parameters()).device\n        dtype = next(self.model.parameters()).dtype\n        zero = torch.zeros(\n            batch_size,\n            self.num_patches,\n            self.model.config.hidden_size,\n            device=device,\n            dtype=dtype,\n        )\n\n        return zero\n\n\nclass CLIPImageEncoder(ImageEncoder):\n    MODEL_CLASS = CLIPVisionModelWithProjection\n    MODEL_CONFIG_CLASS = CLIPVisionConfig\n    mean = [0.48145466, 0.4578275, 0.40821073]\n    std = [0.26862954, 0.26130258, 0.27577711]\n\n\nclass DinoImageEncoder(ImageEncoder):\n    MODEL_CLASS = Dinov2Model\n    MODEL_CONFIG_CLASS = Dinov2Config\n    mean = [0.485, 0.456, 0.406]\n    std = [0.229, 0.224, 0.225]\n\n\nclass DinoImageEncoderMV(DinoImageEncoder):\n    def __init__(\n        self,\n        version=None,\n        config=None,\n        use_cls_token=True,\n        image_size=224,\n        view_num=4,\n        **kwargs,\n    ):\n        super().__init__(version, config, use_cls_token, image_size, **kwargs)\n        self.view_num = view_num\n        self.num_patches = self.num_patches\n        pos = np.arange(self.view_num, dtype=np.float32)\n        view_embedding = torch.from_numpy(\n            get_1d_sincos_pos_embed_from_grid(self.model.config.hidden_size, pos)).float()\n\n        view_embedding = view_embedding.unsqueeze(1).repeat(1, self.num_patches, 1)\n        self.view_embed = view_embedding.unsqueeze(0)\n\n    def forward(self, image, mask=None, value_range=(-1, 1), view_idxs=None):\n        if value_range is not None:\n            low, high = value_range\n            image = (image - low) / (high - low)\n\n        image = image.to(self.model.device, dtype=self.model.dtype)\n\n        bs, num_views, c, h, w = image.shape\n        image = image.view(bs * num_views, c, h, w)\n\n        inputs = self.transform(image)\n        outputs = self.model(inputs)\n\n        last_hidden_state = outputs.last_hidden_state\n        last_hidden_state = last_hidden_state.view(\n            bs, num_views, last_hidden_state.shape[-2],\n            last_hidden_state.shape[-1]\n        )\n\n        view_embedding = self.view_embed.to(last_hidden_state.dtype).to(last_hidden_state.device)\n        if view_idxs is not None:\n            assert len(view_idxs) == bs\n            view_embeddings = []\n            for i in range(bs):\n                view_idx = view_idxs[i]\n                assert num_views == len(view_idx)\n                view_embeddings.append(self.view_embed[:, view_idx, ...])\n            view_embedding = torch.cat(view_embeddings, 0).to(last_hidden_state.dtype).to(last_hidden_state.device)\n\n        if num_views != self.view_num:\n            view_embedding = view_embedding[:, :num_views, ...]\n        last_hidden_state = last_hidden_state + view_embedding\n        last_hidden_state = last_hidden_state.view(bs, num_views * last_hidden_state.shape[-2],\n                                                   last_hidden_state.shape[-1])\n        return last_hidden_state\n\n    def unconditional_embedding(self, batch_size, view_idxs=None, **kwargs):\n        device = next(self.model.parameters()).device\n        dtype = next(self.model.parameters()).dtype\n        zero = torch.zeros(\n            batch_size,\n            self.num_patches * len(view_idxs[0]),\n            self.model.config.hidden_size,\n            device=device,\n            dtype=dtype,\n        )\n        return zero\n\n\ndef build_image_encoder(config):\n    if config['type'] == 'CLIPImageEncoder':\n        return CLIPImageEncoder(**config['kwargs'])\n    elif config['type'] == 'DinoImageEncoder':\n        return DinoImageEncoder(**config['kwargs'])\n    elif config['type'] == 'DinoImageEncoderMV':\n        return DinoImageEncoderMV(**config['kwargs'])\n    else:\n        raise ValueError(f'Unknown image encoder type: {config[\"type\"]}')\n\n\nclass DualImageEncoder(nn.Module):\n    def __init__(\n        self,\n        main_image_encoder,\n        additional_image_encoder,\n    ):\n        super().__init__()\n        self.main_image_encoder = build_image_encoder(main_image_encoder)\n        self.additional_image_encoder = build_image_encoder(additional_image_encoder)\n\n    def forward(self, image, mask=None, **kwargs):\n        outputs = {\n            'main': self.main_image_encoder(image, mask=mask, **kwargs),\n            'additional': self.additional_image_encoder(image, mask=mask, **kwargs),\n        }\n        return outputs\n\n    def unconditional_embedding(self, batch_size, **kwargs):\n        outputs = {\n            'main': self.main_image_encoder.unconditional_embedding(batch_size, **kwargs),\n            'additional': self.additional_image_encoder.unconditional_embedding(batch_size, **kwargs),\n        }\n        return outputs\n\n\nclass SingleImageEncoder(nn.Module):\n    def __init__(\n        self,\n        main_image_encoder,\n    ):\n        super().__init__()\n        self.main_image_encoder = build_image_encoder(main_image_encoder)\n\n    def forward(self, image, mask=None, **kwargs):\n        outputs = {\n            'main': self.main_image_encoder(image, mask=mask, **kwargs),\n        }\n        return outputs\n\n    def unconditional_embedding(self, batch_size, **kwargs):\n        outputs = {\n            'main': self.main_image_encoder.unconditional_embedding(batch_size, **kwargs),\n        }\n        return outputs\n"
  },
  {
    "path": "hy3dgen/shapegen/models/denoisers/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom .hunyuan3ddit import Hunyuan3DDiT\n"
  },
  {
    "path": "hy3dgen/shapegen/models/denoisers/hunyuan3ddit.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport math\nimport os\nfrom dataclasses import dataclass\nfrom typing import List, Tuple, Optional\n\nimport torch\nfrom einops import rearrange\nfrom torch import Tensor, nn\n\nscaled_dot_product_attention = nn.functional.scaled_dot_product_attention\nif os.environ.get('USE_SAGEATTN', '0') == '1':\n    try:\n        from sageattention import sageattn\n    except ImportError:\n        raise ImportError('Please install the package \"sageattention\" to use this USE_SAGEATTN.')\n    scaled_dot_product_attention = sageattn\n\n\ndef attention(q: Tensor, k: Tensor, v: Tensor, **kwargs) -> Tensor:\n    x = scaled_dot_product_attention(q, k, v)\n    x = rearrange(x, \"B H L D -> B L (H D)\")\n    return x\n\n\ndef timestep_embedding(t: Tensor, dim, max_period=10000, time_factor: float = 1000.0):\n    \"\"\"\n    Create sinusoidal timestep embeddings.\n    :param t: a 1-D Tensor of N indices, one per batch element.\n                      These may be fractional.\n    :param dim: the dimension of the output.\n    :param max_period: controls the minimum frequency of the embeddings.\n    :return: an (N, D) Tensor of positional embeddings.\n    \"\"\"\n    t = time_factor * t\n    half = dim // 2\n    freqs = torch.exp(-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half).to(\n        t.device\n    )\n\n    args = t[:, None].float() * freqs[None]\n    embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)\n    if dim % 2:\n        embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)\n    if torch.is_floating_point(t):\n        embedding = embedding.to(t)\n    return embedding\n\n\nclass GELU(nn.Module):\n    def __init__(self, approximate='tanh'):\n        super().__init__()\n        self.approximate = approximate\n\n    def forward(self, x: Tensor) -> Tensor:\n        return nn.functional.gelu(x, approximate=self.approximate)\n\n\nclass MLPEmbedder(nn.Module):\n    def __init__(self, in_dim: int, hidden_dim: int):\n        super().__init__()\n        self.in_layer = nn.Linear(in_dim, hidden_dim, bias=True)\n        self.silu = nn.SiLU()\n        self.out_layer = nn.Linear(hidden_dim, hidden_dim, bias=True)\n\n    def forward(self, x: Tensor) -> Tensor:\n        return self.out_layer(self.silu(self.in_layer(x)))\n\n\nclass RMSNorm(torch.nn.Module):\n    def __init__(self, dim: int):\n        super().__init__()\n        self.scale = nn.Parameter(torch.ones(dim))\n\n    def forward(self, x: Tensor):\n        x_dtype = x.dtype\n        x = x.float()\n        rrms = torch.rsqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + 1e-6)\n        return (x * rrms).to(dtype=x_dtype) * self.scale\n\n\nclass QKNorm(torch.nn.Module):\n    def __init__(self, dim: int):\n        super().__init__()\n        self.query_norm = RMSNorm(dim)\n        self.key_norm = RMSNorm(dim)\n\n    def forward(self, q: Tensor, k: Tensor, v: Tensor) -> Tuple[Tensor, Tensor]:\n        q = self.query_norm(q)\n        k = self.key_norm(k)\n        return q.to(v), k.to(v)\n\n\nclass SelfAttention(nn.Module):\n    def __init__(\n        self,\n        dim: int,\n        num_heads: int = 8,\n        qkv_bias: bool = False,\n    ):\n        super().__init__()\n        self.num_heads = num_heads\n        head_dim = dim // num_heads\n\n        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)\n        self.norm = QKNorm(head_dim)\n        self.proj = nn.Linear(dim, dim)\n\n    def forward(self, x: Tensor, pe: Tensor) -> Tensor:\n        qkv = self.qkv(x)\n        q, k, v = rearrange(qkv, \"B L (K H D) -> K B H L D\", K=3, H=self.num_heads)\n        q, k = self.norm(q, k, v)\n        x = attention(q, k, v, pe=pe)\n        x = self.proj(x)\n        return x\n\n\n@dataclass\nclass ModulationOut:\n    shift: Tensor\n    scale: Tensor\n    gate: Tensor\n\n\nclass Modulation(nn.Module):\n    def __init__(self, dim: int, double: bool):\n        super().__init__()\n        self.is_double = double\n        self.multiplier = 6 if double else 3\n        self.lin = nn.Linear(dim, self.multiplier * dim, bias=True)\n\n    def forward(self, vec: Tensor) -> Tuple[ModulationOut, Optional[ModulationOut]]:\n        out = self.lin(nn.functional.silu(vec))[:, None, :]\n        out = out.chunk(self.multiplier, dim=-1)\n\n        return (\n            ModulationOut(*out[:3]),\n            ModulationOut(*out[3:]) if self.is_double else None,\n        )\n\n\nclass DoubleStreamBlock(nn.Module):\n    def __init__(\n        self,\n        hidden_size: int,\n        num_heads: int,\n        mlp_ratio: float,\n        qkv_bias: bool = False,\n    ):\n        super().__init__()\n        mlp_hidden_dim = int(hidden_size * mlp_ratio)\n        self.num_heads = num_heads\n        self.hidden_size = hidden_size\n        self.img_mod = Modulation(hidden_size, double=True)\n        self.img_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n        self.img_attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias)\n\n        self.img_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n        self.img_mlp = nn.Sequential(\n            nn.Linear(hidden_size, mlp_hidden_dim, bias=True),\n            GELU(approximate=\"tanh\"),\n            nn.Linear(mlp_hidden_dim, hidden_size, bias=True),\n        )\n\n        self.txt_mod = Modulation(hidden_size, double=True)\n        self.txt_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n        self.txt_attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias)\n\n        self.txt_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n        self.txt_mlp = nn.Sequential(\n            nn.Linear(hidden_size, mlp_hidden_dim, bias=True),\n            GELU(approximate=\"tanh\"),\n            nn.Linear(mlp_hidden_dim, hidden_size, bias=True),\n        )\n\n    def forward(self, img: Tensor, txt: Tensor, vec: Tensor, pe: Tensor) -> Tuple[Tensor, Tensor]:\n        img_mod1, img_mod2 = self.img_mod(vec)\n        txt_mod1, txt_mod2 = self.txt_mod(vec)\n\n        img_modulated = self.img_norm1(img)\n        img_modulated = (1 + img_mod1.scale) * img_modulated + img_mod1.shift\n        img_qkv = self.img_attn.qkv(img_modulated)\n        img_q, img_k, img_v = rearrange(img_qkv, \"B L (K H D) -> K B H L D\", K=3, H=self.num_heads)\n        img_q, img_k = self.img_attn.norm(img_q, img_k, img_v)\n\n        txt_modulated = self.txt_norm1(txt)\n        txt_modulated = (1 + txt_mod1.scale) * txt_modulated + txt_mod1.shift\n        txt_qkv = self.txt_attn.qkv(txt_modulated)\n        txt_q, txt_k, txt_v = rearrange(txt_qkv, \"B L (K H D) -> K B H L D\", K=3, H=self.num_heads)\n        txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v)\n\n        q = torch.cat((txt_q, img_q), dim=2)\n        k = torch.cat((txt_k, img_k), dim=2)\n        v = torch.cat((txt_v, img_v), dim=2)\n\n        attn = attention(q, k, v, pe=pe)\n        txt_attn, img_attn = attn[:, : txt.shape[1]], attn[:, txt.shape[1]:]\n\n        img = img + img_mod1.gate * self.img_attn.proj(img_attn)\n        img = img + img_mod2.gate * self.img_mlp((1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift)\n\n        txt = txt + txt_mod1.gate * self.txt_attn.proj(txt_attn)\n        txt = txt + txt_mod2.gate * self.txt_mlp((1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift)\n        return img, txt\n\n\nclass SingleStreamBlock(nn.Module):\n    \"\"\"\n    A DiT block with parallel linear layers as described in\n    https://arxiv.org/abs/2302.05442 and adapted modulation interface.\n    \"\"\"\n\n    def __init__(\n        self,\n        hidden_size: int,\n        num_heads: int,\n        mlp_ratio: float = 4.0,\n        qk_scale: Optional[float] = None,\n    ):\n        super().__init__()\n\n        self.hidden_dim = hidden_size\n        self.num_heads = num_heads\n        head_dim = hidden_size // num_heads\n        self.scale = qk_scale or head_dim ** -0.5\n\n        self.mlp_hidden_dim = int(hidden_size * mlp_ratio)\n        # qkv and mlp_in\n        self.linear1 = nn.Linear(hidden_size, hidden_size * 3 + self.mlp_hidden_dim)\n        # proj and mlp_out\n        self.linear2 = nn.Linear(hidden_size + self.mlp_hidden_dim, hidden_size)\n\n        self.norm = QKNorm(head_dim)\n\n        self.hidden_size = hidden_size\n        self.pre_norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n\n        self.mlp_act = GELU(approximate=\"tanh\")\n        self.modulation = Modulation(hidden_size, double=False)\n\n    def forward(self, x: Tensor, vec: Tensor, pe: Tensor) -> Tensor:\n        mod, _ = self.modulation(vec)\n\n        x_mod = (1 + mod.scale) * self.pre_norm(x) + mod.shift\n        qkv, mlp = torch.split(self.linear1(x_mod), [3 * self.hidden_size, self.mlp_hidden_dim], dim=-1)\n\n        q, k, v = rearrange(qkv, \"B L (K H D) -> K B H L D\", K=3, H=self.num_heads)\n        q, k = self.norm(q, k, v)\n\n        # compute attention\n        attn = attention(q, k, v, pe=pe)\n        # compute activation in mlp stream, cat again and run second linear layer\n        output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2))\n        return x + mod.gate * output\n\n\nclass LastLayer(nn.Module):\n    def __init__(self, hidden_size: int, patch_size: int, out_channels: int):\n        super().__init__()\n        self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)\n        self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)\n        self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=True))\n\n    def forward(self, x: Tensor, vec: Tensor) -> Tensor:\n        shift, scale = self.adaLN_modulation(vec).chunk(2, dim=1)\n        x = (1 + scale[:, None, :]) * self.norm_final(x) + shift[:, None, :]\n        x = self.linear(x)\n        return x\n\n\nclass Hunyuan3DDiT(nn.Module):\n    def __init__(\n        self,\n        in_channels: int = 64,\n        context_in_dim: int = 1536,\n        hidden_size: int = 1024,\n        mlp_ratio: float = 4.0,\n        num_heads: int = 16,\n        depth: int = 16,\n        depth_single_blocks: int = 32,\n        axes_dim: List[int] = [64],\n        theta: int = 10_000,\n        qkv_bias: bool = True,\n        time_factor: float = 1000,\n        guidance_embed: bool = False,\n        ckpt_path: Optional[str] = None,\n        **kwargs,\n    ):\n        super().__init__()\n        self.in_channels = in_channels\n        self.context_in_dim = context_in_dim\n        self.hidden_size = hidden_size\n        self.mlp_ratio = mlp_ratio\n        self.num_heads = num_heads\n        self.depth = depth\n        self.depth_single_blocks = depth_single_blocks\n        self.axes_dim = axes_dim\n        self.theta = theta\n        self.qkv_bias = qkv_bias\n        self.time_factor = time_factor\n        self.out_channels = self.in_channels\n        self.guidance_embed = guidance_embed\n\n        if hidden_size % num_heads != 0:\n            raise ValueError(\n                f\"Hidden size {hidden_size} must be divisible by num_heads {num_heads}\"\n            )\n        pe_dim = hidden_size // num_heads\n        if sum(axes_dim) != pe_dim:\n            raise ValueError(f\"Got {axes_dim} but expected positional dim {pe_dim}\")\n        self.hidden_size = hidden_size\n        self.num_heads = num_heads\n        self.latent_in = nn.Linear(self.in_channels, self.hidden_size, bias=True)\n        self.time_in = MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size)\n        self.cond_in = nn.Linear(context_in_dim, self.hidden_size)\n        self.guidance_in = (\n            MLPEmbedder(in_dim=256, hidden_dim=self.hidden_size) if guidance_embed else nn.Identity()\n        )\n\n        self.double_blocks = nn.ModuleList(\n            [\n                DoubleStreamBlock(\n                    self.hidden_size,\n                    self.num_heads,\n                    mlp_ratio=mlp_ratio,\n                    qkv_bias=qkv_bias,\n                )\n                for _ in range(depth)\n            ]\n        )\n\n        self.single_blocks = nn.ModuleList(\n            [\n                SingleStreamBlock(\n                    self.hidden_size,\n                    self.num_heads,\n                    mlp_ratio=mlp_ratio,\n                )\n                for _ in range(depth_single_blocks)\n            ]\n        )\n\n        self.final_layer = LastLayer(self.hidden_size, 1, self.out_channels)\n\n        if ckpt_path is not None:\n            print('restored denoiser ckpt', ckpt_path)\n\n            ckpt = torch.load(ckpt_path, map_location=\"cpu\")\n            if 'state_dict' not in ckpt:\n                # deepspeed ckpt\n                state_dict = {}\n                for k in ckpt.keys():\n                    new_k = k.replace('_forward_module.', '')\n                    state_dict[new_k] = ckpt[k]\n            else:\n                state_dict = ckpt[\"state_dict\"]\n\n            final_state_dict = {}\n            for k, v in state_dict.items():\n                if k.startswith('model.'):\n                    final_state_dict[k.replace('model.', '')] = v\n                else:\n                    final_state_dict[k] = v\n            missing, unexpected = self.load_state_dict(final_state_dict, strict=False)\n            print('unexpected keys:', unexpected)\n            print('missing keys:', missing)\n\n    def forward(\n        self,\n        x,\n        t,\n        contexts,\n        **kwargs,\n    ) -> Tensor:\n        cond = contexts['main']\n        latent = self.latent_in(x)\n\n        vec = self.time_in(timestep_embedding(t, 256, self.time_factor).to(dtype=latent.dtype))\n        if self.guidance_embed:\n            guidance = kwargs.get('guidance', None)\n            if guidance is None:\n                raise ValueError(\"Didn't get guidance strength for guidance distilled model.\")\n            vec = vec + self.guidance_in(timestep_embedding(guidance, 256, self.time_factor))\n\n        cond = self.cond_in(cond)\n        pe = None\n\n        for block in self.double_blocks:\n            latent, cond = block(img=latent, txt=cond, vec=vec, pe=pe)\n\n        latent = torch.cat((cond, latent), 1)\n        for block in self.single_blocks:\n            latent = block(latent, vec=vec, pe=pe)\n\n        latent = latent[:, cond.shape[1]:, ...]\n        latent = self.final_layer(latent, vec)\n        return latent\n"
  },
  {
    "path": "hy3dgen/shapegen/models/denoisers/hunyuandit.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport math\n\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom einops import rearrange\n\nfrom .moe_layers import MoEBlock\n\n\ndef modulate(x, shift, scale):\n    return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)\n\n\ndef get_1d_sincos_pos_embed_from_grid(embed_dim, pos):\n    \"\"\"\n    embed_dim: output dimension for each position\n    pos: a list of positions to be encoded: size (M,)\n    out: (M, D)\n    \"\"\"\n    assert embed_dim % 2 == 0\n    omega = np.arange(embed_dim // 2, dtype=np.float64)\n    omega /= embed_dim / 2.\n    omega = 1. / 10000 ** omega  # (D/2,)\n\n    pos = pos.reshape(-1)  # (M,)\n    out = np.einsum('m,d->md', pos, omega)  # (M, D/2), outer product\n\n    emb_sin = np.sin(out)  # (M, D/2)\n    emb_cos = np.cos(out)  # (M, D/2)\n\n    return np.concatenate([emb_sin, emb_cos], axis=1)\n\n\nclass Timesteps(nn.Module):\n    def __init__(self,\n                 num_channels: int,\n                 downscale_freq_shift: float = 0.0,\n                 scale: int = 1,\n                 max_period: int = 10000\n                 ):\n        super().__init__()\n        self.num_channels = num_channels\n        self.downscale_freq_shift = downscale_freq_shift\n        self.scale = scale\n        self.max_period = max_period\n\n    def forward(self, timesteps):\n        assert len(timesteps.shape) == 1, \"Timesteps should be a 1d-array\"\n        embedding_dim = self.num_channels\n        half_dim = embedding_dim // 2\n        exponent = -math.log(self.max_period) * torch.arange(\n            start=0, end=half_dim, dtype=torch.float32, device=timesteps.device)\n        exponent = exponent / (half_dim - self.downscale_freq_shift)\n        emb = torch.exp(exponent)\n        emb = timesteps[:, None].float() * emb[None, :]\n        emb = self.scale * emb\n        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)\n        if embedding_dim % 2 == 1:\n            emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))\n        return emb\n\n\nclass TimestepEmbedder(nn.Module):\n    \"\"\"\n    Embeds scalar timesteps into vector representations.\n    \"\"\"\n\n    def __init__(self, hidden_size, frequency_embedding_size=256, cond_proj_dim=None, out_size=None):\n        super().__init__()\n        if out_size is None:\n            out_size = hidden_size\n        self.mlp = nn.Sequential(\n            nn.Linear(hidden_size, frequency_embedding_size, bias=True),\n            nn.GELU(),\n            nn.Linear(frequency_embedding_size, out_size, bias=True),\n        )\n        self.frequency_embedding_size = frequency_embedding_size\n\n        if cond_proj_dim is not None:\n            self.cond_proj = nn.Linear(cond_proj_dim, frequency_embedding_size, bias=False)\n\n        self.time_embed = Timesteps(hidden_size)\n\n    def forward(self, t, condition):\n\n        t_freq = self.time_embed(t).type(self.mlp[0].weight.dtype)\n\n        # t_freq = timestep_embedding(t, self.frequency_embedding_size).type(self.mlp[0].weight.dtype)\n        if condition is not None:\n            t_freq = t_freq + self.cond_proj(condition)\n\n        t = self.mlp(t_freq)\n        t = t.unsqueeze(dim=1)\n        return t\n\n\nclass MLP(nn.Module):\n    def __init__(self, *, width: int):\n        super().__init__()\n        self.width = width\n        self.fc1 = nn.Linear(width, width * 4)\n        self.fc2 = nn.Linear(width * 4, width)\n        self.gelu = nn.GELU()\n\n    def forward(self, x):\n        return self.fc2(self.gelu(self.fc1(x)))\n\n\nclass CrossAttention(nn.Module):\n    def __init__(\n        self,\n        qdim,\n        kdim,\n        num_heads,\n        qkv_bias=True,\n        qk_norm=False,\n        norm_layer=nn.LayerNorm,\n        with_decoupled_ca=False,\n        decoupled_ca_dim=16,\n        decoupled_ca_weight=1.0,\n        **kwargs,\n    ):\n        super().__init__()\n        self.qdim = qdim\n        self.kdim = kdim\n        self.num_heads = num_heads\n        assert self.qdim % num_heads == 0, \"self.qdim must be divisible by num_heads\"\n        self.head_dim = self.qdim // num_heads\n        assert self.head_dim % 8 == 0 and self.head_dim <= 128, \"Only support head_dim <= 128 and divisible by 8\"\n        self.scale = self.head_dim ** -0.5\n\n        self.to_q = nn.Linear(qdim, qdim, bias=qkv_bias)\n        self.to_k = nn.Linear(kdim, qdim, bias=qkv_bias)\n        self.to_v = nn.Linear(kdim, qdim, bias=qkv_bias)\n\n        # TODO: eps should be 1 / 65530 if using fp16\n        self.q_norm = norm_layer(self.head_dim, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.k_norm = norm_layer(self.head_dim, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.out_proj = nn.Linear(qdim, qdim, bias=True)\n\n        self.with_dca = with_decoupled_ca\n        if self.with_dca:\n            self.kv_proj_dca = nn.Linear(kdim, 2 * qdim, bias=qkv_bias)\n            self.k_norm_dca = norm_layer(self.head_dim, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n            self.dca_dim = decoupled_ca_dim\n            self.dca_weight = decoupled_ca_weight\n\n    def forward(self, x, y):\n        \"\"\"\n        Parameters\n        ----------\n        x: torch.Tensor\n            (batch, seqlen1, hidden_dim) (where hidden_dim = num heads * head dim)\n        y: torch.Tensor\n            (batch, seqlen2, hidden_dim2)\n        freqs_cis_img: torch.Tensor\n            (batch, hidden_dim // 2), RoPE for image\n        \"\"\"\n        b, s1, c = x.shape  # [b, s1, D]\n\n        if self.with_dca:\n            token_len = y.shape[1]\n            context_dca = y[:, -self.dca_dim:, :]\n            kv_dca = self.kv_proj_dca(context_dca).view(b, self.dca_dim, 2, self.num_heads, self.head_dim)\n            k_dca, v_dca = kv_dca.unbind(dim=2)  # [b, s, h, d]\n            k_dca = self.k_norm_dca(k_dca)\n            y = y[:, :(token_len - self.dca_dim), :]\n\n        _, s2, c = y.shape  # [b, s2, 1024]\n        q = self.to_q(x)\n        k = self.to_k(y)\n        v = self.to_v(y)\n\n        kv = torch.cat((k, v), dim=-1)\n        split_size = kv.shape[-1] // self.num_heads // 2\n        kv = kv.view(1, -1, self.num_heads, split_size * 2)\n        k, v = torch.split(kv, split_size, dim=-1)\n\n        q = q.view(b, s1, self.num_heads, self.head_dim)  # [b, s1, h, d]\n        k = k.view(b, s2, self.num_heads, self.head_dim)  # [b, s2, h, d]\n        v = v.view(b, s2, self.num_heads, self.head_dim)  # [b, s2, h, d]\n\n        q = self.q_norm(q)\n        k = self.k_norm(k)\n\n        with torch.backends.cuda.sdp_kernel(\n            enable_flash=True,\n            enable_math=False,\n            enable_mem_efficient=True\n        ):\n            q, k, v = map(lambda t: rearrange(t, 'b n h d -> b h n d', h=self.num_heads), (q, k, v))\n            context = F.scaled_dot_product_attention(\n                q, k, v\n            ).transpose(1, 2).reshape(b, s1, -1)\n\n        if self.with_dca:\n            with torch.backends.cuda.sdp_kernel(\n                enable_flash=True,\n                enable_math=False,\n                enable_mem_efficient=True\n            ):\n                k_dca, v_dca = map(lambda t: rearrange(t, 'b n h d -> b h n d', h=self.num_heads),\n                                   (k_dca, v_dca))\n                context_dca = F.scaled_dot_product_attention(\n                    q, k_dca, v_dca).transpose(1, 2).reshape(b, s1, -1)\n\n            context = context + self.dca_weight * context_dca\n\n        out = self.out_proj(context)  # context.reshape - B, L1, -1\n\n        return out\n\n\nclass Attention(nn.Module):\n    \"\"\"\n    We rename some layer names to align with flash attention\n    \"\"\"\n\n    def __init__(\n        self,\n        dim,\n        num_heads,\n        qkv_bias=True,\n        qk_norm=False,\n        norm_layer=nn.LayerNorm,\n    ):\n        super().__init__()\n        self.dim = dim\n        self.num_heads = num_heads\n        assert self.dim % num_heads == 0, 'dim should be divisible by num_heads'\n        self.head_dim = self.dim // num_heads\n        # This assertion is aligned with flash attention\n        assert self.head_dim % 8 == 0 and self.head_dim <= 128, \"Only support head_dim <= 128 and divisible by 8\"\n        self.scale = self.head_dim ** -0.5\n\n        self.to_q = nn.Linear(dim, dim, bias=qkv_bias)\n        self.to_k = nn.Linear(dim, dim, bias=qkv_bias)\n        self.to_v = nn.Linear(dim, dim, bias=qkv_bias)\n        # TODO: eps should be 1 / 65530 if using fp16\n        self.q_norm = norm_layer(self.head_dim, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.k_norm = norm_layer(self.head_dim, elementwise_affine=True, eps=1e-6) if qk_norm else nn.Identity()\n        self.out_proj = nn.Linear(dim, dim)\n\n    def forward(self, x):\n        B, N, C = x.shape\n\n        q = self.to_q(x)\n        k = self.to_k(x)\n        v = self.to_v(x)\n\n        qkv = torch.cat((q, k, v), dim=-1)\n        split_size = qkv.shape[-1] // self.num_heads // 3\n        qkv = qkv.view(1, -1, self.num_heads, split_size * 3)\n        q, k, v = torch.split(qkv, split_size, dim=-1)\n\n        q = q.reshape(B, N, self.num_heads, self.head_dim).transpose(1, 2)  # [b, h, s, d]\n        k = k.reshape(B, N, self.num_heads, self.head_dim).transpose(1, 2)  # [b, h, s, d]\n        v = v.reshape(B, N, self.num_heads, self.head_dim).transpose(1, 2)\n\n        q = self.q_norm(q)  # [b, h, s, d]\n        k = self.k_norm(k)  # [b, h, s, d]\n\n        with torch.backends.cuda.sdp_kernel(\n            enable_flash=True,\n            enable_math=False,\n            enable_mem_efficient=True\n        ):\n            x = F.scaled_dot_product_attention(q, k, v)\n            x = x.transpose(1, 2).reshape(B, N, -1)\n\n        x = self.out_proj(x)\n        return x\n\n\nclass HunYuanDiTBlock(nn.Module):\n    def __init__(\n        self,\n        hidden_size,\n        c_emb_size,\n        num_heads,\n        text_states_dim=1024,\n        use_flash_attn=False,\n        qk_norm=False,\n        norm_layer=nn.LayerNorm,\n        qk_norm_layer=nn.RMSNorm,\n        with_decoupled_ca=False,\n        decoupled_ca_dim=16,\n        decoupled_ca_weight=1.0,\n        init_scale=1.0,\n        qkv_bias=True,\n        skip_connection=True,\n        timested_modulate=False,\n        use_moe: bool = False,\n        num_experts: int = 8,\n        moe_top_k: int = 2,\n        **kwargs,\n    ):\n        super().__init__()\n        self.use_flash_attn = use_flash_attn\n        use_ele_affine = True\n\n        # ========================= Self-Attention =========================\n        self.norm1 = norm_layer(hidden_size, elementwise_affine=use_ele_affine, eps=1e-6)\n        self.attn1 = Attention(hidden_size, num_heads=num_heads, qkv_bias=qkv_bias, qk_norm=qk_norm,\n                               norm_layer=qk_norm_layer)\n\n        # ========================= FFN =========================\n        self.norm2 = norm_layer(hidden_size, elementwise_affine=use_ele_affine, eps=1e-6)\n\n        # ========================= Add =========================\n        # Simply use add like SDXL.\n        self.timested_modulate = timested_modulate\n        if self.timested_modulate:\n            self.default_modulation = nn.Sequential(\n                nn.SiLU(),\n                nn.Linear(c_emb_size, hidden_size, bias=True)\n            )\n\n        # ========================= Cross-Attention =========================\n        self.attn2 = CrossAttention(hidden_size, text_states_dim, num_heads=num_heads, qkv_bias=qkv_bias,\n                                    qk_norm=qk_norm, norm_layer=qk_norm_layer,\n                                    with_decoupled_ca=with_decoupled_ca, decoupled_ca_dim=decoupled_ca_dim,\n                                    decoupled_ca_weight=decoupled_ca_weight, init_scale=init_scale,\n                                    )\n        self.norm3 = norm_layer(hidden_size, elementwise_affine=True, eps=1e-6)\n\n        if skip_connection:\n            self.skip_norm = norm_layer(hidden_size, elementwise_affine=True, eps=1e-6)\n            self.skip_linear = nn.Linear(2 * hidden_size, hidden_size)\n        else:\n            self.skip_linear = None\n\n        self.use_moe = use_moe\n        if self.use_moe:\n            print(\"using moe\")\n            self.moe = MoEBlock(\n                hidden_size,\n                num_experts=num_experts,\n                moe_top_k=moe_top_k,\n                dropout=0.0,\n                activation_fn=\"gelu\",\n                final_dropout=False,\n                ff_inner_dim=int(hidden_size * 4.0),\n                ff_bias=True,\n            )\n        else:\n            self.mlp = MLP(width=hidden_size)\n\n    def forward(self, x, c=None, text_states=None, skip_value=None):\n\n        if self.skip_linear is not None:\n            cat = torch.cat([skip_value, x], dim=-1)\n            x = self.skip_linear(cat)\n            x = self.skip_norm(x)\n\n        # Self-Attention\n        if self.timested_modulate:\n            shift_msa = self.default_modulation(c).unsqueeze(dim=1)\n            x = x + shift_msa\n\n        attn_out = self.attn1(self.norm1(x))\n\n        x = x + attn_out\n\n        # Cross-Attention\n        x = x + self.attn2(self.norm2(x), text_states)\n\n        # FFN Layer\n        mlp_inputs = self.norm3(x)\n\n        if self.use_moe:\n            x = x + self.moe(mlp_inputs)\n        else:\n            x = x + self.mlp(mlp_inputs)\n\n        return x\n\n\nclass AttentionPool(nn.Module):\n    def __init__(self, spacial_dim: int, embed_dim: int, num_heads: int, output_dim: int = None):\n        super().__init__()\n        self.positional_embedding = nn.Parameter(torch.randn(spacial_dim + 1, embed_dim) / embed_dim ** 0.5)\n        self.k_proj = nn.Linear(embed_dim, embed_dim)\n        self.q_proj = nn.Linear(embed_dim, embed_dim)\n        self.v_proj = nn.Linear(embed_dim, embed_dim)\n        self.c_proj = nn.Linear(embed_dim, output_dim or embed_dim)\n        self.num_heads = num_heads\n\n    def forward(self, x, attention_mask=None):\n        x = x.permute(1, 0, 2)  # NLC -> LNC\n        if attention_mask is not None:\n            attention_mask = attention_mask.unsqueeze(-1).permute(1, 0, 2)\n            global_emb = (x * attention_mask).sum(dim=0) / attention_mask.sum(dim=0)\n            x = torch.cat([global_emb[None,], x], dim=0)\n\n        else:\n            x = torch.cat([x.mean(dim=0, keepdim=True), x], dim=0)  # (L+1)NC\n        x = x + self.positional_embedding[:, None, :].to(x.dtype)  # (L+1)NC\n        x, _ = F.multi_head_attention_forward(\n            query=x[:1], key=x, value=x,\n            embed_dim_to_check=x.shape[-1],\n            num_heads=self.num_heads,\n            q_proj_weight=self.q_proj.weight,\n            k_proj_weight=self.k_proj.weight,\n            v_proj_weight=self.v_proj.weight,\n            in_proj_weight=None,\n            in_proj_bias=torch.cat([self.q_proj.bias, self.k_proj.bias, self.v_proj.bias]),\n            bias_k=None,\n            bias_v=None,\n            add_zero_attn=False,\n            dropout_p=0,\n            out_proj_weight=self.c_proj.weight,\n            out_proj_bias=self.c_proj.bias,\n            use_separate_proj_weight=True,\n            training=self.training,\n            need_weights=False\n        )\n        return x.squeeze(0)\n\n\nclass FinalLayer(nn.Module):\n    \"\"\"\n    The final layer of HunYuanDiT.\n    \"\"\"\n\n    def __init__(self, final_hidden_size, out_channels):\n        super().__init__()\n        self.final_hidden_size = final_hidden_size\n        self.norm_final = nn.LayerNorm(final_hidden_size, elementwise_affine=True, eps=1e-6)\n        self.linear = nn.Linear(final_hidden_size, out_channels, bias=True)\n\n    def forward(self, x):\n        x = self.norm_final(x)\n        x = x[:, 1:]\n        x = self.linear(x)\n        return x\n\n\nclass HunYuanDiTPlain(nn.Module):\n\n    def __init__(\n        self,\n        input_size=1024,\n        in_channels=4,\n        hidden_size=1024,\n        context_dim=1024,\n        depth=24,\n        num_heads=16,\n        mlp_ratio=4.0,\n        norm_type='layer',\n        qk_norm_type='rms',\n        qk_norm=False,\n        text_len=257,\n        with_decoupled_ca=False,\n        additional_cond_hidden_state=768,\n        decoupled_ca_dim=16,\n        decoupled_ca_weight=1.0,\n        use_pos_emb=False,\n        use_attention_pooling=True,\n        guidance_cond_proj_dim=None,\n        qkv_bias=True,\n        num_moe_layers: int = 6,\n        num_experts: int = 8,\n        moe_top_k: int = 2,\n    ):\n        super().__init__()\n        self.input_size = input_size\n        self.depth = depth\n        self.in_channels = in_channels\n        self.out_channels = in_channels\n        self.num_heads = num_heads\n\n        self.hidden_size = hidden_size\n        self.norm = nn.LayerNorm if norm_type == 'layer' else nn.RMSNorm\n        self.qk_norm = nn.RMSNorm if qk_norm_type == 'rms' else nn.LayerNorm\n        self.context_dim = context_dim\n\n        self.with_decoupled_ca = with_decoupled_ca\n        self.decoupled_ca_dim = decoupled_ca_dim\n        self.decoupled_ca_weight = decoupled_ca_weight\n        self.use_pos_emb = use_pos_emb\n        self.use_attention_pooling = use_attention_pooling\n        self.guidance_cond_proj_dim = guidance_cond_proj_dim\n\n        self.text_len = text_len\n\n        self.x_embedder = nn.Linear(in_channels, hidden_size, bias=True)\n        self.t_embedder = TimestepEmbedder(hidden_size, hidden_size * 4, cond_proj_dim=guidance_cond_proj_dim)\n\n        # Will use fixed sin-cos embedding:\n        if self.use_pos_emb:\n            self.register_buffer(\"pos_embed\", torch.zeros(1, input_size, hidden_size))\n            pos = np.arange(self.input_size, dtype=np.float32)\n            pos_embed = get_1d_sincos_pos_embed_from_grid(self.pos_embed.shape[-1], pos)\n            self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))\n\n        self.use_attention_pooling = use_attention_pooling\n        if use_attention_pooling:\n            self.pooler = AttentionPool(self.text_len, context_dim, num_heads=8, output_dim=1024)\n            self.extra_embedder = nn.Sequential(\n                nn.Linear(1024, hidden_size * 4),\n                nn.SiLU(),\n                nn.Linear(hidden_size * 4, hidden_size, bias=True),\n            )\n\n        if with_decoupled_ca:\n            self.additional_cond_hidden_state = additional_cond_hidden_state\n            self.additional_cond_proj = nn.Sequential(\n                nn.Linear(additional_cond_hidden_state, hidden_size * 4),\n                nn.SiLU(),\n                nn.Linear(hidden_size * 4, 1024, bias=True),\n            )\n\n        # HUnYuanDiT Blocks\n        self.blocks = nn.ModuleList([\n            HunYuanDiTBlock(hidden_size=hidden_size,\n                            c_emb_size=hidden_size,\n                            num_heads=num_heads,\n                            mlp_ratio=mlp_ratio,\n                            text_states_dim=context_dim,\n                            qk_norm=qk_norm,\n                            norm_layer=self.norm,\n                            qk_norm_layer=self.qk_norm,\n                            skip_connection=layer > depth // 2,\n                            with_decoupled_ca=with_decoupled_ca,\n                            decoupled_ca_dim=decoupled_ca_dim,\n                            decoupled_ca_weight=decoupled_ca_weight,\n                            qkv_bias=qkv_bias,\n                            use_moe=True if depth - layer <= num_moe_layers else False,\n                            num_experts=num_experts,\n                            moe_top_k=moe_top_k\n                            )\n            for layer in range(depth)\n        ])\n        self.depth = depth\n\n        self.final_layer = FinalLayer(hidden_size, self.out_channels)\n\n    def forward(self, x, t, contexts, **kwargs):\n        cond = contexts['main']\n\n        t = self.t_embedder(t, condition=kwargs.get('guidance_cond'))\n        x = self.x_embedder(x)\n\n        if self.use_pos_emb:\n            pos_embed = self.pos_embed.to(x.dtype)\n            x = x + pos_embed\n\n        if self.use_attention_pooling:\n            extra_vec = self.pooler(cond, None)\n            c = t + self.extra_embedder(extra_vec)  # [B, D]\n        else:\n            c = t\n\n        if self.with_decoupled_ca:\n            additional_cond = self.additional_cond_proj(contexts['additional'])\n            cond = torch.cat([cond, additional_cond], dim=1)\n\n        x = torch.cat([c, x], dim=1)\n\n        skip_value_list = []\n        for layer, block in enumerate(self.blocks):\n            skip_value = None if layer <= self.depth // 2 else skip_value_list.pop()\n            x = block(x, c, cond, skip_value=skip_value)\n            if layer < self.depth // 2:\n                skip_value_list.append(x)\n\n        x = self.final_layer(x)\n        return x\n"
  },
  {
    "path": "hy3dgen/shapegen/models/denoisers/moe_layers.py",
    "content": "import math\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom diffusers.models.attention import FeedForward\n\n\nclass AddAuxiliaryLoss(torch.autograd.Function):\n    \"\"\"\n    The trick function of adding auxiliary (aux) loss,\n    which includes the gradient of the aux loss during backpropagation.\n    \"\"\"\n\n    @staticmethod\n    def forward(ctx, x, loss):\n        assert loss.numel() == 1\n        ctx.dtype = loss.dtype\n        ctx.required_aux_loss = loss.requires_grad\n        return x\n\n    @staticmethod\n    def backward(ctx, grad_output):\n        grad_loss = None\n        if ctx.required_aux_loss:\n            grad_loss = torch.ones(1, dtype=ctx.dtype, device=grad_output.device)\n        return grad_output, grad_loss\n\n\nclass MoEGate(nn.Module):\n    def __init__(self, embed_dim, num_experts=16, num_experts_per_tok=2, aux_loss_alpha=0.01):\n        super().__init__()\n        self.top_k = num_experts_per_tok\n        self.n_routed_experts = num_experts\n\n        self.scoring_func = 'softmax'\n        self.alpha = aux_loss_alpha\n        self.seq_aux = False\n\n        # topk selection algorithm\n        self.norm_topk_prob = False\n        self.gating_dim = embed_dim\n        self.weight = nn.Parameter(torch.empty((self.n_routed_experts, self.gating_dim)))\n        self.reset_parameters()\n\n    def reset_parameters(self) -> None:\n        import torch.nn.init as init\n        init.kaiming_uniform_(self.weight, a=math.sqrt(5))\n\n    def forward(self, hidden_states):\n        bsz, seq_len, h = hidden_states.shape\n        # print(bsz, seq_len, h)\n        ### compute gating score\n        hidden_states = hidden_states.view(-1, h)\n        logits = F.linear(hidden_states, self.weight, None)\n        if self.scoring_func == 'softmax':\n            scores = logits.softmax(dim=-1)\n        else:\n            raise NotImplementedError(f'insupportable scoring function for MoE gating: {self.scoring_func}')\n\n        ### select top-k experts\n        topk_weight, topk_idx = torch.topk(scores, k=self.top_k, dim=-1, sorted=False)\n\n        ### norm gate to sum 1\n        if self.top_k > 1 and self.norm_topk_prob:\n            denominator = topk_weight.sum(dim=-1, keepdim=True) + 1e-20\n            topk_weight = topk_weight / denominator\n\n        ### expert-level computation auxiliary loss\n        if self.training and self.alpha > 0.0:\n            scores_for_aux = scores\n            aux_topk = self.top_k\n            # always compute aux loss based on the naive greedy topk method\n            topk_idx_for_aux_loss = topk_idx.view(bsz, -1)\n            if self.seq_aux:\n                scores_for_seq_aux = scores_for_aux.view(bsz, seq_len, -1)\n                ce = torch.zeros(bsz, self.n_routed_experts, device=hidden_states.device)\n                ce.scatter_add_(1, topk_idx_for_aux_loss,\n                                torch.ones(bsz, seq_len * aux_topk, device=hidden_states.device)).div_(\n                    seq_len * aux_topk / self.n_routed_experts)\n                aux_loss = (ce * scores_for_seq_aux.mean(dim=1)).sum(dim=1).mean() * self.alpha\n            else:\n                mask_ce = F.one_hot(topk_idx_for_aux_loss.view(-1), num_classes=self.n_routed_experts)\n                ce = mask_ce.float().mean(0)\n                Pi = scores_for_aux.mean(0)\n                fi = ce * self.n_routed_experts\n                aux_loss = (Pi * fi).sum() * self.alpha\n        else:\n            aux_loss = None\n        return topk_idx, topk_weight, aux_loss\n\n\nclass MoEBlock(nn.Module):\n    def __init__(self, dim, num_experts=8, moe_top_k=2,\n                 activation_fn=\"gelu\", dropout=0.0, final_dropout=False,\n                 ff_inner_dim=None, ff_bias=True):\n        super().__init__()\n        self.moe_top_k = moe_top_k\n        self.experts = nn.ModuleList([\n            FeedForward(dim, dropout=dropout, activation_fn=activation_fn, final_dropout=final_dropout,\n                        inner_dim=ff_inner_dim, bias=ff_bias)\n            for i in range(num_experts)])\n        self.gate = MoEGate(embed_dim=dim, num_experts=num_experts, num_experts_per_tok=moe_top_k)\n\n        self.shared_experts = FeedForward(dim, dropout=dropout, activation_fn=activation_fn,\n                                          final_dropout=final_dropout, inner_dim=ff_inner_dim,\n                                          bias=ff_bias)\n\n    def initialize_weight(self):\n        pass\n\n    def forward(self, hidden_states):\n        identity = hidden_states\n        orig_shape = hidden_states.shape\n        topk_idx, topk_weight, aux_loss = self.gate(hidden_states)\n\n        hidden_states = hidden_states.view(-1, hidden_states.shape[-1])\n        flat_topk_idx = topk_idx.view(-1)\n        if self.training:\n            hidden_states = hidden_states.repeat_interleave(self.moe_top_k, dim=0)\n            y = torch.empty_like(hidden_states, dtype=hidden_states.dtype)\n            for i, expert in enumerate(self.experts):\n                y[flat_topk_idx == i] = expert(hidden_states[flat_topk_idx == i])\n            y = (y.view(*topk_weight.shape, -1) * topk_weight.unsqueeze(-1)).sum(dim=1)\n            y = y.view(*orig_shape)\n            y = AddAuxiliaryLoss.apply(y, aux_loss)\n        else:\n            y = self.moe_infer(hidden_states, flat_topk_idx, topk_weight.view(-1, 1)).view(*orig_shape)\n        y = y + self.shared_experts(identity)\n        return y\n\n    @torch.no_grad()\n    def moe_infer(self, x, flat_expert_indices, flat_expert_weights):\n        expert_cache = torch.zeros_like(x)\n        idxs = flat_expert_indices.argsort()\n        tokens_per_expert = flat_expert_indices.bincount().cpu().numpy().cumsum(0)\n        token_idxs = idxs // self.moe_top_k\n        for i, end_idx in enumerate(tokens_per_expert):\n            start_idx = 0 if i == 0 else tokens_per_expert[i - 1]\n            if start_idx == end_idx:\n                continue\n            expert = self.experts[i]\n            exp_token_idx = token_idxs[start_idx:end_idx]\n            expert_tokens = x[exp_token_idx]\n            expert_out = expert(expert_tokens)\n            expert_out.mul_(flat_expert_weights[idxs[start_idx:end_idx]])\n\n            # for fp16 and other dtype\n            expert_cache = expert_cache.to(expert_out.dtype)\n            expert_cache.scatter_reduce_(0, exp_token_idx.view(-1, 1).repeat(1, x.shape[-1]), expert_out, reduce='sum')\n        return expert_cache\n"
  },
  {
    "path": "hy3dgen/shapegen/pipelines.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport copy\nimport importlib\nimport inspect\nimport os\nfrom typing import List, Optional, Union\n\nimport numpy as np\nimport torch\nimport trimesh\nimport yaml\nfrom PIL import Image\nfrom diffusers.utils.torch_utils import randn_tensor\nfrom diffusers.utils.import_utils import is_accelerate_version, is_accelerate_available\nfrom tqdm import tqdm\n\nfrom .models.autoencoders import ShapeVAE\nfrom .models.autoencoders import SurfaceExtractors\nfrom .utils import logger, synchronize_timer, smart_load_model\n\n\ndef retrieve_timesteps(\n    scheduler,\n    num_inference_steps: Optional[int] = None,\n    device: Optional[Union[str, torch.device]] = None,\n    timesteps: Optional[List[int]] = None,\n    sigmas: Optional[List[float]] = None,\n    **kwargs,\n):\n    \"\"\"\n    Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles\n    custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`.\n\n    Args:\n        scheduler (`SchedulerMixin`):\n            The scheduler to get timesteps from.\n        num_inference_steps (`int`):\n            The number of diffusion steps used when generating samples with a pre-trained model. If used, `timesteps`\n            must be `None`.\n        device (`str` or `torch.device`, *optional*):\n            The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.\n        timesteps (`List[int]`, *optional*):\n            Custom timesteps used to override the timestep spacing strategy of the scheduler. If `timesteps` is passed,\n            `num_inference_steps` and `sigmas` must be `None`.\n        sigmas (`List[float]`, *optional*):\n            Custom sigmas used to override the timestep spacing strategy of the scheduler. If `sigmas` is passed,\n            `num_inference_steps` and `timesteps` must be `None`.\n\n    Returns:\n        `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the\n        second element is the number of inference steps.\n    \"\"\"\n    if timesteps is not None and sigmas is not None:\n        raise ValueError(\"Only one of `timesteps` or `sigmas` can be passed. Please choose one to set custom values\")\n    if timesteps is not None:\n        accepts_timesteps = \"timesteps\" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())\n        if not accepts_timesteps:\n            raise ValueError(\n                f\"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom\"\n                f\" timestep schedules. Please check whether you are using the correct scheduler.\"\n            )\n        scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)\n        timesteps = scheduler.timesteps\n        num_inference_steps = len(timesteps)\n    elif sigmas is not None:\n        accept_sigmas = \"sigmas\" in set(inspect.signature(scheduler.set_timesteps).parameters.keys())\n        if not accept_sigmas:\n            raise ValueError(\n                f\"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom\"\n                f\" sigmas schedules. Please check whether you are using the correct scheduler.\"\n            )\n        scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)\n        timesteps = scheduler.timesteps\n        num_inference_steps = len(timesteps)\n    else:\n        scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)\n        timesteps = scheduler.timesteps\n    return timesteps, num_inference_steps\n\n\n@synchronize_timer('Export to trimesh')\ndef export_to_trimesh(mesh_output):\n    if isinstance(mesh_output, list):\n        outputs = []\n        for mesh in mesh_output:\n            if mesh is None:\n                outputs.append(None)\n            else:\n                mesh.mesh_f = mesh.mesh_f[:, ::-1]\n                mesh_output = trimesh.Trimesh(mesh.mesh_v, mesh.mesh_f)\n                outputs.append(mesh_output)\n        return outputs\n    else:\n        mesh_output.mesh_f = mesh_output.mesh_f[:, ::-1]\n        mesh_output = trimesh.Trimesh(mesh_output.mesh_v, mesh_output.mesh_f)\n        return mesh_output\n\n\ndef get_obj_from_str(string, reload=False):\n    module, cls = string.rsplit(\".\", 1)\n    if reload:\n        module_imp = importlib.import_module(module)\n        importlib.reload(module_imp)\n    return getattr(importlib.import_module(module, package=None), cls)\n\n\ndef instantiate_from_config(config, **kwargs):\n    if \"target\" not in config:\n        raise KeyError(\"Expected key `target` to instantiate.\")\n    try:\n        target = config['target']\n        cls = get_obj_from_str(target)\n    except Exception as e:\n        target = config['target'].replace(\"hy3dshape\", \"hy3dgen.shapegen\")\n        cls = get_obj_from_str(target)\n    params = config.get(\"params\", dict())\n    kwargs.update(params)\n    instance = cls(**kwargs)\n    return instance\n\n\nclass Hunyuan3DDiTPipeline:\n    model_cpu_offload_seq = \"conditioner->model->vae\"\n    _exclude_from_cpu_offload = []\n\n    @classmethod\n    @synchronize_timer('Hunyuan3DDiTPipeline Model Loading')\n    def from_single_file(\n        cls,\n        ckpt_path,\n        config_path,\n        device='cuda',\n        dtype=torch.float16,\n        use_safetensors=None,\n        **kwargs,\n    ):\n        # load config\n        with open(config_path, 'r') as f:\n            config = yaml.safe_load(f)\n\n        # load ckpt\n        if use_safetensors:\n            ckpt_path = ckpt_path.replace('.ckpt', '.safetensors')\n        if not os.path.exists(ckpt_path):\n            raise FileNotFoundError(f\"Model file {ckpt_path} not found\")\n        logger.info(f\"Loading model from {ckpt_path}\")\n\n        if use_safetensors:\n            # parse safetensors\n            import safetensors.torch\n            safetensors_ckpt = safetensors.torch.load_file(ckpt_path, device='cpu')\n            ckpt = {}\n            for key, value in safetensors_ckpt.items():\n                model_name = key.split('.')[0]\n                new_key = key[len(model_name) + 1:]\n                if model_name not in ckpt:\n                    ckpt[model_name] = {}\n                ckpt[model_name][new_key] = value\n        else:\n            ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=True)\n        # load model\n        model = instantiate_from_config(config['model'])\n        model.load_state_dict(ckpt['model'])\n        vae = instantiate_from_config(config['vae'])\n        vae.load_state_dict(ckpt['vae'], strict=False)\n        conditioner = instantiate_from_config(config['conditioner'])\n        if 'conditioner' in ckpt:\n            conditioner.load_state_dict(ckpt['conditioner'])\n        image_processor = instantiate_from_config(config['image_processor'])\n        scheduler = instantiate_from_config(config['scheduler'])\n\n        model_kwargs = dict(\n            vae=vae,\n            model=model,\n            scheduler=scheduler,\n            conditioner=conditioner,\n            image_processor=image_processor,\n            device=device,\n            dtype=dtype,\n        )\n        model_kwargs.update(kwargs)\n\n        return cls(\n            **model_kwargs\n        )\n\n    @classmethod\n    def from_pretrained(\n        cls,\n        model_path,\n        device='cuda',\n        dtype=torch.float16,\n        use_safetensors=True,\n        variant='fp16',\n        subfolder='hunyuan3d-dit-v2-0',\n        **kwargs,\n    ):\n        kwargs['from_pretrained_kwargs'] = dict(\n            model_path=model_path,\n            subfolder=subfolder,\n            use_safetensors=use_safetensors,\n            variant=variant,\n            dtype=dtype,\n            device=device,\n        )\n        config_path, ckpt_path = smart_load_model(\n            model_path,\n            subfolder=subfolder,\n            use_safetensors=use_safetensors,\n            variant=variant\n        )\n        return cls.from_single_file(\n            ckpt_path,\n            config_path,\n            device=device,\n            dtype=dtype,\n            use_safetensors=use_safetensors,\n            **kwargs\n        )\n\n    def __init__(\n        self,\n        vae,\n        model,\n        scheduler,\n        conditioner,\n        image_processor,\n        device='cuda',\n        dtype=torch.float16,\n        **kwargs\n    ):\n        self.vae = vae\n        self.model = model\n        self.scheduler = scheduler\n        self.conditioner = conditioner\n        self.image_processor = image_processor\n        self.kwargs = kwargs\n        self.to(device, dtype)\n\n    def compile(self):\n        self.vae = torch.compile(self.vae)\n        self.model = torch.compile(self.model)\n        self.conditioner = torch.compile(self.conditioner)\n\n    def enable_flashvdm(\n        self,\n        enabled: bool = True,\n        adaptive_kv_selection=True,\n        topk_mode='mean',\n        mc_algo='mc',\n        replace_vae=True,\n    ):\n        if enabled:\n            model_path = self.kwargs['from_pretrained_kwargs']['model_path']\n            turbo_vae_mapping = {\n                'Hunyuan3D-2': ('tencent/Hunyuan3D-2', 'hunyuan3d-vae-v2-0-turbo'),\n                'Hunyuan3D-2mv': ('tencent/Hunyuan3D-2', 'hunyuan3d-vae-v2-0-turbo'),\n                'Hunyuan3D-2mini': ('tencent/Hunyuan3D-2mini', 'hunyuan3d-vae-v2-mini-turbo'),\n            }\n            model_name = model_path.split('/')[-1]\n            if replace_vae and model_name in turbo_vae_mapping:\n                model_path, subfolder = turbo_vae_mapping[model_name]\n                self.vae = ShapeVAE.from_pretrained(\n                    model_path, subfolder=subfolder,\n                    use_safetensors=self.kwargs['from_pretrained_kwargs']['use_safetensors'],\n                    device=self.device,\n                )\n            self.vae.enable_flashvdm_decoder(\n                enabled=enabled,\n                adaptive_kv_selection=adaptive_kv_selection,\n                topk_mode=topk_mode,\n                mc_algo=mc_algo\n            )\n        else:\n            model_path = self.kwargs['from_pretrained_kwargs']['model_path']\n            vae_mapping = {\n                'Hunyuan3D-2': ('tencent/Hunyuan3D-2', 'hunyuan3d-vae-v2-0'),\n                'Hunyuan3D-2mv': ('tencent/Hunyuan3D-2', 'hunyuan3d-vae-v2-0'),\n                'Hunyuan3D-2mini': ('tencent/Hunyuan3D-2mini', 'hunyuan3d-vae-v2-mini'),\n            }\n            model_name = model_path.split('/')[-1]\n            if model_name in vae_mapping:\n                model_path, subfolder = vae_mapping[model_name]\n                self.vae = ShapeVAE.from_pretrained(model_path, subfolder=subfolder)\n            self.vae.enable_flashvdm_decoder(enabled=False)\n\n    def to(self, device=None, dtype=None):\n        if dtype is not None:\n            self.dtype = dtype\n            self.vae.to(dtype=dtype)\n            self.model.to(dtype=dtype)\n            self.conditioner.to(dtype=dtype)\n        if device is not None:\n            self.device = torch.device(device)\n            self.vae.to(device)\n            self.model.to(device)\n            self.conditioner.to(device)\n\n    @property\n    def _execution_device(self):\n        r\"\"\"\n        Returns the device on which the pipeline's models will be executed. After calling\n        [`~DiffusionPipeline.enable_sequential_cpu_offload`] the execution device can only be inferred from\n        Accelerate's module hooks.\n        \"\"\"\n        for name, model in self.components.items():\n            if not isinstance(model, torch.nn.Module) or name in self._exclude_from_cpu_offload:\n                continue\n\n            if not hasattr(model, \"_hf_hook\"):\n                return self.device\n            for module in model.modules():\n                if (\n                    hasattr(module, \"_hf_hook\")\n                    and hasattr(module._hf_hook, \"execution_device\")\n                    and module._hf_hook.execution_device is not None\n                ):\n                    return torch.device(module._hf_hook.execution_device)\n        return self.device\n\n    def enable_model_cpu_offload(self, gpu_id: Optional[int] = None, device: Union[torch.device, str] = \"cuda\"):\n        r\"\"\"\n        Offloads all models to CPU using accelerate, reducing memory usage with a low impact on performance. Compared\n        to `enable_sequential_cpu_offload`, this method moves one whole model at a time to the GPU when its `forward`\n        method is called, and the model remains in GPU until the next model runs. Memory savings are lower than with\n        `enable_sequential_cpu_offload`, but performance is much better due to the iterative execution of the `unet`.\n\n        Arguments:\n            gpu_id (`int`, *optional*):\n                The ID of the accelerator that shall be used in inference. If not specified, it will default to 0.\n            device (`torch.Device` or `str`, *optional*, defaults to \"cuda\"):\n                The PyTorch device type of the accelerator that shall be used in inference. If not specified, it will\n                default to \"cuda\".\n        \"\"\"\n        if self.model_cpu_offload_seq is None:\n            raise ValueError(\n                \"Model CPU offload cannot be enabled because no `model_cpu_offload_seq` class attribute is set.\"\n            )\n\n        if is_accelerate_available() and is_accelerate_version(\">=\", \"0.17.0.dev0\"):\n            from accelerate import cpu_offload_with_hook\n        else:\n            raise ImportError(\"`enable_model_cpu_offload` requires `accelerate v0.17.0` or higher.\")\n\n        torch_device = torch.device(device)\n        device_index = torch_device.index\n\n        if gpu_id is not None and device_index is not None:\n            raise ValueError(\n                f\"You have passed both `gpu_id`={gpu_id} and an index as part of the passed device `device`={device}\"\n                f\"Cannot pass both. Please make sure to either not define `gpu_id` or not pass the index as part of the device: `device`={torch_device.type}\"\n            )\n\n        # _offload_gpu_id should be set to passed gpu_id (or id in passed `device`) or default to previously set id or default to 0\n        self._offload_gpu_id = gpu_id or torch_device.index or getattr(self, \"_offload_gpu_id\", 0)\n\n        device_type = torch_device.type\n        device = torch.device(f\"{device_type}:{self._offload_gpu_id}\")\n\n        if self.device.type != \"cpu\":\n            self.to(\"cpu\")\n            device_mod = getattr(torch, self.device.type, None)\n            if hasattr(device_mod, \"empty_cache\") and device_mod.is_available():\n                device_mod.empty_cache()  # otherwise we don't see the memory savings (but they probably exist)\n\n        all_model_components = {k: v for k, v in self.components.items() if isinstance(v, torch.nn.Module)}\n\n        self._all_hooks = []\n        hook = None\n        for model_str in self.model_cpu_offload_seq.split(\"->\"):\n            model = all_model_components.pop(model_str, None)\n            if not isinstance(model, torch.nn.Module):\n                continue\n\n            _, hook = cpu_offload_with_hook(model, device, prev_module_hook=hook)\n            self._all_hooks.append(hook)\n\n        # CPU offload models that are not in the seq chain unless they are explicitly excluded\n        # these models will stay on CPU until maybe_free_model_hooks is called\n        # some models cannot be in the seq chain because they are iteratively called, such as controlnet\n        for name, model in all_model_components.items():\n            if not isinstance(model, torch.nn.Module):\n                continue\n\n            if name in self._exclude_from_cpu_offload:\n                model.to(device)\n            else:\n                _, hook = cpu_offload_with_hook(model, device)\n                self._all_hooks.append(hook)\n\n    def maybe_free_model_hooks(self):\n        r\"\"\"\n        Function that offloads all components, removes all model hooks that were added when using\n        `enable_model_cpu_offload` and then applies them again. In case the model has not been offloaded this function\n        is a no-op. Make sure to add this function to the end of the `__call__` function of your pipeline so that it\n        functions correctly when applying enable_model_cpu_offload.\n        \"\"\"\n        if not hasattr(self, \"_all_hooks\") or len(self._all_hooks) == 0:\n            # `enable_model_cpu_offload` has not be called, so silently do nothing\n            return\n\n        for hook in self._all_hooks:\n            # offload model and remove hook from model\n            hook.offload()\n            hook.remove()\n\n        # make sure the model is in the same state as before calling it\n        self.enable_model_cpu_offload()\n\n    @synchronize_timer('Encode cond')\n    def encode_cond(self, image, additional_cond_inputs, do_classifier_free_guidance, dual_guidance):\n        bsz = image.shape[0]\n        cond = self.conditioner(image=image, **additional_cond_inputs)\n\n        if do_classifier_free_guidance:\n            un_cond = self.conditioner.unconditional_embedding(bsz, **additional_cond_inputs)\n\n            if dual_guidance:\n                un_cond_drop_main = copy.deepcopy(un_cond)\n                un_cond_drop_main['additional'] = cond['additional']\n\n                def cat_recursive(a, b, c):\n                    if isinstance(a, torch.Tensor):\n                        return torch.cat([a, b, c], dim=0).to(self.dtype)\n                    out = {}\n                    for k in a.keys():\n                        out[k] = cat_recursive(a[k], b[k], c[k])\n                    return out\n\n                cond = cat_recursive(cond, un_cond_drop_main, un_cond)\n            else:\n                def cat_recursive(a, b):\n                    if isinstance(a, torch.Tensor):\n                        return torch.cat([a, b], dim=0).to(self.dtype)\n                    out = {}\n                    for k in a.keys():\n                        out[k] = cat_recursive(a[k], b[k])\n                    return out\n\n                cond = cat_recursive(cond, un_cond)\n        return cond\n\n    def prepare_extra_step_kwargs(self, generator, eta):\n        # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature\n        # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.\n        # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502\n        # and should be between [0, 1]\n\n        accepts_eta = \"eta\" in set(inspect.signature(self.scheduler.step).parameters.keys())\n        extra_step_kwargs = {}\n        if accepts_eta:\n            extra_step_kwargs[\"eta\"] = eta\n\n        # check if the scheduler accepts generator\n        accepts_generator = \"generator\" in set(inspect.signature(self.scheduler.step).parameters.keys())\n        if accepts_generator:\n            extra_step_kwargs[\"generator\"] = generator\n        return extra_step_kwargs\n\n    def prepare_latents(self, batch_size, dtype, device, generator, latents=None):\n        shape = (batch_size, *self.vae.latent_shape)\n        if isinstance(generator, list) and len(generator) != batch_size:\n            raise ValueError(\n                f\"You have passed a list of generators of length {len(generator)}, but requested an effective batch\"\n                f\" size of {batch_size}. Make sure the batch size matches the length of the generators.\"\n            )\n\n        if latents is None:\n            latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)\n        else:\n            latents = latents.to(device)\n\n        # scale the initial noise by the standard deviation required by the scheduler\n        latents = latents * getattr(self.scheduler, 'init_noise_sigma', 1.0)\n        return latents\n\n    def prepare_image(self, image) -> dict:\n        if isinstance(image, str) and not os.path.exists(image):\n            raise FileNotFoundError(f\"Couldn't find image at path {image}\")\n\n        if not isinstance(image, list):\n            image = [image]\n\n        outputs = []\n        for img in image:\n            output = self.image_processor(img)\n            outputs.append(output)\n\n        cond_input = {k: [] for k in outputs[0].keys()}\n        for output in outputs:\n            for key, value in output.items():\n                cond_input[key].append(value)\n        for key, value in cond_input.items():\n            if isinstance(value[0], torch.Tensor):\n                cond_input[key] = torch.cat(value, dim=0)\n\n        return cond_input\n\n    def get_guidance_scale_embedding(self, w, embedding_dim=512, dtype=torch.float32):\n        \"\"\"\n        See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298\n\n        Args:\n            timesteps (`torch.Tensor`):\n                generate embedding vectors at these timesteps\n            embedding_dim (`int`, *optional*, defaults to 512):\n                dimension of the embeddings to generate\n            dtype:\n                data type of the generated embeddings\n\n        Returns:\n            `torch.FloatTensor`: Embedding vectors with shape `(len(timesteps), embedding_dim)`\n        \"\"\"\n        assert len(w.shape) == 1\n        w = w * 1000.0\n\n        half_dim = embedding_dim // 2\n        emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)\n        emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb)\n        emb = w.to(dtype)[:, None] * emb[None, :]\n        emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)\n        if embedding_dim % 2 == 1:  # zero pad\n            emb = torch.nn.functional.pad(emb, (0, 1))\n        assert emb.shape == (w.shape[0], embedding_dim)\n        return emb\n\n    def set_surface_extractor(self, mc_algo):\n        if mc_algo is None:\n            return\n        logger.info('The parameters `mc_algo` is deprecated, and will be removed in future versions.\\n'\n                    'Please use: \\n'\n                    'from hy3dgen.shapegen.models.autoencoders import SurfaceExtractors\\n'\n                    'pipeline.vae.surface_extractor = SurfaceExtractors[mc_algo]() instead\\n')\n        if mc_algo not in SurfaceExtractors.keys():\n            raise ValueError(f\"Unknown mc_algo {mc_algo}\")\n        self.vae.surface_extractor = SurfaceExtractors[mc_algo]()\n\n    @torch.no_grad()\n    def __call__(\n        self,\n        image: Union[str, List[str], Image.Image] = None,\n        num_inference_steps: int = 50,\n        timesteps: List[int] = None,\n        sigmas: List[float] = None,\n        eta: float = 0.0,\n        guidance_scale: float = 7.5,\n        dual_guidance_scale: float = 10.5,\n        dual_guidance: bool = True,\n        generator=None,\n        box_v=1.01,\n        octree_resolution=384,\n        mc_level=-1 / 512,\n        num_chunks=8000,\n        mc_algo=None,\n        output_type: Optional[str] = \"trimesh\",\n        enable_pbar=True,\n        **kwargs,\n    ) -> List[List[trimesh.Trimesh]]:\n        callback = kwargs.pop(\"callback\", None)\n        callback_steps = kwargs.pop(\"callback_steps\", None)\n\n        self.set_surface_extractor(mc_algo)\n\n        device = self.device\n        dtype = self.dtype\n        do_classifier_free_guidance = guidance_scale >= 0 and \\\n                                      getattr(self.model, 'guidance_cond_proj_dim', None) is None\n        dual_guidance = dual_guidance_scale >= 0 and dual_guidance\n\n        cond_inputs = self.prepare_image(image)\n        image = cond_inputs.pop('image')\n        cond = self.encode_cond(\n            image=image,\n            additional_cond_inputs=cond_inputs,\n            do_classifier_free_guidance=do_classifier_free_guidance,\n            dual_guidance=False,\n        )\n        batch_size = image.shape[0]\n\n        t_dtype = torch.long\n        timesteps, num_inference_steps = retrieve_timesteps(\n            self.scheduler, num_inference_steps, device, timesteps, sigmas)\n\n        latents = self.prepare_latents(batch_size, dtype, device, generator)\n        extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)\n\n        guidance_cond = None\n        if getattr(self.model, 'guidance_cond_proj_dim', None) is not None:\n            logger.info('Using lcm guidance scale')\n            guidance_scale_tensor = torch.tensor(guidance_scale - 1).repeat(batch_size)\n            guidance_cond = self.get_guidance_scale_embedding(\n                guidance_scale_tensor, embedding_dim=self.model.guidance_cond_proj_dim\n            ).to(device=device, dtype=latents.dtype)\n        with synchronize_timer('Diffusion Sampling'):\n            for i, t in enumerate(tqdm(timesteps, disable=not enable_pbar, desc=\"Diffusion Sampling:\", leave=False)):\n                # expand the latents if we are doing classifier free guidance\n                if do_classifier_free_guidance:\n                    latent_model_input = torch.cat([latents] * (3 if dual_guidance else 2))\n                else:\n                    latent_model_input = latents\n                latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n\n                # predict the noise residual\n                timestep_tensor = torch.tensor([t], dtype=t_dtype, device=device)\n                timestep_tensor = timestep_tensor.expand(latent_model_input.shape[0])\n                noise_pred = self.model(latent_model_input, timestep_tensor, cond, guidance_cond=guidance_cond)\n\n                # no drop, drop clip, all drop\n                if do_classifier_free_guidance:\n                    if dual_guidance:\n                        noise_pred_clip, noise_pred_dino, noise_pred_uncond = noise_pred.chunk(3)\n                        noise_pred = (\n                            noise_pred_uncond\n                            + guidance_scale * (noise_pred_clip - noise_pred_dino)\n                            + dual_guidance_scale * (noise_pred_dino - noise_pred_uncond)\n                        )\n                    else:\n                        noise_pred_cond, noise_pred_uncond = noise_pred.chunk(2)\n                        noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond)\n\n                # compute the previous noisy sample x_t -> x_t-1\n                outputs = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs)\n                latents = outputs.prev_sample\n\n                if callback is not None and i % callback_steps == 0:\n                    step_idx = i // getattr(self.scheduler, \"order\", 1)\n                    callback(step_idx, t, outputs)\n\n        return self._export(\n            latents,\n            output_type,\n            box_v, mc_level, num_chunks, octree_resolution, mc_algo,\n        )\n\n    def _export(\n        self,\n        latents,\n        output_type='trimesh',\n        box_v=1.01,\n        mc_level=0.0,\n        num_chunks=20000,\n        octree_resolution=256,\n        mc_algo='mc',\n        enable_pbar=True\n    ):\n        if not output_type == \"latent\":\n            latents = 1. / self.vae.scale_factor * latents\n            latents = self.vae(latents)\n            outputs = self.vae.latents2mesh(\n                latents,\n                bounds=box_v,\n                mc_level=mc_level,\n                num_chunks=num_chunks,\n                octree_resolution=octree_resolution,\n                mc_algo=mc_algo,\n                enable_pbar=enable_pbar,\n            )\n        else:\n            outputs = latents\n\n        if output_type == 'trimesh':\n            outputs = export_to_trimesh(outputs)\n\n        return outputs\n\n\nclass Hunyuan3DDiTFlowMatchingPipeline(Hunyuan3DDiTPipeline):\n\n    @torch.inference_mode()\n    def __call__(\n        self,\n        image: Union[str, List[str], Image.Image, dict, List[dict]] = None,\n        num_inference_steps: int = 50,\n        timesteps: List[int] = None,\n        sigmas: List[float] = None,\n        eta: float = 0.0,\n        guidance_scale: float = 5.0,\n        generator=None,\n        box_v=1.01,\n        octree_resolution=384,\n        mc_level=0.0,\n        mc_algo=None,\n        num_chunks=8000,\n        output_type: Optional[str] = \"trimesh\",\n        enable_pbar=True,\n        **kwargs,\n    ) -> List[List[trimesh.Trimesh]]:\n        callback = kwargs.pop(\"callback\", None)\n        callback_steps = kwargs.pop(\"callback_steps\", None)\n\n        self.set_surface_extractor(mc_algo)\n\n        device = self.device\n        dtype = self.dtype\n        do_classifier_free_guidance = guidance_scale >= 0 and not (\n            hasattr(self.model, 'guidance_embed') and\n            self.model.guidance_embed is True\n        )\n\n        cond_inputs = self.prepare_image(image)\n        image = cond_inputs.pop('image')\n        cond = self.encode_cond(\n            image=image,\n            additional_cond_inputs=cond_inputs,\n            do_classifier_free_guidance=do_classifier_free_guidance,\n            dual_guidance=False,\n        )\n        batch_size = image.shape[0]\n\n        # 5. Prepare timesteps\n        # NOTE: this is slightly different from common usage, we start from 0.\n        sigmas = np.linspace(0, 1, num_inference_steps) if sigmas is None else sigmas\n        timesteps, num_inference_steps = retrieve_timesteps(\n            self.scheduler,\n            num_inference_steps,\n            device,\n            sigmas=sigmas,\n        )\n        latents = self.prepare_latents(batch_size, dtype, device, generator)\n\n        guidance = None\n        if hasattr(self.model, 'guidance_embed') and \\\n            self.model.guidance_embed is True:\n            guidance = torch.tensor([guidance_scale] * batch_size, device=device, dtype=dtype)\n            # logger.info(f'Using guidance embed with scale {guidance_scale}')\n\n        with synchronize_timer('Diffusion Sampling'):\n            for i, t in enumerate(tqdm(timesteps, disable=not enable_pbar, desc=\"Diffusion Sampling:\")):\n                # expand the latents if we are doing classifier free guidance\n                if do_classifier_free_guidance:\n                    latent_model_input = torch.cat([latents] * 2)\n                else:\n                    latent_model_input = latents\n\n                # NOTE: we assume model get timesteps ranged from 0 to 1\n                timestep = t.expand(latent_model_input.shape[0]).to(\n                    latents.dtype) / self.scheduler.config.num_train_timesteps\n                noise_pred = self.model(latent_model_input, timestep, cond, guidance=guidance)\n\n                if do_classifier_free_guidance:\n                    noise_pred_cond, noise_pred_uncond = noise_pred.chunk(2)\n                    noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_cond - noise_pred_uncond)\n\n                # compute the previous noisy sample x_t -> x_t-1\n                outputs = self.scheduler.step(noise_pred, t, latents)\n                latents = outputs.prev_sample\n\n                if callback is not None and i % callback_steps == 0:\n                    step_idx = i // getattr(self.scheduler, \"order\", 1)\n                    callback(step_idx, t, outputs)\n\n        return self._export(\n            latents,\n            output_type,\n            box_v, mc_level, num_chunks, octree_resolution, mc_algo,\n            enable_pbar=enable_pbar,\n        )\n"
  },
  {
    "path": "hy3dgen/shapegen/postprocessors.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport os\nimport tempfile\nfrom typing import Union\n\nimport numpy as np\nimport pymeshlab\nimport torch\nimport trimesh\n\nfrom .models.autoencoders import Latent2MeshOutput\nfrom .utils import synchronize_timer\n\n\ndef load_mesh(path):\n    if path.endswith(\".glb\"):\n        mesh = trimesh.load(path)\n    else:\n        mesh = pymeshlab.MeshSet()\n        mesh.load_new_mesh(path)\n    return mesh\n\n\ndef reduce_face(mesh: pymeshlab.MeshSet, max_facenum: int = 200000):\n    if max_facenum > mesh.current_mesh().face_number():\n        return mesh\n\n    mesh.apply_filter(\n        \"meshing_decimation_quadric_edge_collapse\",\n        targetfacenum=max_facenum,\n        qualitythr=1.0,\n        preserveboundary=True,\n        boundaryweight=3,\n        preservenormal=True,\n        preservetopology=True,\n        autoclean=True\n    )\n    return mesh\n\n\ndef remove_floater(mesh: pymeshlab.MeshSet):\n    mesh.apply_filter(\"compute_selection_by_small_disconnected_components_per_face\",\n                      nbfaceratio=0.005)\n    mesh.apply_filter(\"compute_selection_transfer_face_to_vertex\", inclusive=False)\n    mesh.apply_filter(\"meshing_remove_selected_vertices_and_faces\")\n    return mesh\n\n\ndef pymeshlab2trimesh(mesh: pymeshlab.MeshSet):\n    with tempfile.NamedTemporaryFile(suffix='.ply', delete=False) as temp_file:\n        mesh.save_current_mesh(temp_file.name)\n        mesh = trimesh.load(temp_file.name)\n    # 检查加载的对象类型\n    if isinstance(mesh, trimesh.Scene):\n        combined_mesh = trimesh.Trimesh()\n        # 如果是Scene，遍历所有的geometry并合并\n        for geom in mesh.geometry.values():\n            combined_mesh = trimesh.util.concatenate([combined_mesh, geom])\n        mesh = combined_mesh\n    return mesh\n\n\ndef trimesh2pymeshlab(mesh: trimesh.Trimesh):\n    with tempfile.NamedTemporaryFile(suffix='.ply', delete=False) as temp_file:\n        if isinstance(mesh, trimesh.scene.Scene):\n            for idx, obj in enumerate(mesh.geometry.values()):\n                if idx == 0:\n                    temp_mesh = obj\n                else:\n                    temp_mesh = temp_mesh + obj\n            mesh = temp_mesh\n        mesh.export(temp_file.name)\n        mesh = pymeshlab.MeshSet()\n        mesh.load_new_mesh(temp_file.name)\n    return mesh\n\n\ndef export_mesh(input, output):\n    if isinstance(input, pymeshlab.MeshSet):\n        mesh = output\n    elif isinstance(input, Latent2MeshOutput):\n        output = Latent2MeshOutput()\n        output.mesh_v = output.current_mesh().vertex_matrix()\n        output.mesh_f = output.current_mesh().face_matrix()\n        mesh = output\n    else:\n        mesh = pymeshlab2trimesh(output)\n    return mesh\n\n\ndef import_mesh(mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str]) -> pymeshlab.MeshSet:\n    if isinstance(mesh, str):\n        mesh = load_mesh(mesh)\n    elif isinstance(mesh, Latent2MeshOutput):\n        mesh = pymeshlab.MeshSet()\n        mesh_pymeshlab = pymeshlab.Mesh(vertex_matrix=mesh.mesh_v, face_matrix=mesh.mesh_f)\n        mesh.add_mesh(mesh_pymeshlab, \"converted_mesh\")\n\n    if isinstance(mesh, (trimesh.Trimesh, trimesh.scene.Scene)):\n        mesh = trimesh2pymeshlab(mesh)\n\n    return mesh\n\n\nclass FaceReducer:\n    @synchronize_timer('FaceReducer')\n    def __call__(\n        self,\n        mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],\n        max_facenum: int = 40000\n    ) -> Union[pymeshlab.MeshSet, trimesh.Trimesh]:\n        ms = import_mesh(mesh)\n        ms = reduce_face(ms, max_facenum=max_facenum)\n        mesh = export_mesh(mesh, ms)\n        return mesh\n\n\nclass FloaterRemover:\n    @synchronize_timer('FloaterRemover')\n    def __call__(\n        self,\n        mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],\n    ) -> Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput]:\n        ms = import_mesh(mesh)\n        ms = remove_floater(ms)\n        mesh = export_mesh(mesh, ms)\n        return mesh\n\n\nclass DegenerateFaceRemover:\n    @synchronize_timer('DegenerateFaceRemover')\n    def __call__(\n        self,\n        mesh: Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput, str],\n    ) -> Union[pymeshlab.MeshSet, trimesh.Trimesh, Latent2MeshOutput]:\n        ms = import_mesh(mesh)\n\n        with tempfile.NamedTemporaryFile(suffix='.ply', delete=False) as temp_file:\n            ms.save_current_mesh(temp_file.name)\n            ms = pymeshlab.MeshSet()\n            ms.load_new_mesh(temp_file.name)\n\n        mesh = export_mesh(mesh, ms)\n        return mesh\n\n\ndef mesh_normalize(mesh):\n    \"\"\"\n    Normalize mesh vertices to sphere\n    \"\"\"\n    scale_factor = 1.2\n    vtx_pos = np.asarray(mesh.vertices)\n    max_bb = (vtx_pos - 0).max(0)[0]\n    min_bb = (vtx_pos - 0).min(0)[0]\n\n    center = (max_bb + min_bb) / 2\n\n    scale = torch.norm(torch.tensor(vtx_pos - center, dtype=torch.float32), dim=1).max() * 2.0\n\n    vtx_pos = (vtx_pos - center) * (scale_factor / float(scale))\n    mesh.vertices = vtx_pos\n\n    return mesh\n\n\nclass MeshSimplifier:\n    def __init__(self, executable: str = None):\n        if executable is None:\n            CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))\n            executable = os.path.join(CURRENT_DIR, \"mesh_simplifier.bin\")\n        self.executable = executable\n\n    @synchronize_timer('MeshSimplifier')\n    def __call__(\n        self,\n        mesh: Union[trimesh.Trimesh],\n    ) -> Union[trimesh.Trimesh]:\n        with tempfile.NamedTemporaryFile(suffix='.obj', delete=False) as temp_input:\n            with tempfile.NamedTemporaryFile(suffix='.obj', delete=False) as temp_output:\n                mesh.export(temp_input.name)\n                os.system(f'{self.executable} {temp_input.name} {temp_output.name}')\n                ms = trimesh.load(temp_output.name, process=False)\n                if isinstance(ms, trimesh.Scene):\n                    combined_mesh = trimesh.Trimesh()\n                    for geom in ms.geometry.values():\n                        combined_mesh = trimesh.util.concatenate([combined_mesh, geom])\n                    ms = combined_mesh\n                ms = mesh_normalize(ms)\n                return ms\n"
  },
  {
    "path": "hy3dgen/shapegen/preprocessors.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport cv2\nimport numpy as np\nimport torch\nfrom PIL import Image\nfrom einops import repeat, rearrange\n\n\ndef array_to_tensor(np_array):\n    image_pt = torch.tensor(np_array).float()\n    image_pt = image_pt / 255 * 2 - 1\n    image_pt = rearrange(image_pt, \"h w c -> c h w\")\n    image_pts = repeat(image_pt, \"c h w -> b c h w\", b=1)\n    return image_pts\n\n\nclass ImageProcessorV2:\n    def __init__(self, size=512, border_ratio=None):\n        self.size = size\n        self.border_ratio = border_ratio\n\n    @staticmethod\n    def recenter(image, border_ratio: float = 0.2):\n        \"\"\" recenter an image to leave some empty space at the image border.\n\n        Args:\n            image (ndarray): input image, float/uint8 [H, W, 3/4]\n            mask (ndarray): alpha mask, bool [H, W]\n            border_ratio (float, optional): border ratio, image will be resized to (1 - border_ratio). Defaults to 0.2.\n\n        Returns:\n            ndarray: output image, float/uint8 [H, W, 3/4]\n        \"\"\"\n\n        if image.shape[-1] == 4:\n            mask = image[..., 3]\n        else:\n            mask = np.ones_like(image[..., 0:1]) * 255\n            image = np.concatenate([image, mask], axis=-1)\n            mask = mask[..., 0]\n\n        H, W, C = image.shape\n\n        size = max(H, W)\n        result = np.zeros((size, size, C), dtype=np.uint8)\n\n        coords = np.nonzero(mask)\n        x_min, x_max = coords[0].min(), coords[0].max()\n        y_min, y_max = coords[1].min(), coords[1].max()\n        h = x_max - x_min\n        w = y_max - y_min\n        if h == 0 or w == 0:\n            raise ValueError('input image is empty')\n        desired_size = int(size * (1 - border_ratio))\n        scale = desired_size / max(h, w)\n        h2 = int(h * scale)\n        w2 = int(w * scale)\n        x2_min = (size - h2) // 2\n        x2_max = x2_min + h2\n\n        y2_min = (size - w2) // 2\n        y2_max = y2_min + w2\n\n        result[x2_min:x2_max, y2_min:y2_max] = cv2.resize(image[x_min:x_max, y_min:y_max], (w2, h2),\n                                                          interpolation=cv2.INTER_AREA)\n\n        bg = np.ones((result.shape[0], result.shape[1], 3), dtype=np.uint8) * 255\n\n        mask = result[..., 3:].astype(np.float32) / 255\n        result = result[..., :3] * mask + bg * (1 - mask)\n\n        mask = mask * 255\n        result = result.clip(0, 255).astype(np.uint8)\n        mask = mask.clip(0, 255).astype(np.uint8)\n        return result, mask\n\n    def load_image(self, image, border_ratio=0.15, to_tensor=True):\n        if isinstance(image, str):\n            image = cv2.imread(image, cv2.IMREAD_UNCHANGED)\n            image, mask = self.recenter(image, border_ratio=border_ratio)\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        elif isinstance(image, Image.Image):\n            image = image.convert(\"RGBA\")\n            image = np.asarray(image)\n            image, mask = self.recenter(image, border_ratio=border_ratio)\n\n        image = cv2.resize(image, (self.size, self.size), interpolation=cv2.INTER_CUBIC)\n        mask = cv2.resize(mask, (self.size, self.size), interpolation=cv2.INTER_NEAREST)\n        mask = mask[..., np.newaxis]\n\n        if to_tensor:\n            image = array_to_tensor(image)\n            mask = array_to_tensor(mask)\n        return image, mask\n\n    def __call__(self, image, border_ratio=0.15, to_tensor=True, **kwargs):\n        if self.border_ratio is not None:\n            border_ratio = self.border_ratio\n        image, mask = self.load_image(image, border_ratio=border_ratio, to_tensor=to_tensor)\n        outputs = {\n            'image': image,\n            'mask': mask\n        }\n        return outputs\n\n\nclass MVImageProcessorV2(ImageProcessorV2):\n    \"\"\"\n    view order: front, front clockwise 90, back, front clockwise 270\n    \"\"\"\n    return_view_idx = True\n\n    def __init__(self, size=512, border_ratio=None):\n        super().__init__(size, border_ratio)\n        self.view2idx = {\n            'front': 0,\n            'left': 1,\n            'back': 2,\n            'right': 3\n        }\n\n    def __call__(self, image_dict, border_ratio=0.15, to_tensor=True, **kwargs):\n        if self.border_ratio is not None:\n            border_ratio = self.border_ratio\n\n        images = []\n        masks = []\n        view_idxs = []\n        for idx, (view_tag, image) in enumerate(image_dict.items()):\n            view_idxs.append(self.view2idx[view_tag])\n            image, mask = self.load_image(image, border_ratio=border_ratio, to_tensor=to_tensor)\n            images.append(image)\n            masks.append(mask)\n\n        zipped_lists = zip(view_idxs, images, masks)\n        sorted_zipped_lists = sorted(zipped_lists)\n        view_idxs, images, masks = zip(*sorted_zipped_lists)\n\n        image = torch.cat(images, 0).unsqueeze(0)\n        mask = torch.cat(masks, 0).unsqueeze(0)\n        outputs = {\n            'image': image,\n            'mask': mask,\n            'view_idxs': view_idxs\n        }\n        return outputs\n\n\nIMAGE_PROCESSORS = {\n    \"v2\": ImageProcessorV2,\n    'mv_v2': MVImageProcessorV2,\n}\n\nDEFAULT_IMAGEPROCESSOR = 'v2'\n"
  },
  {
    "path": "hy3dgen/shapegen/schedulers.py",
    "content": "# Copyright 2024 Stability AI, Katherine Crowson and The HuggingFace Team. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport math\nfrom dataclasses import dataclass\nfrom typing import List, Optional, Tuple, Union\n\nimport numpy as np\nimport torch\nfrom diffusers.configuration_utils import ConfigMixin, register_to_config\nfrom diffusers.schedulers.scheduling_utils import SchedulerMixin\nfrom diffusers.utils import BaseOutput, logging\n\nlogger = logging.get_logger(__name__)  # pylint: disable=invalid-name\n\n\n@dataclass\nclass FlowMatchEulerDiscreteSchedulerOutput(BaseOutput):\n    \"\"\"\n    Output class for the scheduler's `step` function output.\n\n    Args:\n        prev_sample (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)` for images):\n            Computed sample `(x_{t-1})` of previous timestep. `prev_sample` should be used as next model input in the\n            denoising loop.\n    \"\"\"\n\n    prev_sample: torch.FloatTensor\n\n\nclass FlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):\n    \"\"\"\n    NOTE: this is very similar to diffusers.FlowMatchEulerDiscreteScheduler. Except our timesteps are reversed\n\n    Euler scheduler.\n\n    This model inherits from [`SchedulerMixin`] and [`ConfigMixin`]. Check the superclass documentation for the generic\n    methods the library implements for all schedulers such as loading and saving.\n\n    Args:\n        num_train_timesteps (`int`, defaults to 1000):\n            The number of diffusion steps to train the model.\n        timestep_spacing (`str`, defaults to `\"linspace\"`):\n            The way the timesteps should be scaled. Refer to Table 2 of the [Common Diffusion Noise Schedules and\n            Sample Steps are Flawed](https://huggingface.co/papers/2305.08891) for more information.\n        shift (`float`, defaults to 1.0):\n            The shift value for the timestep schedule.\n    \"\"\"\n\n    _compatibles = []\n    order = 1\n\n    @register_to_config\n    def __init__(\n        self,\n        num_train_timesteps: int = 1000,\n        shift: float = 1.0,\n        use_dynamic_shifting=False,\n    ):\n        timesteps = np.linspace(1, num_train_timesteps, num_train_timesteps, dtype=np.float32).copy()\n        timesteps = torch.from_numpy(timesteps).to(dtype=torch.float32)\n\n        sigmas = timesteps / num_train_timesteps\n        if not use_dynamic_shifting:\n            # when use_dynamic_shifting is True, we apply the timestep shifting on the fly based on the image resolution\n            sigmas = shift * sigmas / (1 + (shift - 1) * sigmas)\n\n        self.timesteps = sigmas * num_train_timesteps\n\n        self._step_index = None\n        self._begin_index = None\n\n        self.sigmas = sigmas.to(\"cpu\")  # to avoid too much CPU/GPU communication\n        self.sigma_min = self.sigmas[-1].item()\n        self.sigma_max = self.sigmas[0].item()\n\n    @property\n    def step_index(self):\n        \"\"\"\n        The index counter for current timestep. It will increase 1 after each scheduler step.\n        \"\"\"\n        return self._step_index\n\n    @property\n    def begin_index(self):\n        \"\"\"\n        The index for the first timestep. It should be set from pipeline with `set_begin_index` method.\n        \"\"\"\n        return self._begin_index\n\n    # Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index\n    def set_begin_index(self, begin_index: int = 0):\n        \"\"\"\n        Sets the begin index for the scheduler. This function should be run from pipeline before the inference.\n\n        Args:\n            begin_index (`int`):\n                The begin index for the scheduler.\n        \"\"\"\n        self._begin_index = begin_index\n\n    def scale_noise(\n        self,\n        sample: torch.FloatTensor,\n        timestep: Union[float, torch.FloatTensor],\n        noise: Optional[torch.FloatTensor] = None,\n    ) -> torch.FloatTensor:\n        \"\"\"\n        Forward process in flow-matching\n\n        Args:\n            sample (`torch.FloatTensor`):\n                The input sample.\n            timestep (`int`, *optional*):\n                The current timestep in the diffusion chain.\n\n        Returns:\n            `torch.FloatTensor`:\n                A scaled input sample.\n        \"\"\"\n        # Make sure sigmas and timesteps have the same device and dtype as original_samples\n        sigmas = self.sigmas.to(device=sample.device, dtype=sample.dtype)\n\n        if sample.device.type == \"mps\" and torch.is_floating_point(timestep):\n            # mps does not support float64\n            schedule_timesteps = self.timesteps.to(sample.device, dtype=torch.float32)\n            timestep = timestep.to(sample.device, dtype=torch.float32)\n        else:\n            schedule_timesteps = self.timesteps.to(sample.device)\n            timestep = timestep.to(sample.device)\n\n        # self.begin_index is None when scheduler is used for training, or pipeline does not implement set_begin_index\n        if self.begin_index is None:\n            step_indices = [self.index_for_timestep(t, schedule_timesteps) for t in timestep]\n        elif self.step_index is not None:\n            # add_noise is called after first denoising step (for inpainting)\n            step_indices = [self.step_index] * timestep.shape[0]\n        else:\n            # add noise is called before first denoising step to create initial latent(img2img)\n            step_indices = [self.begin_index] * timestep.shape[0]\n\n        sigma = sigmas[step_indices].flatten()\n        while len(sigma.shape) < len(sample.shape):\n            sigma = sigma.unsqueeze(-1)\n\n        sample = sigma * noise + (1.0 - sigma) * sample\n\n        return sample\n\n    def _sigma_to_t(self, sigma):\n        return sigma * self.config.num_train_timesteps\n\n    def time_shift(self, mu: float, sigma: float, t: torch.Tensor):\n        return math.exp(mu) / (math.exp(mu) + (1 / t - 1) ** sigma)\n\n    def set_timesteps(\n        self,\n        num_inference_steps: int = None,\n        device: Union[str, torch.device] = None,\n        sigmas: Optional[List[float]] = None,\n        mu: Optional[float] = None,\n    ):\n        \"\"\"\n        Sets the discrete timesteps used for the diffusion chain (to be run before inference).\n\n        Args:\n            num_inference_steps (`int`):\n                The number of diffusion steps used when generating samples with a pre-trained model.\n            device (`str` or `torch.device`, *optional*):\n                The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.\n        \"\"\"\n\n        if self.config.use_dynamic_shifting and mu is None:\n            raise ValueError(\" you have a pass a value for `mu` when `use_dynamic_shifting` is set to be `True`\")\n\n        if sigmas is None:\n            self.num_inference_steps = num_inference_steps\n            timesteps = np.linspace(\n                self._sigma_to_t(self.sigma_max), self._sigma_to_t(self.sigma_min), num_inference_steps\n            )\n\n            sigmas = timesteps / self.config.num_train_timesteps\n\n        if self.config.use_dynamic_shifting:\n            sigmas = self.time_shift(mu, 1.0, sigmas)\n        else:\n            sigmas = self.config.shift * sigmas / (1 + (self.config.shift - 1) * sigmas)\n\n        sigmas = torch.from_numpy(sigmas).to(dtype=torch.float32, device=device)\n        timesteps = sigmas * self.config.num_train_timesteps\n\n        self.timesteps = timesteps.to(device=device)\n        self.sigmas = torch.cat([sigmas, torch.ones(1, device=sigmas.device)])\n\n        self._step_index = None\n        self._begin_index = None\n\n    def index_for_timestep(self, timestep, schedule_timesteps=None):\n        if schedule_timesteps is None:\n            schedule_timesteps = self.timesteps\n\n        indices = (schedule_timesteps == timestep).nonzero()\n\n        # The sigma index that is taken for the **very** first `step`\n        # is always the second index (or the last index if there is only 1)\n        # This way we can ensure we don't accidentally skip a sigma in\n        # case we start in the middle of the denoising schedule (e.g. for image-to-image)\n        pos = 1 if len(indices) > 1 else 0\n\n        return indices[pos].item()\n\n    def _init_step_index(self, timestep):\n        if self.begin_index is None:\n            if isinstance(timestep, torch.Tensor):\n                timestep = timestep.to(self.timesteps.device)\n            self._step_index = self.index_for_timestep(timestep)\n        else:\n            self._step_index = self._begin_index\n\n    def step(\n        self,\n        model_output: torch.FloatTensor,\n        timestep: Union[float, torch.FloatTensor],\n        sample: torch.FloatTensor,\n        s_churn: float = 0.0,\n        s_tmin: float = 0.0,\n        s_tmax: float = float(\"inf\"),\n        s_noise: float = 1.0,\n        generator: Optional[torch.Generator] = None,\n        return_dict: bool = True,\n    ) -> Union[FlowMatchEulerDiscreteSchedulerOutput, Tuple]:\n        \"\"\"\n        Predict the sample from the previous timestep by reversing the SDE. This function propagates the diffusion\n        process from the learned model outputs (most often the predicted noise).\n\n        Args:\n            model_output (`torch.FloatTensor`):\n                The direct output from learned diffusion model.\n            timestep (`float`):\n                The current discrete timestep in the diffusion chain.\n            sample (`torch.FloatTensor`):\n                A current instance of a sample created by the diffusion process.\n            s_churn (`float`):\n            s_tmin  (`float`):\n            s_tmax  (`float`):\n            s_noise (`float`, defaults to 1.0):\n                Scaling factor for noise added to the sample.\n            generator (`torch.Generator`, *optional*):\n                A random number generator.\n            return_dict (`bool`):\n                Whether or not to return a [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or\n                tuple.\n\n        Returns:\n            [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] or `tuple`:\n                If return_dict is `True`, [`~schedulers.scheduling_euler_discrete.EulerDiscreteSchedulerOutput`] is\n                returned, otherwise a tuple is returned where the first element is the sample tensor.\n        \"\"\"\n\n        if (\n            isinstance(timestep, int)\n            or isinstance(timestep, torch.IntTensor)\n            or isinstance(timestep, torch.LongTensor)\n        ):\n            raise ValueError(\n                (\n                    \"Passing integer indices (e.g. from `enumerate(timesteps)`) as timesteps to\"\n                    \" `EulerDiscreteScheduler.step()` is not supported. Make sure to pass\"\n                    \" one of the `scheduler.timesteps` as a timestep.\"\n                ),\n            )\n\n        if self.step_index is None:\n            self._init_step_index(timestep)\n\n        # Upcast to avoid precision issues when computing prev_sample\n        sample = sample.to(torch.float32)\n\n        sigma = self.sigmas[self.step_index]\n        sigma_next = self.sigmas[self.step_index + 1]\n\n        prev_sample = sample + (sigma_next - sigma) * model_output\n\n        # Cast sample back to model compatible dtype\n        prev_sample = prev_sample.to(model_output.dtype)\n\n        # upon completion increase step index by one\n        self._step_index += 1\n\n        if not return_dict:\n            return (prev_sample,)\n\n        return FlowMatchEulerDiscreteSchedulerOutput(prev_sample=prev_sample)\n\n    def __len__(self):\n        return self.config.num_train_timesteps\n\n\n@dataclass\nclass ConsistencyFlowMatchEulerDiscreteSchedulerOutput(BaseOutput):\n    prev_sample: torch.FloatTensor\n    pred_original_sample: torch.FloatTensor\n\n\nclass ConsistencyFlowMatchEulerDiscreteScheduler(SchedulerMixin, ConfigMixin):\n    _compatibles = []\n    order = 1\n\n    @register_to_config\n    def __init__(\n        self,\n        num_train_timesteps: int = 1000,\n        pcm_timesteps: int = 50,\n    ):\n        sigmas = np.linspace(0, 1, num_train_timesteps)\n        step_ratio = num_train_timesteps // pcm_timesteps\n\n        euler_timesteps = (np.arange(1, pcm_timesteps) * step_ratio).round().astype(np.int64) - 1\n        euler_timesteps = np.asarray([0] + euler_timesteps.tolist())\n\n        self.euler_timesteps = euler_timesteps\n        self.sigmas = sigmas[self.euler_timesteps]\n        self.sigmas = torch.from_numpy((self.sigmas.copy())).to(dtype=torch.float32)\n        self.timesteps = self.sigmas * num_train_timesteps\n        self._step_index = None\n        self._begin_index = None\n        self.sigmas = self.sigmas.to(\"cpu\")  # to avoid too much CPU/GPU communication\n\n    @property\n    def step_index(self):\n        \"\"\"\n        The index counter for current timestep. It will increase 1 after each scheduler step.\n        \"\"\"\n        return self._step_index\n\n    @property\n    def begin_index(self):\n        \"\"\"\n        The index for the first timestep. It should be set from pipeline with `set_begin_index` method.\n        \"\"\"\n        return self._begin_index\n\n    # Copied from diffusers.schedulers.scheduling_dpmsolver_multistep.DPMSolverMultistepScheduler.set_begin_index\n    def set_begin_index(self, begin_index: int = 0):\n        \"\"\"\n        Sets the begin index for the scheduler. This function should be run from pipeline before the inference.\n\n        Args:\n            begin_index (`int`):\n                The begin index for the scheduler.\n        \"\"\"\n        self._begin_index = begin_index\n\n    def _sigma_to_t(self, sigma):\n        return sigma * self.config.num_train_timesteps\n\n    def set_timesteps(\n        self,\n        num_inference_steps: int = None,\n        device: Union[str, torch.device] = None,\n        sigmas: Optional[List[float]] = None,\n    ):\n        \"\"\"\n        Sets the discrete timesteps used for the diffusion chain (to be run before inference).\n\n        Args:\n            num_inference_steps (`int`):\n                The number of diffusion steps used when generating samples with a pre-trained model.\n            device (`str` or `torch.device`, *optional*):\n                The device to which the timesteps should be moved to. If `None`, the timesteps are not moved.\n        \"\"\"\n        self.num_inference_steps = num_inference_steps if num_inference_steps is not None else len(sigmas)\n        inference_indices = np.linspace(\n            0, self.config.pcm_timesteps, num=self.num_inference_steps, endpoint=False\n        )\n        inference_indices = np.floor(inference_indices).astype(np.int64)\n        inference_indices = torch.from_numpy(inference_indices).long()\n\n        self.sigmas_ = self.sigmas[inference_indices]\n        timesteps = self.sigmas_ * self.config.num_train_timesteps\n        self.timesteps = timesteps.to(device=device)\n        self.sigmas_ = torch.cat(\n            [self.sigmas_, torch.ones(1, device=self.sigmas_.device)]\n        )\n\n        self._step_index = None\n        self._begin_index = None\n\n    def index_for_timestep(self, timestep, schedule_timesteps=None):\n        if schedule_timesteps is None:\n            schedule_timesteps = self.timesteps\n\n        indices = (schedule_timesteps == timestep).nonzero()\n\n        # The sigma index that is taken for the **very** first `step`\n        # is always the second index (or the last index if there is only 1)\n        # This way we can ensure we don't accidentally skip a sigma in\n        # case we start in the middle of the denoising schedule (e.g. for image-to-image)\n        pos = 1 if len(indices) > 1 else 0\n\n        return indices[pos].item()\n\n    def _init_step_index(self, timestep):\n        if self.begin_index is None:\n            if isinstance(timestep, torch.Tensor):\n                timestep = timestep.to(self.timesteps.device)\n            self._step_index = self.index_for_timestep(timestep)\n        else:\n            self._step_index = self._begin_index\n\n    def step(\n        self,\n        model_output: torch.FloatTensor,\n        timestep: Union[float, torch.FloatTensor],\n        sample: torch.FloatTensor,\n        generator: Optional[torch.Generator] = None,\n        return_dict: bool = True,\n    ) -> Union[ConsistencyFlowMatchEulerDiscreteSchedulerOutput, Tuple]:\n        if (\n            isinstance(timestep, int)\n            or isinstance(timestep, torch.IntTensor)\n            or isinstance(timestep, torch.LongTensor)\n        ):\n            raise ValueError(\n                (\n                    \"Passing integer indices (e.g. from `enumerate(timesteps)`) as timesteps to\"\n                    \" `EulerDiscreteScheduler.step()` is not supported. Make sure to pass\"\n                    \" one of the `scheduler.timesteps` as a timestep.\"\n                ),\n            )\n\n        if self.step_index is None:\n            self._init_step_index(timestep)\n\n        sample = sample.to(torch.float32)\n\n        sigma = self.sigmas_[self.step_index]\n        sigma_next = self.sigmas_[self.step_index + 1]\n\n        prev_sample = sample + (sigma_next - sigma) * model_output\n        prev_sample = prev_sample.to(model_output.dtype)\n\n        pred_original_sample = sample + (1.0 - sigma) * model_output\n        pred_original_sample = pred_original_sample.to(model_output.dtype)\n\n        self._step_index += 1\n\n        if not return_dict:\n            return (prev_sample,)\n\n        return ConsistencyFlowMatchEulerDiscreteSchedulerOutput(prev_sample=prev_sample,\n                                                                pred_original_sample=pred_original_sample)\n\n    def __len__(self):\n        return self.config.num_train_timesteps\n"
  },
  {
    "path": "hy3dgen/shapegen/surface_loaders.py",
    "content": "import numpy as np\n\nimport torch\nimport trimesh\n\n\ndef normalize_mesh(mesh, scale=0.9999):\n    bbox = mesh.bounds\n    center = (bbox[1] + bbox[0]) / 2\n    scale_ = (bbox[1] - bbox[0]).max()\n\n    mesh.apply_translation(-center)\n    mesh.apply_scale(1 / scale_ * 2 * scale)\n\n    return mesh\n\n\ndef sample_pointcloud(mesh, num=200000):\n    points, face_idx = mesh.sample(num, return_index=True)\n    normals = mesh.face_normals[face_idx]\n    points = torch.from_numpy(points.astype(np.float32))\n    normals = torch.from_numpy(normals.astype(np.float32))\n    return points, normals\n\n\ndef load_surface(mesh, num_points=8192):\n    mesh = normalize_mesh(mesh, scale=0.98)\n    surface, normal = sample_pointcloud(mesh)\n\n    rng = np.random.default_rng(seed=0)\n    ind = rng.choice(surface.shape[0], num_points, replace=False)\n    surface = torch.FloatTensor(surface[ind])\n    normal = torch.FloatTensor(normal[ind])\n\n    surface = torch.cat([surface, normal], dim=-1).unsqueeze(0)\n\n    return surface, mesh\n\n\ndef sharp_sample_pointcloud(mesh, num=16384):\n    V = mesh.vertices\n    N = mesh.face_normals\n    VN = mesh.vertex_normals\n    F = mesh.faces\n    VN2 = np.ones(V.shape[0])\n    for i in range(3):\n        dot = np.stack((VN2[F[:, i]], np.sum(VN[F[:, i]] * N, axis=-1)), axis=-1)\n        VN2[F[:, i]] = np.min(dot, axis=-1)\n\n    sharp_mask = VN2 < 0.985\n    # collect edge\n    edge_a = np.concatenate((F[:, 0], F[:, 1], F[:, 2]))\n    edge_b = np.concatenate((F[:, 1], F[:, 2], F[:, 0]))\n    sharp_edge = ((sharp_mask[edge_a] * sharp_mask[edge_b]))\n    edge_a = edge_a[sharp_edge > 0]\n    edge_b = edge_b[sharp_edge > 0]\n\n    sharp_verts_a = V[edge_a]\n    sharp_verts_b = V[edge_b]\n    sharp_verts_an = VN[edge_a]\n    sharp_verts_bn = VN[edge_b]\n\n    weights = np.linalg.norm(sharp_verts_b - sharp_verts_a, axis=-1)\n    weights /= np.sum(weights)\n\n    random_number = np.random.rand(num)\n    w = np.random.rand(num, 1)\n    index = np.searchsorted(weights.cumsum(), random_number)\n    samples = w * sharp_verts_a[index] + (1 - w) * sharp_verts_b[index]\n    normals = w * sharp_verts_an[index] + (1 - w) * sharp_verts_bn[index]\n    return samples, normals\n\n\ndef load_surface_sharpegde(mesh, num_points=4096, num_sharp_points=4096, sharpedge_flag=True):\n    try:\n        mesh_full = trimesh.util.concatenate(mesh.dump())\n    except Exception as err:\n        mesh_full = trimesh.util.concatenate(mesh)\n    mesh_full = normalize_mesh(mesh_full)\n\n    origin_num = mesh_full.faces.shape[0]\n    original_vertices = mesh_full.vertices\n    original_faces = mesh_full.faces\n\n    mesh = trimesh.Trimesh(vertices=original_vertices, faces=original_faces[:origin_num])\n    mesh_fill = trimesh.Trimesh(vertices=original_vertices, faces=original_faces[origin_num:])\n    area = mesh.area\n    area_fill = mesh_fill.area\n    sample_num = 499712 // 2\n    num_fill = int(sample_num * (area_fill / (area + area_fill)))\n    num = sample_num - num_fill\n\n    random_surface, random_normal = sample_pointcloud(mesh, num=num)\n    if num_fill == 0:\n        random_surface_fill, random_normal_fill = np.zeros((0, 3)), np.zeros((0, 3))\n    else:\n        random_surface_fill, random_normal_fill = sample_pointcloud(mesh_fill, num=num_fill)\n    random_sharp_surface, sharp_normal = sharp_sample_pointcloud(mesh, num=sample_num)\n\n    # save_surface\n    surface = np.concatenate((random_surface, random_normal), axis=1).astype(np.float16)\n    surface_fill = np.concatenate((random_surface_fill, random_normal_fill), axis=1).astype(np.float16)\n    sharp_surface = np.concatenate((random_sharp_surface, sharp_normal), axis=1).astype(np.float16)\n    surface = np.concatenate((surface, surface_fill), axis=0)\n    if sharpedge_flag:\n        sharpedge_label = np.zeros((surface.shape[0], 1))\n        surface = np.concatenate((surface, sharpedge_label), axis=1)\n        sharpedge_label = np.ones((sharp_surface.shape[0], 1))\n        sharp_surface = np.concatenate((sharp_surface, sharpedge_label), axis=1)\n    rng = np.random.default_rng()\n    ind = rng.choice(surface.shape[0], num_points, replace=False)\n    surface = torch.FloatTensor(surface[ind])\n    ind = rng.choice(sharp_surface.shape[0], num_sharp_points, replace=False)\n    sharp_surface = torch.FloatTensor(sharp_surface[ind])\n\n    return torch.cat([surface, sharp_surface], dim=0).unsqueeze(0), mesh_full\n\n\nclass SurfaceLoader:\n    def __init__(self, num_points=8192):\n        self.num_points = num_points\n\n    def __call__(self, mesh_or_mesh_path, num_points=None):\n        if num_points is None:\n            num_points = self.num_points\n\n        mesh = mesh_or_mesh_path\n        if isinstance(mesh, str):\n            mesh = trimesh.load(mesh, force=\"mesh\", merge_primitives=True)\n        if isinstance(mesh, trimesh.scene.Scene):\n            for idx, obj in enumerate(mesh.geometry.values()):\n                if idx == 0:\n                    temp_mesh = obj\n                else:\n                    temp_mesh = temp_mesh + obj\n            mesh = temp_mesh\n        surface, mesh = load_surface(mesh, num_points=num_points)\n        return surface\n\n\nclass SharpEdgeSurfaceLoader:\n    def __init__(self, num_uniform_points=8192, num_sharp_points=8192, **kwargs):\n        self.num_uniform_points = num_uniform_points\n        self.num_sharp_points = num_sharp_points\n        self.num_points = num_uniform_points + num_sharp_points\n\n    def __call__(self, mesh_or_mesh_path, num_uniform_points=None, num_sharp_points=None):\n        if num_uniform_points is None:\n            num_uniform_points = self.num_uniform_points\n        if num_sharp_points is None:\n            num_sharp_points = self.num_sharp_points\n\n        mesh = mesh_or_mesh_path\n        if isinstance(mesh, str):\n            mesh = trimesh.load(mesh, force=\"mesh\", merge_primitives=True)\n        if isinstance(mesh, trimesh.scene.Scene):\n            for idx, obj in enumerate(mesh.geometry.values()):\n                if idx == 0:\n                    temp_mesh = obj\n                else:\n                    temp_mesh = temp_mesh + obj\n            mesh = temp_mesh\n        surface, mesh = load_surface_sharpegde(mesh, num_points=num_uniform_points, num_sharp_points=num_sharp_points)\n        return surface\n"
  },
  {
    "path": "hy3dgen/shapegen/utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport logging\nimport os\nfrom functools import wraps\n\nimport torch\n\n\ndef get_logger(name):\n    logger = logging.getLogger(name)\n    logger.setLevel(logging.INFO)\n\n    console_handler = logging.StreamHandler()\n    console_handler.setLevel(logging.INFO)\n\n    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n    console_handler.setFormatter(formatter)\n    logger.addHandler(console_handler)\n    return logger\n\n\nlogger = get_logger('hy3dgen.shapgen')\n\n\nclass synchronize_timer:\n    \"\"\" Synchronized timer to count the inference time of `nn.Module.forward`.\n\n        Supports both context manager and decorator usage.\n\n        Example as context manager:\n        ```python\n        with synchronize_timer('name') as t:\n            run()\n        ```\n\n        Example as decorator:\n        ```python\n        @synchronize_timer('Export to trimesh')\n        def export_to_trimesh(mesh_output):\n            pass\n        ```\n    \"\"\"\n\n    def __init__(self, name=None):\n        self.name = name\n\n    def __enter__(self):\n        \"\"\"Context manager entry: start timing.\"\"\"\n        if os.environ.get('HY3DGEN_DEBUG', '0') == '1':\n            self.start = torch.cuda.Event(enable_timing=True)\n            self.end = torch.cuda.Event(enable_timing=True)\n            self.start.record()\n            return lambda: self.time\n\n    def __exit__(self, exc_type, exc_value, exc_tb):\n        \"\"\"Context manager exit: stop timing and log results.\"\"\"\n        if os.environ.get('HY3DGEN_DEBUG', '0') == '1':\n            self.end.record()\n            torch.cuda.synchronize()\n            self.time = self.start.elapsed_time(self.end)\n            if self.name is not None:\n                logger.info(f'{self.name} takes {self.time} ms')\n\n    def __call__(self, func):\n        \"\"\"Decorator: wrap the function to time its execution.\"\"\"\n\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            with self:\n                result = func(*args, **kwargs)\n            return result\n\n        return wrapper\n\n\ndef smart_load_model(\n    model_path,\n    subfolder,\n    use_safetensors,\n    variant,\n):\n    original_model_path = model_path\n    # try local path\n    base_dir = os.environ.get('HY3DGEN_MODELS', '~/.cache/hy3dgen')\n    model_path = os.path.expanduser(os.path.join(base_dir, model_path, subfolder))\n    logger.info(f'Try to load model from local path: {model_path}')\n    if not os.path.exists(model_path):\n        logger.info('Model path not exists, try to download from huggingface')\n        try:\n            from huggingface_hub import snapshot_download\n            # 只下载指定子目录\n            path = snapshot_download(\n                repo_id=original_model_path,\n                allow_patterns=[f\"{subfolder}/*\"],  # 关键修改：模式匹配子文件夹\n            )\n            model_path = os.path.join(path, subfolder)  # 保持路径拼接逻辑不变\n        except ImportError:\n            logger.warning(\n                \"You need to install HuggingFace Hub to load models from the hub.\"\n            )\n            raise RuntimeError(f\"Model path {model_path} not found\")\n        except Exception as e:\n            raise e\n\n    if not os.path.exists(model_path):\n        raise FileNotFoundError(f\"Model path {original_model_path} not found\")\n\n    extension = 'ckpt' if not use_safetensors else 'safetensors'\n    variant = '' if variant is None else f'.{variant}'\n    ckpt_name = f'model{variant}.{extension}'\n    config_path = os.path.join(model_path, 'config.yaml')\n    ckpt_path = os.path.join(model_path, ckpt_name)\n    return config_path, ckpt_path\n"
  },
  {
    "path": "hy3dgen/texgen/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nfrom .pipelines import Hunyuan3DPaintPipeline, Hunyuan3DTexGenConfig\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/custom_rasterizer/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n'''\nfrom .hierarchy import BuildHierarchy, BuildHierarchyWithColor\nfrom .io_obj import LoadObj, LoadObjWithTexture\nfrom .render import rasterize, interpolate\n'''\nfrom .io_glb import *\nfrom .io_obj import *\nfrom .render import *\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/custom_rasterizer/io_glb.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport base64\nimport io\nimport os\n\nimport numpy as np\nfrom PIL import Image as PILImage\nfrom pygltflib import GLTF2\nfrom scipy.spatial.transform import Rotation as R\n\n\n# Function to extract buffer data\ndef get_buffer_data(gltf, buffer_view):\n    buffer = gltf.buffers[buffer_view.buffer]\n    buffer_data = gltf.get_data_from_buffer_uri(buffer.uri)\n    byte_offset = buffer_view.byteOffset if buffer_view.byteOffset else 0\n    byte_length = buffer_view.byteLength\n    return buffer_data[byte_offset:byte_offset + byte_length]\n\n\n# Function to extract attribute data\ndef get_attribute_data(gltf, accessor_index):\n    accessor = gltf.accessors[accessor_index]\n    buffer_view = gltf.bufferViews[accessor.bufferView]\n    buffer_data = get_buffer_data(gltf, buffer_view)\n\n    comptype = {5120: np.int8, 5121: np.uint8, 5122: np.int16, 5123: np.uint16, 5125: np.uint32, 5126: np.float32}\n    dtype = comptype[accessor.componentType]\n\n    t2n = {'SCALAR': 1, 'VEC2': 2, 'VEC3': 3, 'VEC4': 4, 'MAT2': 4, 'MAT3': 9, 'MAT4': 16}\n    num_components = t2n[accessor.type]\n\n    # Calculate the correct slice of data\n    byte_offset = accessor.byteOffset if accessor.byteOffset else 0\n    byte_stride = buffer_view.byteStride if buffer_view.byteStride else num_components * np.dtype(dtype).itemsize\n    count = accessor.count\n\n    # Extract the attribute data\n    attribute_data = np.zeros((count, num_components), dtype=dtype)\n    for i in range(count):\n        start = byte_offset + i * byte_stride\n        end = start + num_components * np.dtype(dtype).itemsize\n        attribute_data[i] = np.frombuffer(buffer_data[start:end], dtype=dtype)\n\n    return attribute_data\n\n\n# Function to extract image data\ndef get_image_data(gltf, image, folder):\n    if image.uri:\n        if image.uri.startswith('data:'):\n            # Data URI\n            header, encoded = image.uri.split(',', 1)\n            data = base64.b64decode(encoded)\n        else:\n            # External file\n            fn = image.uri\n            if not os.path.isabs(fn):\n                fn = folder + '/' + fn\n            with open(fn, 'rb') as f:\n                data = f.read()\n    else:\n        buffer_view = gltf.bufferViews[image.bufferView]\n        data = get_buffer_data(gltf, buffer_view)\n    return data\n\n\n# Function to convert triangle strip to triangles\ndef convert_triangle_strip_to_triangles(indices):\n    triangles = []\n    for i in range(len(indices) - 2):\n        if i % 2 == 0:\n            triangles.append([indices[i], indices[i + 1], indices[i + 2]])\n        else:\n            triangles.append([indices[i], indices[i + 2], indices[i + 1]])\n    return np.array(triangles).reshape(-1, 3)\n\n\n# Function to convert triangle fan to triangles\ndef convert_triangle_fan_to_triangles(indices):\n    triangles = []\n    for i in range(1, len(indices) - 1):\n        triangles.append([indices[0], indices[i], indices[i + 1]])\n    return np.array(triangles).reshape(-1, 3)\n\n\n# Function to get the transformation matrix from a node\ndef get_node_transform(node):\n    if node.matrix:\n        return np.array(node.matrix).reshape(4, 4).T\n    else:\n        T = np.eye(4)\n        if node.translation:\n            T[:3, 3] = node.translation\n        if node.rotation:\n            R_mat = R.from_quat(node.rotation).as_matrix()\n            T[:3, :3] = R_mat\n        if node.scale:\n            S = np.diag(node.scale + [1])\n            T = T @ S\n        return T\n\n\ndef get_world_transform(gltf, node_index, parents, world_transforms):\n    if parents[node_index] == -2:\n        return world_transforms[node_index]\n\n    node = gltf.nodes[node_index]\n    if parents[node_index] == -1:\n        world_transforms[node_index] = get_node_transform(node)\n        parents[node_index] = -2\n        return world_transforms[node_index]\n\n    parent_index = parents[node_index]\n    parent_transform = get_world_transform(gltf, parent_index, parents, world_transforms)\n    world_transforms[node_index] = parent_transform @ get_node_transform(node)\n    parents[node_index] = -2\n    return world_transforms[node_index]\n\n\ndef LoadGlb(path):\n    # Load the GLB file using pygltflib\n    gltf = GLTF2().load(path)\n\n    primitives = []\n    images = {}\n    # Iterate through the meshes in the GLB file\n\n    world_transforms = [np.identity(4) for i in range(len(gltf.nodes))]\n    parents = [-1 for i in range(len(gltf.nodes))]\n    for node_index, node in enumerate(gltf.nodes):\n        for idx in node.children:\n            parents[idx] = node_index\n    # for i in range(len(gltf.nodes)):\n    #    get_world_transform(gltf, i, parents, world_transform)\n\n    for node_index, node in enumerate(gltf.nodes):\n        if node.mesh is not None:\n            world_transform = get_world_transform(gltf, node_index, parents, world_transforms)\n            # Iterate through the primitives in the mesh\n            mesh = gltf.meshes[node.mesh]\n            for primitive in mesh.primitives:\n                # Access the attributes of the primitive\n                attributes = primitive.attributes.__dict__\n                mode = primitive.mode if primitive.mode is not None else 4  # Default to TRIANGLES\n                result = {}\n                if primitive.indices is not None:\n                    indices = get_attribute_data(gltf, primitive.indices)\n                    if mode == 4:  # TRIANGLES\n                        face_indices = indices.reshape(-1, 3)\n                    elif mode == 5:  # TRIANGLE_STRIP\n                        face_indices = convert_triangle_strip_to_triangles(indices)\n                    elif mode == 6:  # TRIANGLE_FAN\n                        face_indices = convert_triangle_fan_to_triangles(indices)\n                    else:\n                        continue\n                    result['F'] = face_indices\n\n                # Extract vertex positions\n                if 'POSITION' in attributes and attributes['POSITION'] is not None:\n                    positions = get_attribute_data(gltf, attributes['POSITION'])\n                    # Apply the world transformation to the positions\n                    positions_homogeneous = np.hstack([positions, np.ones((positions.shape[0], 1))])\n                    transformed_positions = (world_transform @ positions_homogeneous.T).T[:, :3]\n                    result['V'] = transformed_positions\n\n                # Extract vertex colors\n                if 'COLOR_0' in attributes and attributes['COLOR_0'] is not None:\n                    colors = get_attribute_data(gltf, attributes['COLOR_0'])\n                    if colors.shape[-1] > 3:\n                        colors = colors[..., :3]\n                    result['VC'] = colors\n\n                # Extract UVs\n                if 'TEXCOORD_0' in attributes and not attributes['TEXCOORD_0'] is None:\n                    uvs = get_attribute_data(gltf, attributes['TEXCOORD_0'])\n                    result['UV'] = uvs\n\n                if primitive.material is not None:\n                    material = gltf.materials[primitive.material]\n                    if (\n                        material.pbrMetallicRoughness is not None \n                        and material.pbrMetallicRoughness.baseColorTexture is not None\n                    ):\n                        texture_index = material.pbrMetallicRoughness.baseColorTexture.index\n                        texture = gltf.textures[texture_index]\n                        image_index = texture.source\n                        if not image_index in images:\n                            image = gltf.images[image_index]\n                            image_data = get_image_data(gltf, image, os.path.dirname(path))\n                            pil_image = PILImage.open(io.BytesIO(image_data))\n                            if pil_image.mode != 'RGB':\n                                pil_image = pil_image.convert('RGB')\n                            images[image_index] = pil_image\n                        result['TEX'] = image_index\n                    elif material.emissiveTexture is not None:\n                        texture_index = material.emissiveTexture.index\n                        texture = gltf.textures[texture_index]\n                        image_index = texture.source\n                        if not image_index in images:\n                            image = gltf.images[image_index]\n                            image_data = get_image_data(gltf, image, os.path.dirname(path))\n                            pil_image = PILImage.open(io.BytesIO(image_data))\n                            if pil_image.mode != 'RGB':\n                                pil_image = pil_image.convert('RGB')\n                            images[image_index] = pil_image\n                        result['TEX'] = image_index\n                    else:\n                        if material.pbrMetallicRoughness is not None:\n                            base_color = material.pbrMetallicRoughness.baseColorFactor\n                        else:\n                            base_color = np.array([0.8, 0.8, 0.8], dtype=np.float32)\n                        result['MC'] = base_color\n\n                primitives.append(result)\n\n    return primitives, images\n\n\ndef RotatePrimitives(primitives, transform):\n    for i in range(len(primitives)):\n        if 'V' in primitives[i]:\n            primitives[i]['V'] = primitives[i]['V'] @ transform.T\n\n\nif __name__ == '__main__':\n    path = 'data/test.glb'\n    LoadGlb(path)\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/custom_rasterizer/io_obj.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport cv2\nimport numpy as np\n\n\ndef LoadObj(fn):\n    lines = [l.strip() for l in open(fn)]\n    vertices = []\n    faces = []\n    for l in lines:\n        words = [w for w in l.split(' ') if w != '']\n        if len(words) == 0:\n            continue\n        if words[0] == 'v':\n            v = [float(words[i]) for i in range(1, 4)]\n            vertices.append(v)\n        elif words[0] == 'f':\n            f = [int(words[i]) - 1 for i in range(1, 4)]\n            faces.append(f)\n\n    return np.array(vertices).astype('float32'), np.array(faces).astype('int32')\n\n\ndef LoadObjWithTexture(fn, tex_fn):\n    lines = [l.strip() for l in open(fn)]\n    vertices = []\n    vertex_textures = []\n    faces = []\n    face_textures = []\n    for l in lines:\n        words = [w for w in l.split(' ') if w != '']\n        if len(words) == 0:\n            continue\n        if words[0] == 'v':\n            v = [float(words[i]) for i in range(1, len(words))]\n            vertices.append(v)\n        elif words[0] == 'vt':\n            v = [float(words[i]) for i in range(1, len(words))]\n            vertex_textures.append(v)\n        elif words[0] == 'f':\n            f = []\n            ft = []\n            for i in range(1, len(words)):\n                t = words[i].split('/')\n                f.append(int(t[0]) - 1)\n                ft.append(int(t[1]) - 1)\n            for i in range(2, len(f)):\n                faces.append([f[0], f[i - 1], f[i]])\n                face_textures.append([ft[0], ft[i - 1], ft[i]])\n\n    tex_image = cv2.cvtColor(cv2.imread(tex_fn), cv2.COLOR_BGR2RGB)\n    return np.array(vertices).astype('float32'), np.array(vertex_textures).astype('float32'), np.array(faces).astype(\n        'int32'), np.array(face_textures).astype('int32'), tex_image\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/custom_rasterizer/render.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport custom_rasterizer_kernel\nimport torch\n\n\ndef rasterize(pos, tri, resolution, clamp_depth=torch.zeros(0), use_depth_prior=0):\n    assert (pos.device == tri.device)\n    findices, barycentric = custom_rasterizer_kernel.rasterize_image(pos[0], tri, clamp_depth, resolution[1],\n                                                                     resolution[0], 1e-6, use_depth_prior)\n    return findices, barycentric\n\n\ndef interpolate(col, findices, barycentric, tri):\n    f = findices - 1 + (findices == 0)\n    vcol = col[0, tri.long()[f.long()]]\n    result = barycentric.view(*barycentric.shape, 1) * vcol\n    result = torch.sum(result, axis=-2)\n    return result.view(1, *result.shape)\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/lib/custom_rasterizer_kernel/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/lib/custom_rasterizer_kernel/grid_neighbor.cpp",
    "content": "#include \"rasterizer.h\"\n#include <fstream>\n\ninline int pos2key(float* p, int resolution) {\n    int x = (p[0] * 0.5 + 0.5) * resolution;\n    int y = (p[1] * 0.5 + 0.5) * resolution;\n    int z = (p[2] * 0.5 + 0.5) * resolution;\n    return (x * resolution + y) * resolution + z;\n}\n\ninline void key2pos(int key, int resolution, float* p) {\n    int x = key / resolution / resolution;\n    int y = key / resolution % resolution;\n    int z = key % resolution;\n    p[0] = ((x + 0.5) / resolution - 0.5) * 2;\n    p[1] = ((y + 0.5) / resolution - 0.5) * 2;\n    p[2] = ((z + 0.5) / resolution - 0.5) * 2;\n}\n\ninline void key2cornerpos(int key, int resolution, float* p) {\n    int x = key / resolution / resolution;\n    int y = key / resolution % resolution;\n    int z = key % resolution;\n    p[0] = ((x + 0.75) / resolution - 0.5) * 2;\n    p[1] = ((y + 0.25) / resolution - 0.5) * 2;\n    p[2] = ((z + 0.75) / resolution - 0.5) * 2;\n}\n\ninline float* pos_ptr(int l, int i, int j, torch::Tensor t) {\n    float* pdata = t.data_ptr<float>();\n    int height = t.size(1);\n    int width = t.size(2);\n    return &pdata[((l * height + i) * width + j) * 4];\n}\n\nstruct Grid\n{\n    std::vector<int> seq2oddcorner;\n    std::vector<int> seq2evencorner;\n    std::vector<int> seq2grid;\n    std::vector<int> seq2normal;\n    std::vector<int> seq2neighbor;\n    std::unordered_map<int, int> grid2seq;\n    std::vector<int> downsample_seq;\n    int num_origin_seq;\n    int resolution;\n    int stride;\n};\n\ninline void pos_from_seq(Grid& grid, int seq, float* p) {\n    auto k = grid.seq2grid[seq];\n    key2pos(k, grid.resolution, p);\n}\n\ninline int fetch_seq(Grid& grid, int l, int i, int j, torch::Tensor pdata) {\n    float* p = pos_ptr(l, i, j, pdata);\n    if (p[3] == 0)\n        return -1;\n    auto key = pos2key(p, grid.resolution);\n    int seq = grid.grid2seq[key];\n    return seq;\n}\n\ninline int fetch_last_seq(Grid& grid, int i, int j, torch::Tensor pdata) {\n    int num_layers = pdata.size(0);\n    int l = 0;\n    int idx = fetch_seq(grid, l, i, j, pdata);\n    while (l < num_layers - 1) {\n        l += 1;\n        int new_idx = fetch_seq(grid, l, i, j, pdata);\n        if (new_idx == -1)\n            break;\n        idx = new_idx;\n    }\n    return idx;\n}\n\ninline int fetch_nearest_seq(Grid& grid, int i, int j, int dim, float d, torch::Tensor pdata) {\n    float p[3];\n    float max_dist = 1e10;\n    int best_idx = -1;\n    int num_layers = pdata.size(0);\n    for (int l = 0; l < num_layers; ++l) {\n        int idx = fetch_seq(grid, l, i, j, pdata);\n        if (idx == -1)\n            break;\n        pos_from_seq(grid, idx, p);\n        float dist = std::abs(d - p[(dim + 2) % 3]);\n        if (dist < max_dist) {\n            max_dist = dist;\n            best_idx = idx;\n        }\n    }\n    return best_idx;\n}\n\ninline int fetch_nearest_seq_layer(Grid& grid, int i, int j, int dim, float d, torch::Tensor pdata) {\n    float p[3];\n    float max_dist = 1e10;\n    int best_layer = -1;\n    int num_layers = pdata.size(0);\n    for (int l = 0; l < num_layers; ++l) {\n        int idx = fetch_seq(grid, l, i, j, pdata);\n        if (idx == -1)\n            break;\n        pos_from_seq(grid, idx, p);\n        float dist = std::abs(d - p[(dim + 2) % 3]);\n        if (dist < max_dist) {\n            max_dist = dist;\n            best_layer = l;\n        }\n    }\n    return best_layer;\n}\n\nvoid FetchNeighbor(Grid& grid, int seq, float* pos, int dim, int boundary_info, std::vector<torch::Tensor>& view_layer_positions,\n    int* output_indices)\n{\n    auto t = view_layer_positions[dim];\n    int height = t.size(1);\n    int width = t.size(2);\n    int top = 0;\n    int ci = 0;\n    int cj = 0;\n    if (dim == 0) {\n        ci = (pos[1]/2+0.5)*height;\n        cj = (pos[0]/2+0.5)*width;\n    }\n    else if (dim == 1) {\n        ci = (pos[1]/2+0.5)*height;\n        cj = (pos[2]/2+0.5)*width;\n    }\n    else {\n        ci = (-pos[2]/2+0.5)*height;\n        cj = (pos[0]/2+0.5)*width;\n    }\n    int stride = grid.stride;\n    for (int ni = ci + stride; ni >= ci - stride; ni -= stride) {\n        for (int nj = cj - stride; nj <= cj + stride; nj += stride) {\n            int idx = -1;\n            if (ni == ci && nj == cj)\n                idx = seq;\n            else if (!(ni < 0 || ni >= height || nj < 0 || nj >= width)) {\n                if (boundary_info == -1)\n                    idx = fetch_seq(grid, 0, ni, nj, t);\n                else if (boundary_info == 1)\n                    idx = fetch_last_seq(grid, ni, nj, t);\n                else\n                    idx = fetch_nearest_seq(grid, ni, nj, dim, pos[(dim + 2) % 3], t);\n            }\n            output_indices[top] = idx;\n            top += 1;\n        }\n    }\n}\n\nvoid DownsampleGrid(Grid& src, Grid& tar)\n{\n    src.downsample_seq.resize(src.seq2grid.size(), -1);\n    tar.resolution = src.resolution / 2;\n    tar.stride = src.stride * 2;\n    float pos[3];\n    std::vector<int> seq2normal_count;\n    for (int i = 0; i < src.seq2grid.size(); ++i) {\n        key2pos(src.seq2grid[i], src.resolution, pos);\n        int k = pos2key(pos, tar.resolution);\n        int s = seq2normal_count.size();\n        if (!tar.grid2seq.count(k)) {\n            tar.grid2seq[k] = tar.seq2grid.size();\n            tar.seq2grid.emplace_back(k);\n            seq2normal_count.emplace_back(0);\n            seq2normal_count.emplace_back(0);\n            seq2normal_count.emplace_back(0);\n            //tar.seq2normal.emplace_back(src.seq2normal[i]);\n        } else {\n            s = tar.grid2seq[k] * 3;\n        }\n        seq2normal_count[s + src.seq2normal[i]] += 1;\n        src.downsample_seq[i] = tar.grid2seq[k];\n    }\n    tar.seq2normal.resize(seq2normal_count.size() / 3);\n    for (int i = 0; i < seq2normal_count.size(); i += 3) {\n        int t = 0;\n        for (int j = 1; j < 3; ++j) {\n            if (seq2normal_count[i + j] > seq2normal_count[i + t])\n                t = j;\n        }\n        tar.seq2normal[i / 3] = t;\n    }\n}\n\nvoid NeighborGrid(Grid& grid, std::vector<torch::Tensor> view_layer_positions, int v)\n{\n    grid.seq2evencorner.resize(grid.seq2grid.size(), 0);\n    grid.seq2oddcorner.resize(grid.seq2grid.size(), 0);\n    std::unordered_set<int> visited_seq;\n    for (int vd = 0; vd < 3; ++vd) {\n        auto t = view_layer_positions[vd];\n        auto t0 = view_layer_positions[v];\n        int height = t.size(1);\n        int width = t.size(2);\n        int num_layers = t.size(0);\n        int num_view_layers = t0.size(0);\n        for (int i = 0; i < height; ++i) {\n            for (int j = 0; j < width; ++j) {\n                for (int l = 0; l < num_layers; ++l) {\n                    int seq = fetch_seq(grid, l, i, j, t);\n                    if (seq == -1)\n                        break;\n                    int dim = grid.seq2normal[seq];\n                    if (dim != v)\n                        continue;\n\n                    float pos[3];\n                    pos_from_seq(grid, seq, pos);\n\n                    int ci = 0;\n                    int cj = 0;\n                    if (dim == 0) {\n                        ci = (pos[1]/2+0.5)*height;\n                        cj = (pos[0]/2+0.5)*width;\n                    }\n                    else if (dim == 1) {\n                        ci = (pos[1]/2+0.5)*height;\n                        cj = (pos[2]/2+0.5)*width;\n                    }\n                    else {\n                        ci = (-pos[2]/2+0.5)*height;\n                        cj = (pos[0]/2+0.5)*width;\n                    }\n\n                    if ((ci % (grid.stride * 2) < grid.stride) && (cj % (grid.stride * 2) >= grid.stride))\n                        grid.seq2evencorner[seq] = 1;\n\n                    if ((ci % (grid.stride * 2) >= grid.stride) && (cj % (grid.stride * 2) < grid.stride))\n                        grid.seq2oddcorner[seq] = 1;\n\n                    bool is_boundary = false;\n                    if (vd == v) {\n                        if (l == 0 || l == num_layers - 1)\n                            is_boundary = true;\n                        else {\n                            int seq_new = fetch_seq(grid, l + 1, i, j, t);\n                            if (seq_new == -1)\n                                is_boundary = true;\n                        }\n                    }\n                    int boundary_info = 0;\n                    if (is_boundary && (l == 0))\n                        boundary_info = -1;\n                    else if (is_boundary)\n                        boundary_info = 1;\n                    if (visited_seq.count(seq))\n                        continue;\n                    visited_seq.insert(seq);\n\n                    FetchNeighbor(grid, seq, pos, dim, boundary_info, view_layer_positions, &grid.seq2neighbor[seq * 9]);\n                }\n            }\n        }\n    }\n}\n\nvoid PadGrid(Grid& src, Grid& tar, std::vector<torch::Tensor>& view_layer_positions) {\n    auto& downsample_seq = src.downsample_seq;\n    auto& seq2evencorner = src.seq2evencorner;\n    auto& seq2oddcorner = src.seq2oddcorner;\n    int indices[9];\n    std::vector<int> mapped_even_corners(tar.seq2grid.size(), 0);\n    std::vector<int> mapped_odd_corners(tar.seq2grid.size(), 0);\n    for (int i = 0; i < downsample_seq.size(); ++i) {\n        if (seq2evencorner[i] > 0) {\n            mapped_even_corners[downsample_seq[i]] = 1;\n        }\n        if (seq2oddcorner[i] > 0) {\n            mapped_odd_corners[downsample_seq[i]] = 1;\n        }\n    }\n    auto& tar_seq2normal = tar.seq2normal;\n    auto& tar_seq2grid = tar.seq2grid;\n    for (int i = 0; i < tar_seq2grid.size(); ++i) {\n        if (mapped_even_corners[i] == 1 && mapped_odd_corners[i] == 1)\n            continue;\n        auto k = tar_seq2grid[i];\n        float p[3];\n        key2cornerpos(k, tar.resolution, p);\n\n        int src_key = pos2key(p, src.resolution);\n        if (!src.grid2seq.count(src_key)) {\n            int seq = src.seq2grid.size();\n            src.grid2seq[src_key] = seq;\n            src.seq2evencorner.emplace_back((mapped_even_corners[i] == 0));\n            src.seq2oddcorner.emplace_back((mapped_odd_corners[i] == 0));\n            src.seq2grid.emplace_back(src_key);\n            src.seq2normal.emplace_back(tar_seq2normal[i]);\n            FetchNeighbor(src, seq, p, tar_seq2normal[i], 0, view_layer_positions, indices);\n            for (int j = 0; j < 9; ++j) {\n                src.seq2neighbor.emplace_back(indices[j]);\n            }\n            src.downsample_seq.emplace_back(i);\n        } else {\n            int seq = src.grid2seq[src_key];\n            if (mapped_even_corners[i] == 0)\n                src.seq2evencorner[seq] = 1;\n            if (mapped_odd_corners[i] == 0)\n                src.seq2oddcorner[seq] = 1;\n        }\n    }\n}\n\nstd::vector<std::vector<torch::Tensor>> build_hierarchy(std::vector<torch::Tensor> view_layer_positions,\n    std::vector<torch::Tensor> view_layer_normals, int num_level, int resolution)\n{\n    if (view_layer_positions.size() != 3 || num_level < 1) {\n        printf(\"Alert! We require 3 layers and at least 1 level! (%d %d)\\n\", view_layer_positions.size(), num_level);\n        return {{},{},{},{}};\n    }\n\n    std::vector<Grid> grids;\n    grids.resize(num_level);\n\n    std::vector<float> seq2pos;\n    auto& seq2grid = grids[0].seq2grid;\n    auto& seq2normal = grids[0].seq2normal;\n    auto& grid2seq = grids[0].grid2seq;\n    grids[0].resolution = resolution;\n    grids[0].stride = 1;\n\n    auto int64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);\n    auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);\n\n    for (int v = 0; v < 3; ++v) {\n        int num_layers = view_layer_positions[v].size(0);\n        int height = view_layer_positions[v].size(1);\n        int width = view_layer_positions[v].size(2);\n        float* data = view_layer_positions[v].data_ptr<float>();\n        float* data_normal = view_layer_normals[v].data_ptr<float>();\n        for (int l = 0; l < num_layers; ++l) {\n            for (int i = 0; i < height; ++i) {\n                for (int j = 0; j < width; ++j) {\n                    float* p = &data[(i * width + j) * 4];\n                    float* n = &data_normal[(i * width + j) * 3];\n                    if (p[3] == 0)\n                        continue;\n                    auto k = pos2key(p, resolution);\n                    if (!grid2seq.count(k)) {\n                        int dim = 0;\n                        for (int d = 0; d < 3; ++d) {\n                            if (std::abs(n[d]) > std::abs(n[dim]))\n                                dim = d;\n                        }\n                        dim = (dim + 1) % 3;\n                        grid2seq[k] = seq2grid.size();\n                        seq2grid.emplace_back(k);\n                        seq2pos.push_back(p[0]);\n                        seq2pos.push_back(p[1]);\n                        seq2pos.push_back(p[2]);\n                        seq2normal.emplace_back(dim);\n                    }\n                }\n            }\n            data += (height * width * 4);\n            data_normal += (height * width * 3);\n        }\n    }\n\n    for (int i = 0; i < num_level - 1; ++i) {\n        DownsampleGrid(grids[i], grids[i + 1]);\n    }\n\n    for (int l = 0; l < num_level; ++l) {\n        grids[l].seq2neighbor.resize(grids[l].seq2grid.size() * 9, -1);\n        grids[l].num_origin_seq = grids[l].seq2grid.size();\n        for (int d = 0; d < 3; ++d) {\n            NeighborGrid(grids[l], view_layer_positions, d);\n        }\n    }\n\n    for (int i = num_level - 2; i >= 0; --i) {\n        PadGrid(grids[i], grids[i + 1], view_layer_positions);\n    }\n    for (int i = grids[0].num_origin_seq; i < grids[0].seq2grid.size(); ++i) {\n        int k = grids[0].seq2grid[i];\n        float p[3];\n        key2pos(k, grids[0].resolution, p);\n        seq2pos.push_back(p[0]);\n        seq2pos.push_back(p[1]);\n        seq2pos.push_back(p[2]);\n    }\n\n    std::vector<torch::Tensor> texture_positions(2);\n    std::vector<torch::Tensor> grid_neighbors(grids.size());\n    std::vector<torch::Tensor> grid_downsamples(grids.size() - 1);\n    std::vector<torch::Tensor> grid_evencorners(grids.size());\n    std::vector<torch::Tensor> grid_oddcorners(grids.size());\n\n    texture_positions[0] = torch::zeros({static_cast<int64_t>(seq2pos.size() / 3), static_cast<int64_t>(3)}, float_options);\n    texture_positions[1] = torch::zeros({static_cast<int64_t>(seq2pos.size() / 3)}, float_options);\n    float* positions_out_ptr = texture_positions[0].data_ptr<float>();\n    memcpy(positions_out_ptr, seq2pos.data(), sizeof(float) * seq2pos.size());\n    positions_out_ptr = texture_positions[1].data_ptr<float>();\n    for (int i = 0; i < grids[0].seq2grid.size(); ++i) {\n        positions_out_ptr[i] = (i < grids[0].num_origin_seq);\n    }\n\n    for (int i = 0; i < grids.size(); ++i) {\n        grid_neighbors[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2grid.size()), static_cast<int64_t>(9)}, int64_options);\n        int64_t* nptr = grid_neighbors[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2neighbor.size(); ++j) {\n            nptr[j] = grids[i].seq2neighbor[j];\n        }\n\n        grid_evencorners[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2evencorner.size())}, int64_options);\n        grid_oddcorners[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2oddcorner.size())}, int64_options);\n        int64_t* dptr = grid_evencorners[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2evencorner.size(); ++j) {\n            dptr[j] = grids[i].seq2evencorner[j];\n        }\n        dptr = grid_oddcorners[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2oddcorner.size(); ++j) {\n            dptr[j] = grids[i].seq2oddcorner[j];\n        }            \n        if (i + 1 < grids.size()) {\n            grid_downsamples[i] = torch::zeros({static_cast<int64_t>(grids[i].downsample_seq.size())}, int64_options);\n            int64_t* dptr = grid_downsamples[i].data_ptr<int64_t>();\n            for (int j = 0; j < grids[i].downsample_seq.size(); ++j) {\n                dptr[j] = grids[i].downsample_seq[j];\n            }\n        }\n\n    }\n    return {texture_positions, grid_neighbors, grid_downsamples, grid_evencorners, grid_oddcorners};\n}\n\nstd::vector<std::vector<torch::Tensor>> build_hierarchy_with_feat(\n    std::vector<torch::Tensor> view_layer_positions,\n    std::vector<torch::Tensor> view_layer_normals,\n    std::vector<torch::Tensor> view_layer_feats,\n    int num_level, int resolution)\n{\n    if (view_layer_positions.size() != 3 || num_level < 1) {\n        printf(\"Alert! We require 3 layers and at least 1 level! (%d %d)\\n\", view_layer_positions.size(), num_level);\n        return {{},{},{},{}};\n    }\n\n    std::vector<Grid> grids;\n    grids.resize(num_level);\n\n    std::vector<float> seq2pos;\n    std::vector<float> seq2feat;\n    auto& seq2grid = grids[0].seq2grid;\n    auto& seq2normal = grids[0].seq2normal;\n    auto& grid2seq = grids[0].grid2seq;\n    grids[0].resolution = resolution;\n    grids[0].stride = 1;\n\n    auto int64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);\n    auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);\n\n    int feat_channel = 3;\n    for (int v = 0; v < 3; ++v) {\n        int num_layers = view_layer_positions[v].size(0);\n        int height = view_layer_positions[v].size(1);\n        int width = view_layer_positions[v].size(2);\n        float* data = view_layer_positions[v].data_ptr<float>();\n        float* data_normal = view_layer_normals[v].data_ptr<float>();\n        float* data_feat = view_layer_feats[v].data_ptr<float>();\n        feat_channel = view_layer_feats[v].size(3);\n        for (int l = 0; l < num_layers; ++l) {\n            for (int i = 0; i < height; ++i) {\n                for (int j = 0; j < width; ++j) {\n                    float* p = &data[(i * width + j) * 4];\n                    float* n = &data_normal[(i * width + j) * 3];\n                    float* f = &data_feat[(i * width + j) * feat_channel];\n                    if (p[3] == 0)\n                        continue;\n                    auto k = pos2key(p, resolution);\n                    if (!grid2seq.count(k)) {\n                        int dim = 0;\n                        for (int d = 0; d < 3; ++d) {\n                            if (std::abs(n[d]) > std::abs(n[dim]))\n                                dim = d;\n                        }\n                        dim = (dim + 1) % 3;\n                        grid2seq[k] = seq2grid.size();\n                        seq2grid.emplace_back(k);\n                        seq2pos.push_back(p[0]);\n                        seq2pos.push_back(p[1]);\n                        seq2pos.push_back(p[2]);\n                        for (int c = 0; c < feat_channel; ++c) {\n                            seq2feat.emplace_back(f[c]);\n                        }\n                        seq2normal.emplace_back(dim);\n                    }\n                }\n            }\n            data += (height * width * 4);\n            data_normal += (height * width * 3);\n            data_feat += (height * width * feat_channel);\n        }\n    }\n\n    for (int i = 0; i < num_level - 1; ++i) {\n        DownsampleGrid(grids[i], grids[i + 1]);\n    }\n\n    for (int l = 0; l < num_level; ++l) {\n        grids[l].seq2neighbor.resize(grids[l].seq2grid.size() * 9, -1);\n        grids[l].num_origin_seq = grids[l].seq2grid.size();\n        for (int d = 0; d < 3; ++d) {\n            NeighborGrid(grids[l], view_layer_positions, d);\n        }\n    }\n\n    for (int i = num_level - 2; i >= 0; --i) {\n        PadGrid(grids[i], grids[i + 1], view_layer_positions);\n    }\n    for (int i = grids[0].num_origin_seq; i < grids[0].seq2grid.size(); ++i) {\n        int k = grids[0].seq2grid[i];\n        float p[3];\n        key2pos(k, grids[0].resolution, p);\n        seq2pos.push_back(p[0]);\n        seq2pos.push_back(p[1]);\n        seq2pos.push_back(p[2]);\n        for (int c = 0; c < feat_channel; ++c) {\n            seq2feat.emplace_back(0.5);\n        }\n    }\n\n    std::vector<torch::Tensor> texture_positions(2);\n    std::vector<torch::Tensor> texture_feats(1);\n    std::vector<torch::Tensor> grid_neighbors(grids.size());\n    std::vector<torch::Tensor> grid_downsamples(grids.size() - 1);\n    std::vector<torch::Tensor> grid_evencorners(grids.size());\n    std::vector<torch::Tensor> grid_oddcorners(grids.size());\n\n    texture_positions[0] = torch::zeros({static_cast<int64_t>(seq2pos.size() / 3), static_cast<int64_t>(3)}, float_options);\n    texture_positions[1] = torch::zeros({static_cast<int64_t>(seq2pos.size() / 3)}, float_options);\n    texture_feats[0] = torch::zeros({static_cast<int64_t>(seq2feat.size() / feat_channel), static_cast<int64_t>(feat_channel)}, float_options);\n    float* positions_out_ptr = texture_positions[0].data_ptr<float>();\n    memcpy(positions_out_ptr, seq2pos.data(), sizeof(float) * seq2pos.size());\n    positions_out_ptr = texture_positions[1].data_ptr<float>();\n    for (int i = 0; i < grids[0].seq2grid.size(); ++i) {\n        positions_out_ptr[i] = (i < grids[0].num_origin_seq);\n    }\n    float* feats_out_ptr = texture_feats[0].data_ptr<float>();\n    memcpy(feats_out_ptr, seq2feat.data(), sizeof(float) * seq2feat.size());\n\n    for (int i = 0; i < grids.size(); ++i) {\n        grid_neighbors[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2grid.size()), static_cast<int64_t>(9)}, int64_options);\n        int64_t* nptr = grid_neighbors[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2neighbor.size(); ++j) {\n            nptr[j] = grids[i].seq2neighbor[j];\n        }\n        grid_evencorners[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2evencorner.size())}, int64_options);\n        grid_oddcorners[i] = torch::zeros({static_cast<int64_t>(grids[i].seq2oddcorner.size())}, int64_options);\n        int64_t* dptr = grid_evencorners[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2evencorner.size(); ++j) {\n            dptr[j] = grids[i].seq2evencorner[j];\n        }\n        dptr = grid_oddcorners[i].data_ptr<int64_t>();\n        for (int j = 0; j < grids[i].seq2oddcorner.size(); ++j) {\n            dptr[j] = grids[i].seq2oddcorner[j];\n        }\n        if (i + 1 < grids.size()) {\n            grid_downsamples[i] = torch::zeros({static_cast<int64_t>(grids[i].downsample_seq.size())}, int64_options);\n            int64_t* dptr = grid_downsamples[i].data_ptr<int64_t>();\n            for (int j = 0; j < grids[i].downsample_seq.size(); ++j) {\n                dptr[j] = grids[i].downsample_seq[j];\n            }\n        }\n    }\n    return {texture_positions, texture_feats, grid_neighbors, grid_downsamples, grid_evencorners, grid_oddcorners};\n}\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.cpp",
    "content": "#include \"rasterizer.h\"\n\nvoid rasterizeTriangleCPU(int idx, float* vt0, float* vt1, float* vt2, int width, int height, INT64* zbuffer, float* d, float occlusion_truncation) {\n    float x_min = std::min(vt0[0], std::min(vt1[0],vt2[0]));\n    float x_max = std::max(vt0[0], std::max(vt1[0],vt2[0]));\n    float y_min = std::min(vt0[1], std::min(vt1[1],vt2[1]));\n    float y_max = std::max(vt0[1], std::max(vt1[1],vt2[1]));\n\n    for (int px = x_min; px < x_max + 1; ++px) {\n        if (px < 0 || px >= width)\n            continue;\n        for (int py = y_min; py < y_max + 1; ++py) {\n            if (py < 0 || py >= height)\n                continue;\n            float vt[2] = {px + 0.5, py + 0.5};\n            float baryCentricCoordinate[3];\n            calculateBarycentricCoordinate(vt0, vt1, vt2, vt, baryCentricCoordinate);\n            if (isBarycentricCoordInBounds(baryCentricCoordinate)) {\n                int pixel = py * width + px;\n                if (zbuffer == 0) {\n                    zbuffer[pixel] = (INT64)(idx + 1);\n                    continue;\n                }\n\n                float depth = baryCentricCoordinate[0] * vt0[2] + baryCentricCoordinate[1] * vt1[2] + baryCentricCoordinate[2] * vt2[2];\n                float depth_thres = 0;\n                if (d) {\n                    depth_thres = d[pixel] * 0.49999f + 0.5f + occlusion_truncation;\n                }\n                \n                int z_quantize = depth * (2<<17);\n                INT64 token = (INT64)z_quantize * MAXINT + (INT64)(idx + 1);\n                if (depth < depth_thres)\n                    continue;\n                zbuffer[pixel] = std::min(zbuffer[pixel], token);\n            }\n        }\n    }\n}\n\nvoid barycentricFromImgcoordCPU(float* V, int* F, int* findices, INT64* zbuffer, int width, int height, int num_vertices, int num_faces,\n    float* barycentric_map, int pix)\n{\n    INT64 f = zbuffer[pix] % MAXINT;\n    if (f == (MAXINT-1)) {\n        findices[pix] = 0;\n        barycentric_map[pix * 3] = 0;\n        barycentric_map[pix * 3 + 1] = 0;\n        barycentric_map[pix * 3 + 2] = 0;\n        return;\n    }\n    findices[pix] = f;\n    f -= 1;\n    float barycentric[3] = {0, 0, 0};\n    if (f >= 0) {\n        float vt[2] = {float(pix % width) + 0.5f, float(pix / width) + 0.5f};\n        float* vt0_ptr = V + (F[f * 3] * 4);\n        float* vt1_ptr = V + (F[f * 3 + 1] * 4);\n        float* vt2_ptr = V + (F[f * 3 + 2] * 4);\n\n        float vt0[2] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f};\n        float vt1[2] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f};\n        float vt2[2] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f};\n\n        calculateBarycentricCoordinate(vt0, vt1, vt2, vt, barycentric);\n\n        barycentric[0] = barycentric[0] / vt0_ptr[3];\n        barycentric[1] = barycentric[1] / vt1_ptr[3];\n        barycentric[2] = barycentric[2] / vt2_ptr[3];\n        float w = 1.0f / (barycentric[0] + barycentric[1] + barycentric[2]);\n        barycentric[0] *= w;\n        barycentric[1] *= w;\n        barycentric[2] *= w;\n\n    }\n    barycentric_map[pix * 3] = barycentric[0];\n    barycentric_map[pix * 3 + 1] = barycentric[1];\n    barycentric_map[pix * 3 + 2] = barycentric[2];\n}\n\nvoid rasterizeImagecoordsKernelCPU(float* V, int* F, float* d, INT64* zbuffer, float occlusion_trunc, int width, int height, int num_vertices, int num_faces, int f)\n{\n    float* vt0_ptr = V + (F[f * 3] * 4);\n    float* vt1_ptr = V + (F[f * 3 + 1] * 4);\n    float* vt2_ptr = V + (F[f * 3 + 2] * 4);\n\n    float vt0[3] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f, vt0_ptr[2] / vt0_ptr[3] * 0.49999f + 0.5f};\n    float vt1[3] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f, vt1_ptr[2] / vt1_ptr[3] * 0.49999f + 0.5f};\n    float vt2[3] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f, vt2_ptr[2] / vt2_ptr[3] * 0.49999f + 0.5f};\n\n    rasterizeTriangleCPU(f, vt0, vt1, vt2, width, height, zbuffer, d, occlusion_trunc);\n}\n\nstd::vector<torch::Tensor> rasterize_image_cpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,\n    int width, int height, float occlusion_truncation, int use_depth_prior)\n{\n    int num_faces = F.size(0);\n    int num_vertices = V.size(0);\n    auto options = torch::TensorOptions().dtype(torch::kInt32).requires_grad(false);\n    auto INT64_options = torch::TensorOptions().dtype(torch::kInt64).requires_grad(false);\n    auto findices = torch::zeros({height, width}, options);\n    INT64 maxint = (INT64)MAXINT * (INT64)MAXINT + (MAXINT - 1);\n    auto z_min = torch::ones({height, width}, INT64_options) * (int64_t)maxint;\n\n    if (!use_depth_prior) {\n        for (int i = 0; i < num_faces; ++i) {\n            rasterizeImagecoordsKernelCPU(V.data_ptr<float>(), F.data_ptr<int>(), 0,\n                (INT64*)z_min.data_ptr<int64_t>(), occlusion_truncation, width, height, num_vertices, num_faces, i); \n        }\n    } else {\n        for (int i = 0; i < num_faces; ++i)\n            rasterizeImagecoordsKernelCPU(V.data_ptr<float>(), F.data_ptr<int>(), D.data_ptr<float>(),\n                (INT64*)z_min.data_ptr<int64_t>(), occlusion_truncation, width, height, num_vertices, num_faces, i);\n    }\n\n    auto float_options = torch::TensorOptions().dtype(torch::kFloat32).requires_grad(false);\n    auto barycentric = torch::zeros({height, width, 3}, float_options);\n    for (int i = 0; i < width * height; ++i)\n        barycentricFromImgcoordCPU(V.data_ptr<float>(), F.data_ptr<int>(),\n            findices.data_ptr<int>(), (INT64*)z_min.data_ptr<int64_t>(), width, height, num_vertices, num_faces, barycentric.data_ptr<float>(), i);\n\n    return {findices, barycentric};\n}\n\nstd::vector<torch::Tensor> rasterize_image(torch::Tensor V, torch::Tensor F, torch::Tensor D,\n    int width, int height, float occlusion_truncation, int use_depth_prior)\n{\n    int device_id = V.get_device();\n    if (device_id == -1)\n        return rasterize_image_cpu(V, F, D, width, height, occlusion_truncation, use_depth_prior);\n    else\n        return rasterize_image_gpu(V, F, D, width, height, occlusion_truncation, use_depth_prior);\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n  m.def(\"rasterize_image\", &rasterize_image, \"Custom image rasterization\");\n  m.def(\"build_hierarchy\", &build_hierarchy, \"Custom image rasterization\");\n  m.def(\"build_hierarchy_with_feat\", &build_hierarchy_with_feat, \"Custom image rasterization\");\n}\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer.h",
    "content": "#ifndef RASTERIZER_H_\n#define RASTERIZER_H_\n\n#include <torch/extension.h>\n#include <vector>\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h> // For CUDA context\n\n#define INT64 unsigned long long\n#define MAXINT 2147483647\n\n__host__ __device__ inline float calculateSignedArea2(float* a, float* b, float* c) {\n    return ((c[0] - a[0]) * (b[1] - a[1]) - (b[0] - a[0]) * (c[1] - a[1]));\n}\n\n__host__ __device__  inline void calculateBarycentricCoordinate(float* a, float* b, float* c, float* p,\n    float* barycentric)\n{\n    float beta_tri = calculateSignedArea2(a, p, c);\n    float gamma_tri = calculateSignedArea2(a, b, p);\n    float area = calculateSignedArea2(a, b, c);\n    if (area == 0) {\n        barycentric[0] = -1.0;\n        barycentric[1] = -1.0;\n        barycentric[2] = -1.0;\n        return;\n    }\n    float tri_inv = 1.0 / area;\n    float beta = beta_tri * tri_inv;\n    float gamma = gamma_tri * tri_inv;\n    float alpha = 1.0 - beta - gamma;\n    barycentric[0] = alpha;\n    barycentric[1] = beta;\n    barycentric[2] = gamma;\n}\n\n__host__ __device__  inline bool isBarycentricCoordInBounds(float* barycentricCoord) {\n    return barycentricCoord[0] >= 0.0 && barycentricCoord[0] <= 1.0 &&\n           barycentricCoord[1] >= 0.0 && barycentricCoord[1] <= 1.0 &&\n           barycentricCoord[2] >= 0.0 && barycentricCoord[2] <= 1.0;\n}\n\nstd::vector<torch::Tensor> rasterize_image_gpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,\n    int width, int height, float occlusion_truncation, int use_depth_prior);\n\nstd::vector<std::vector<torch::Tensor>> build_hierarchy(std::vector<torch::Tensor> view_layer_positions, std::vector<torch::Tensor> view_layer_normals, int num_level, int resolution);\n\nstd::vector<std::vector<torch::Tensor>> build_hierarchy_with_feat(\n    std::vector<torch::Tensor> view_layer_positions,\n    std::vector<torch::Tensor> view_layer_normals,\n    std::vector<torch::Tensor> view_layer_feats,\n    int num_level, int resolution);\n\n#endif"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/lib/custom_rasterizer_kernel/rasterizer_gpu.cu",
    "content": "#include \"rasterizer.h\"\n\n__device__ void rasterizeTriangleGPU(int idx, float* vt0, float* vt1, float* vt2, int width, int height, INT64* zbuffer, float* d, float occlusion_truncation) {\n    float x_min = std::min(vt0[0], std::min(vt1[0],vt2[0]));\n    float x_max = std::max(vt0[0], std::max(vt1[0],vt2[0]));\n    float y_min = std::min(vt0[1], std::min(vt1[1],vt2[1]));\n    float y_max = std::max(vt0[1], std::max(vt1[1],vt2[1]));\n\n    for (int px = x_min; px < x_max + 1; ++px) {\n        if (px < 0 || px >= width)\n            continue;\n        for (int py = y_min; py < y_max + 1; ++py) {\n            if (py < 0 || py >= height)\n                continue;\n            float vt[2] = {px + 0.5f, py + 0.5f};\n            float baryCentricCoordinate[3];\n            calculateBarycentricCoordinate(vt0, vt1, vt2, vt, baryCentricCoordinate);\n            if (isBarycentricCoordInBounds(baryCentricCoordinate)) {\n                int pixel = py * width + px;\n                if (zbuffer == 0) {\n                    atomicExch(&zbuffer[pixel], (INT64)(idx + 1));\n                    continue;\n                }\n                float depth = baryCentricCoordinate[0] * vt0[2] + baryCentricCoordinate[1] * vt1[2] + baryCentricCoordinate[2] * vt2[2];\n                float depth_thres = 0;\n                if (d) {\n                    depth_thres = d[pixel] * 0.49999f + 0.5f + occlusion_truncation;\n                }\n                \n                int z_quantize = depth * (2<<17);\n                INT64 token = (INT64)z_quantize * MAXINT + (INT64)(idx + 1);\n                if (depth < depth_thres)\n                    continue;\n                atomicMin(&zbuffer[pixel], token);\n            }\n        }\n    }\n}\n\n__global__ void barycentricFromImgcoordGPU(float* V, int* F, int* findices, INT64* zbuffer, int width, int height, int num_vertices, int num_faces,\n    float* barycentric_map)\n{\n    int pix = blockIdx.x * blockDim.x + threadIdx.x;\n    if (pix >= width * height)\n        return;\n    INT64 f = zbuffer[pix] % MAXINT;\n    if (f == (MAXINT-1)) {\n        findices[pix] = 0;\n        barycentric_map[pix * 3] = 0;\n        barycentric_map[pix * 3 + 1] = 0;\n        barycentric_map[pix * 3 + 2] = 0;\n        return;\n    }\n    findices[pix] = f;\n    f -= 1;\n    float barycentric[3] = {0, 0, 0};\n    if (f >= 0) {\n        float vt[2] = {float(pix % width) + 0.5f, float(pix / width) + 0.5f};\n        float* vt0_ptr = V + (F[f * 3] * 4);\n        float* vt1_ptr = V + (F[f * 3 + 1] * 4);\n        float* vt2_ptr = V + (F[f * 3 + 2] * 4);\n\n        float vt0[2] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f};\n        float vt1[2] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f};\n        float vt2[2] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f};\n\n        calculateBarycentricCoordinate(vt0, vt1, vt2, vt, barycentric);\n\n        barycentric[0] = barycentric[0] / vt0_ptr[3];\n        barycentric[1] = barycentric[1] / vt1_ptr[3];\n        barycentric[2] = barycentric[2] / vt2_ptr[3];\n        float w = 1.0f / (barycentric[0] + barycentric[1] + barycentric[2]);\n        barycentric[0] *= w;\n        barycentric[1] *= w;\n        barycentric[2] *= w;\n\n    }\n    barycentric_map[pix * 3] = barycentric[0];\n    barycentric_map[pix * 3 + 1] = barycentric[1];\n    barycentric_map[pix * 3 + 2] = barycentric[2];\n}\n\n__global__ void rasterizeImagecoordsKernelGPU(float* V, int* F, float* d, INT64* zbuffer, float occlusion_trunc, int width, int height, int num_vertices, int num_faces)\n{\n    int f = blockIdx.x * blockDim.x + threadIdx.x;\n    if (f >= num_faces)\n        return; \n\n    float* vt0_ptr = V + (F[f * 3] * 4);\n    float* vt1_ptr = V + (F[f * 3 + 1] * 4);\n    float* vt2_ptr = V + (F[f * 3 + 2] * 4);\n\n    float vt0[3] = {(vt0_ptr[0] / vt0_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt0_ptr[1] / vt0_ptr[3]) * (height - 1) + 0.5f, vt0_ptr[2] / vt0_ptr[3] * 0.49999f + 0.5f};\n    float vt1[3] = {(vt1_ptr[0] / vt1_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt1_ptr[1] / vt1_ptr[3]) * (height - 1) + 0.5f, vt1_ptr[2] / vt1_ptr[3] * 0.49999f + 0.5f};\n    float vt2[3] = {(vt2_ptr[0] / vt2_ptr[3] * 0.5f + 0.5f) * (width - 1) + 0.5f, (0.5f + 0.5f * vt2_ptr[1] / vt2_ptr[3]) * (height - 1) + 0.5f, vt2_ptr[2] / vt2_ptr[3] * 0.49999f + 0.5f};\n\n    rasterizeTriangleGPU(f, vt0, vt1, vt2, width, height, zbuffer, d, occlusion_trunc);\n}\n\nstd::vector<torch::Tensor> rasterize_image_gpu(torch::Tensor V, torch::Tensor F, torch::Tensor D,\n    int width, int height, float occlusion_truncation, int use_depth_prior)\n{\n    int device_id = V.get_device();\n    cudaSetDevice(device_id);\n    int num_faces = F.size(0);\n    int num_vertices = V.size(0);\n    auto options = torch::TensorOptions().dtype(torch::kInt32).device(torch::kCUDA, device_id).requires_grad(false);\n    auto INT64_options = torch::TensorOptions().dtype(torch::kInt64).device(torch::kCUDA, device_id).requires_grad(false);\n    auto findices = torch::zeros({height, width}, options);\n    INT64 maxint = (INT64)MAXINT * (INT64)MAXINT + (MAXINT - 1);\n    auto z_min = torch::ones({height, width}, INT64_options) * (int64_t)maxint;\n\n    if (!use_depth_prior) {\n        rasterizeImagecoordsKernelGPU<<<(num_faces+255)/256,256,0,at::cuda::getCurrentCUDAStream()>>>(V.data_ptr<float>(), F.data_ptr<int>(), 0,\n            (INT64*)z_min.data_ptr<int64_t>(), occlusion_truncation, width, height, num_vertices, num_faces); \n    } else {\n        rasterizeImagecoordsKernelGPU<<<(num_faces+255)/256,256,0,at::cuda::getCurrentCUDAStream()>>>(V.data_ptr<float>(), F.data_ptr<int>(), D.data_ptr<float>(),\n            (INT64*)z_min.data_ptr<int64_t>(), occlusion_truncation, width, height, num_vertices, num_faces); \n    }\n\n    auto float_options = torch::TensorOptions().dtype(torch::kFloat32).device(torch::kCUDA, device_id).requires_grad(false);\n    auto barycentric = torch::zeros({height, width, 3}, float_options);\n    barycentricFromImgcoordGPU<<<(width * height + 255)/256, 256>>>(V.data_ptr<float>(), F.data_ptr<int>(),\n        findices.data_ptr<int>(), (INT64*)z_min.data_ptr<int64_t>(), width, height, num_vertices, num_faces, barycentric.data_ptr<float>());\n\n    return {findices, barycentric};\n}\n"
  },
  {
    "path": "hy3dgen/texgen/custom_rasterizer/setup.py",
    "content": "from setuptools import setup, find_packages\nfrom torch.utils.cpp_extension import BuildExtension, CUDAExtension\n\n# build custom rasterizer\n# build with `python setup.py install`\n# nvcc is needed\n\ncustom_rasterizer_module = CUDAExtension('custom_rasterizer_kernel', [\n    'lib/custom_rasterizer_kernel/rasterizer.cpp',\n    'lib/custom_rasterizer_kernel/grid_neighbor.cpp',\n    'lib/custom_rasterizer_kernel/rasterizer_gpu.cu',\n])\n\nsetup(\n    packages=find_packages(),\n    version='0.1',\n    name='custom_rasterizer',\n    include_package_data=True,\n    package_dir={'': '.'},\n    ext_modules=[\n        custom_rasterizer_module,\n    ],\n    cmdclass={\n        'build_ext': BuildExtension\n    }\n)\n"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT."
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/camera_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport math\n\nimport numpy as np\nimport torch\n\n\ndef transform_pos(mtx, pos, keepdim=False):\n    t_mtx = torch.from_numpy(mtx).to(\n        pos.device) if isinstance(\n        mtx, np.ndarray) else mtx\n    if pos.shape[-1] == 3:\n        posw = torch.cat(\n            [pos, torch.ones([pos.shape[0], 1]).to(pos.device)], axis=1)\n    else:\n        posw = pos\n\n    if keepdim:\n        return torch.matmul(posw, t_mtx.t())[...]\n    else:\n        return torch.matmul(posw, t_mtx.t())[None, ...]\n\n\ndef get_mv_matrix(elev, azim, camera_distance, center=None):\n    elev = -elev\n    azim += 90\n\n    elev_rad = math.radians(elev)\n    azim_rad = math.radians(azim)\n\n    camera_position = np.array([camera_distance * math.cos(elev_rad) * math.cos(azim_rad),\n                                camera_distance *\n                                math.cos(elev_rad) * math.sin(azim_rad),\n                                camera_distance * math.sin(elev_rad)])\n\n    if center is None:\n        center = np.array([0, 0, 0])\n    else:\n        center = np.array(center)\n\n    lookat = center - camera_position\n    lookat = lookat / np.linalg.norm(lookat)\n\n    up = np.array([0, 0, 1.0])\n    right = np.cross(lookat, up)\n    right = right / np.linalg.norm(right)\n    up = np.cross(right, lookat)\n    up = up / np.linalg.norm(up)\n\n    c2w = np.concatenate(\n        [np.stack([right, up, -lookat], axis=-1), camera_position[:, None]], axis=-1)\n\n    w2c = np.zeros((4, 4))\n    w2c[:3, :3] = np.transpose(c2w[:3, :3], (1, 0))\n    w2c[:3, 3:] = -np.matmul(np.transpose(c2w[:3, :3], (1, 0)), c2w[:3, 3:])\n    w2c[3, 3] = 1.0\n\n    return w2c.astype(np.float32)\n\n\ndef get_orthographic_projection_matrix(\n    left=-1, right=1, bottom=-1, top=1, near=0, far=2):\n    \"\"\"\n    计算正交投影矩阵。\n\n    参数:\n        left (float): 投影区域左侧边界。\n        right (float): 投影区域右侧边界。\n        bottom (float): 投影区域底部边界。\n        top (float): 投影区域顶部边界。\n        near (float): 投影区域近裁剪面距离。\n        far (float): 投影区域远裁剪面距离。\n\n    返回:\n        numpy.ndarray: 正交投影矩阵。\n    \"\"\"\n    ortho_matrix = np.eye(4, dtype=np.float32)\n    ortho_matrix[0, 0] = 2 / (right - left)\n    ortho_matrix[1, 1] = 2 / (top - bottom)\n    ortho_matrix[2, 2] = -2 / (far - near)\n    ortho_matrix[0, 3] = -(right + left) / (right - left)\n    ortho_matrix[1, 3] = -(top + bottom) / (top - bottom)\n    ortho_matrix[2, 3] = -(far + near) / (far - near)\n    return ortho_matrix\n\n\ndef get_perspective_projection_matrix(fovy, aspect_wh, near, far):\n    fovy_rad = math.radians(fovy)\n    return np.array([[1.0 / (math.tan(fovy_rad / 2.0) * aspect_wh), 0, 0, 0],\n                     [0, 1.0 / math.tan(fovy_rad / 2.0), 0, 0],\n                     [0, 0, -(far + near) / (far - near), -\n                     2.0 * far * near / (far - near)],\n                     [0, 0, -1, 0]]).astype(np.float32)\n"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/compile_mesh_painter.bat",
    "content": "FOR /F \"tokens=*\" %%i IN ('python -m pybind11 --includes') DO SET PYINCLUDES=%%i\necho %PYINCLUDES%\ng++ -O3 -Wall -shared -std=c++11 -fPIC %PYINCLUDES% mesh_processor.cpp -o mesh_processor.pyd -lpython3.12"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/mesh_processor.cpp",
    "content": "#include <vector>\n#include <queue>\n#include <cmath>\n#include <algorithm>\n#include <pybind11/pybind11.h>\n#include <pybind11/numpy.h>\n#include <pybind11/stl.h>\n\nnamespace py = pybind11;\nusing namespace std;\n\nstd::pair<py::array_t<float>,\n  py::array_t<uint8_t>>  meshVerticeInpaint_smooth(py::array_t<float> texture,\npy::array_t<uint8_t> mask,\n                 py::array_t<float> vtx_pos, py::array_t<float> vtx_uv, \n                 py::array_t<int> pos_idx, py::array_t<int> uv_idx) {\n    auto texture_buf = texture.request();\n    auto mask_buf = mask.request();\n    auto vtx_pos_buf = vtx_pos.request();\n    auto vtx_uv_buf = vtx_uv.request();\n    auto pos_idx_buf = pos_idx.request();\n    auto uv_idx_buf = uv_idx.request();\n\n    int texture_height = texture_buf.shape[0];\n    int texture_width = texture_buf.shape[1];\n    int texture_channel = texture_buf.shape[2];\n    float* texture_ptr = static_cast<float*>(texture_buf.ptr);\n    uint8_t* mask_ptr = static_cast<uint8_t*>(mask_buf.ptr);\n\n    int vtx_num = vtx_pos_buf.shape[0];\n    float* vtx_pos_ptr = static_cast<float*>(vtx_pos_buf.ptr);\n    float* vtx_uv_ptr = static_cast<float*>(vtx_uv_buf.ptr);\n    int* pos_idx_ptr = static_cast<int*>(pos_idx_buf.ptr);\n    int* uv_idx_ptr = static_cast<int*>(uv_idx_buf.ptr);\n\n    vector<float> vtx_mask(vtx_num, 0.0f);\n    vector<vector<float>> vtx_color(vtx_num, vector<float>(texture_channel, 0.0f));\n    vector<int> uncolored_vtxs;\n\n    vector<vector<int>> G(vtx_num);\n\n    for (int i = 0; i < uv_idx_buf.shape[0]; ++i) {\n        for (int k = 0; k < 3; ++k) {\n            int vtx_uv_idx = uv_idx_ptr[i * 3 + k];\n            int vtx_idx = pos_idx_ptr[i * 3 + k];\n            int uv_v = round(vtx_uv_ptr[vtx_uv_idx * 2] * (texture_width - 1));\n            int uv_u = round((1.0 - vtx_uv_ptr[vtx_uv_idx * 2 + 1]) * (texture_height - 1));\n\n            if (mask_ptr[uv_u * texture_width + uv_v] > 0) {\n                vtx_mask[vtx_idx] = 1.0f;\n                for (int c = 0; c < texture_channel; ++c) {\n                    vtx_color[vtx_idx][c] = texture_ptr[(uv_u * texture_width + uv_v) * texture_channel + c];\n                }\n            }else{\n                uncolored_vtxs.push_back(vtx_idx);\n            }\n\n            G[pos_idx_ptr[i * 3 + k]].push_back(pos_idx_ptr[i * 3 + (k + 1) % 3]);\n        }\n    }\n\n    int smooth_count = 2;\n    int last_uncolored_vtx_count = 0;\n    while (smooth_count>0) {\n        int uncolored_vtx_count = 0;\n\n        for (int vtx_idx : uncolored_vtxs) {\n\n            vector<float> sum_color(texture_channel, 0.0f);\n            float total_weight = 0.0f;\n\n            array<float, 3> vtx_0 = {vtx_pos_ptr[vtx_idx * 3],\nvtx_pos_ptr[vtx_idx * 3 + 1], vtx_pos_ptr[vtx_idx * 3 + 2]};\n            for (int connected_idx : G[vtx_idx]) {\n                if (vtx_mask[connected_idx] > 0) {\n                    array<float, 3> vtx1 = {vtx_pos_ptr[connected_idx * 3],\n                    vtx_pos_ptr[connected_idx * 3 + 1], vtx_pos_ptr[connected_idx * 3 + 2]};\n                    float dist_weight = 1.0f / max(sqrt(pow(vtx_0[0] - vtx1[0], 2) + pow(vtx_0[1] - vtx1[1], 2) + \\\n                     pow(vtx_0[2] - vtx1[2], 2)), 1E-4);\n                    dist_weight = dist_weight * dist_weight;\n                    for (int c = 0; c < texture_channel; ++c) {\n                        sum_color[c] += vtx_color[connected_idx][c] * dist_weight;\n                    }\n                    total_weight += dist_weight;\n                }\n            }\n\n            if (total_weight > 0.0f) {\n                for (int c = 0; c < texture_channel; ++c) {\n                    vtx_color[vtx_idx][c] = sum_color[c] / total_weight;\n                }\n                vtx_mask[vtx_idx] = 1.0f;\n            } else {\n                uncolored_vtx_count++;\n            }\n            \n        }\n\n        if(last_uncolored_vtx_count==uncolored_vtx_count){\n            smooth_count--;\n        }else{\n            smooth_count++;\n        }\n        last_uncolored_vtx_count = uncolored_vtx_count;\n    }\n\n    // Create new arrays for the output\n    py::array_t<float> new_texture(texture_buf.size);\n    py::array_t<uint8_t> new_mask(mask_buf.size);\n\n    auto new_texture_buf = new_texture.request();\n    auto new_mask_buf = new_mask.request();\n\n    float* new_texture_ptr = static_cast<float*>(new_texture_buf.ptr);\n    uint8_t* new_mask_ptr = static_cast<uint8_t*>(new_mask_buf.ptr);\n    // Copy original texture and mask to new arrays\n    std::copy(texture_ptr, texture_ptr + texture_buf.size, new_texture_ptr);\n    std::copy(mask_ptr, mask_ptr + mask_buf.size, new_mask_ptr);\n\n    for (int face_idx = 0; face_idx < uv_idx_buf.shape[0]; ++face_idx) {\n        for (int k = 0; k < 3; ++k) {\n            int vtx_uv_idx = uv_idx_ptr[face_idx * 3 + k];\n            int vtx_idx = pos_idx_ptr[face_idx * 3 + k];\n\n            if (vtx_mask[vtx_idx] == 1.0f) {\n                int uv_v = round(vtx_uv_ptr[vtx_uv_idx * 2] * (texture_width - 1));\n                int uv_u = round((1.0 - vtx_uv_ptr[vtx_uv_idx * 2 + 1]) * (texture_height - 1));\n\n                for (int c = 0; c < texture_channel; ++c) {\n                    new_texture_ptr[(uv_u * texture_width + uv_v) * texture_channel + c] = vtx_color[vtx_idx][c];\n                }\n                new_mask_ptr[uv_u * texture_width + uv_v] = 255;\n            }\n        }\n    }\n\n    // Reshape the new arrays to match the original texture and mask shapes\n    new_texture.resize({texture_height, texture_width, 3});\n    new_mask.resize({texture_height, texture_width});\n  return std::make_pair(new_texture, new_mask);\n}\n\n\nstd::pair<py::array_t<float>, py::array_t<uint8_t>> meshVerticeInpaint(py::array_t<float> texture,\n          py::array_t<uint8_t> mask,\n          py::array_t<float> vtx_pos, py::array_t<float> vtx_uv,\n          py::array_t<int> pos_idx, py::array_t<int> uv_idx, const std::string& method = \"smooth\") {\n    if (method == \"smooth\") {\n        return meshVerticeInpaint_smooth(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx);\n    } else {\n        throw std::invalid_argument(\"Invalid method. Use 'smooth' or 'forward'.\");\n    }\n}\n\nPYBIND11_MODULE(mesh_processor, m) {\n    m.def(\"meshVerticeInpaint\", &meshVerticeInpaint, \"A function to process mesh\",\n          py::arg(\"texture\"), py::arg(\"mask\"),\n          py::arg(\"vtx_pos\"), py::arg(\"vtx_uv\"),\n          py::arg(\"pos_idx\"), py::arg(\"uv_idx\"),\n          py::arg(\"method\") = \"smooth\");\n}"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/mesh_processor.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport numpy as np\n\ndef meshVerticeInpaint_smooth(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx):\n    texture_height, texture_width, texture_channel = texture.shape\n    vtx_num = vtx_pos.shape[0]\n\n    vtx_mask = np.zeros(vtx_num, dtype=np.float32)\n    vtx_color = [np.zeros(texture_channel, dtype=np.float32) for _ in range(vtx_num)]\n    uncolored_vtxs = []\n    G = [[] for _ in range(vtx_num)]\n\n    for i in range(uv_idx.shape[0]):\n        for k in range(3):\n            vtx_uv_idx = uv_idx[i, k]\n            vtx_idx = pos_idx[i, k]\n            uv_v = int(round(vtx_uv[vtx_uv_idx, 0] * (texture_width - 1)))\n            uv_u = int(round((1.0 - vtx_uv[vtx_uv_idx, 1]) * (texture_height - 1)))\n            if mask[uv_u, uv_v] > 0:\n                vtx_mask[vtx_idx] = 1.0\n                vtx_color[vtx_idx] = texture[uv_u, uv_v]\n            else:\n                uncolored_vtxs.append(vtx_idx)\n            G[pos_idx[i, k]].append(pos_idx[i, (k + 1) % 3])\n\n    smooth_count = 2\n    last_uncolored_vtx_count = 0\n    while smooth_count > 0:\n        uncolored_vtx_count = 0\n        for vtx_idx in uncolored_vtxs:\n            sum_color = np.zeros(texture_channel, dtype=np.float32)\n            total_weight = 0.0\n            vtx_0 = vtx_pos[vtx_idx]\n            for connected_idx in G[vtx_idx]:\n                if vtx_mask[connected_idx] > 0:\n                    vtx1 = vtx_pos[connected_idx]\n                    dist = np.sqrt(np.sum((vtx_0 - vtx1) ** 2))\n                    dist_weight = 1.0 / max(dist, 1e-4)\n                    dist_weight *= dist_weight\n                    sum_color += vtx_color[connected_idx] * dist_weight\n                    total_weight += dist_weight\n            if total_weight > 0:\n                vtx_color[vtx_idx] = sum_color / total_weight\n                vtx_mask[vtx_idx] = 1.0\n            else:\n                uncolored_vtx_count += 1\n\n        if last_uncolored_vtx_count == uncolored_vtx_count:\n            smooth_count -= 1\n        else:\n            smooth_count += 1\n        last_uncolored_vtx_count = uncolored_vtx_count\n\n    new_texture = texture.copy()\n    new_mask = mask.copy()\n    for face_idx in range(uv_idx.shape[0]):\n        for k in range(3):\n            vtx_uv_idx = uv_idx[face_idx, k]\n            vtx_idx = pos_idx[face_idx, k]\n            if vtx_mask[vtx_idx] == 1.0:\n                uv_v = int(round(vtx_uv[vtx_uv_idx, 0] * (texture_width - 1)))\n                uv_u = int(round((1.0 - vtx_uv[vtx_uv_idx, 1]) * (texture_height - 1)))\n                new_texture[uv_u, uv_v] = vtx_color[vtx_idx]\n                new_mask[uv_u, uv_v] = 255\n    return new_texture, new_mask\n\ndef meshVerticeInpaint(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx, method=\"smooth\"):\n    if method == \"smooth\":\n        return meshVerticeInpaint_smooth(texture, mask, vtx_pos, vtx_uv, pos_idx, uv_idx)\n    else:\n        raise ValueError(\"Invalid method. Use 'smooth' or 'forward'.\")"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/mesh_render.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport cv2\nimport numpy as np\nimport torch\nimport torch.nn.functional as F\nimport trimesh\nfrom PIL import Image\n\nfrom .camera_utils import (\n    transform_pos,\n    get_mv_matrix,\n    get_orthographic_projection_matrix,\n    get_perspective_projection_matrix,\n)\nfrom .mesh_processor import meshVerticeInpaint\nfrom .mesh_utils import load_mesh, save_mesh\n\n\ndef stride_from_shape(shape):\n    stride = [1]\n    for x in reversed(shape[1:]):\n        stride.append(stride[-1] * x)\n    return list(reversed(stride))\n\n\ndef scatter_add_nd_with_count(input, count, indices, values, weights=None):\n    # input: [..., C], D dimension + C channel\n    # count: [..., 1], D dimension\n    # indices: [N, D], long\n    # values: [N, C]\n\n    D = indices.shape[-1]\n    C = input.shape[-1]\n    size = input.shape[:-1]\n    stride = stride_from_shape(size)\n\n    assert len(size) == D\n\n    input = input.view(-1, C)  # [HW, C]\n    count = count.view(-1, 1)\n\n    flatten_indices = (indices * torch.tensor(stride,\n                                              dtype=torch.long, device=indices.device)).sum(-1)  # [N]\n\n    if weights is None:\n        weights = torch.ones_like(values[..., :1])\n\n    input.scatter_add_(0, flatten_indices.unsqueeze(1).repeat(1, C), values)\n    count.scatter_add_(0, flatten_indices.unsqueeze(1), weights)\n\n    return input.view(*size, C), count.view(*size, 1)\n\n\ndef linear_grid_put_2d(H, W, coords, values, return_count=False):\n    # coords: [N, 2], float in [0, 1]\n    # values: [N, C]\n\n    C = values.shape[-1]\n\n    indices = coords * torch.tensor(\n        [H - 1, W - 1], dtype=torch.float32, device=coords.device\n    )\n    indices_00 = indices.floor().long()  # [N, 2]\n    indices_00[:, 0].clamp_(0, H - 2)\n    indices_00[:, 1].clamp_(0, W - 2)\n    indices_01 = indices_00 + torch.tensor(\n        [0, 1], dtype=torch.long, device=indices.device\n    )\n    indices_10 = indices_00 + torch.tensor(\n        [1, 0], dtype=torch.long, device=indices.device\n    )\n    indices_11 = indices_00 + torch.tensor(\n        [1, 1], dtype=torch.long, device=indices.device\n    )\n\n    h = indices[..., 0] - indices_00[..., 0].float()\n    w = indices[..., 1] - indices_00[..., 1].float()\n    w_00 = (1 - h) * (1 - w)\n    w_01 = (1 - h) * w\n    w_10 = h * (1 - w)\n    w_11 = h * w\n\n    result = torch.zeros(H, W, C, device=values.device,\n                         dtype=values.dtype)  # [H, W, C]\n    count = torch.zeros(H, W, 1, device=values.device,\n                        dtype=values.dtype)  # [H, W, 1]\n    weights = torch.ones_like(values[..., :1])  # [N, 1]\n\n    result, count = scatter_add_nd_with_count(\n        result, count, indices_00, values * w_00.unsqueeze(1), weights * w_00.unsqueeze(1))\n    result, count = scatter_add_nd_with_count(\n        result, count, indices_01, values * w_01.unsqueeze(1), weights * w_01.unsqueeze(1))\n    result, count = scatter_add_nd_with_count(\n        result, count, indices_10, values * w_10.unsqueeze(1), weights * w_10.unsqueeze(1))\n    result, count = scatter_add_nd_with_count(\n        result, count, indices_11, values * w_11.unsqueeze(1), weights * w_11.unsqueeze(1))\n\n    if return_count:\n        return result, count\n\n    mask = (count.squeeze(-1) > 0)\n    result[mask] = result[mask] / count[mask].repeat(1, C)\n\n    return result\n\n\nclass MeshRender():\n    def __init__(\n        self,\n        camera_distance=1.45, camera_type='orth',\n        default_resolution=1024, texture_size=1024,\n        use_antialias=True, max_mip_level=None, filter_mode='linear',\n        bake_mode='linear', raster_mode='cr', device='cuda'):\n\n        self.device = device\n\n        self.set_default_render_resolution(default_resolution)\n        self.set_default_texture_resolution(texture_size)\n\n        self.camera_distance = camera_distance\n        self.use_antialias = use_antialias\n        self.max_mip_level = max_mip_level\n        self.filter_mode = filter_mode\n\n        self.bake_angle_thres = 75\n        self.bake_unreliable_kernel_size = int(\n            (2 / 512) * max(self.default_resolution[0], self.default_resolution[1]))\n        self.bake_mode = bake_mode\n\n        self.raster_mode = raster_mode\n        if self.raster_mode == 'cr':\n            import custom_rasterizer as cr\n            self.raster = cr\n        else:\n            raise f'No raster named {self.raster_mode}'\n\n        if camera_type == 'orth':\n            self.ortho_scale = 1.2\n            self.camera_proj_mat = get_orthographic_projection_matrix(\n                left=-self.ortho_scale * 0.5, right=self.ortho_scale * 0.5,\n                bottom=-self.ortho_scale * 0.5, top=self.ortho_scale * 0.5,\n                near=0.1, far=100\n            )\n        elif camera_type == 'perspective':\n            self.camera_proj_mat = get_perspective_projection_matrix(\n                49.13, self.default_resolution[1] / self.default_resolution[0],\n                0.01, 100.0\n            )\n        else:\n            raise f'No camera type {camera_type}'\n\n    def raster_rasterize(self, pos, tri, resolution, ranges=None, grad_db=True):\n\n        if self.raster_mode == 'cr':\n            rast_out_db = None\n            if pos.dim() == 2:\n                pos = pos.unsqueeze(0)\n            findices, barycentric = self.raster.rasterize(pos, tri, resolution)\n            rast_out = torch.cat((barycentric, findices.unsqueeze(-1)), dim=-1)\n            rast_out = rast_out.unsqueeze(0)\n        else:\n            raise f'No raster named {self.raster_mode}'\n\n        return rast_out, rast_out_db\n\n    def raster_interpolate(self, uv, rast_out, uv_idx, rast_db=None, diff_attrs=None):\n\n        if self.raster_mode == 'cr':\n            textd = None\n            barycentric = rast_out[0, ..., :-1]\n            findices = rast_out[0, ..., -1]\n            if uv.dim() == 2:\n                uv = uv.unsqueeze(0)\n            textc = self.raster.interpolate(uv, findices, barycentric, uv_idx)\n        else:\n            raise f'No raster named {self.raster_mode}'\n\n        return textc, textd\n\n    def raster_texture(self, tex, uv, uv_da=None, mip_level_bias=None, mip=None, filter_mode='auto',\n                       boundary_mode='wrap', max_mip_level=None):\n\n        if self.raster_mode == 'cr':\n            raise f'Texture is not implemented in cr'\n        else:\n            raise f'No raster named {self.raster_mode}'\n\n        return color\n\n    def raster_antialias(self, color, rast, pos, tri, topology_hash=None, pos_gradient_boost=1.0):\n\n        if self.raster_mode == 'cr':\n            # Antialias has not been supported yet\n            color = color\n        else:\n            raise f'No raster named {self.raster_mode}'\n\n        return color\n\n    def load_mesh(\n        self,\n        mesh,\n        scale_factor=1.15,\n        auto_center=True,\n    ):\n        vtx_pos, pos_idx, vtx_uv, uv_idx, texture_data = load_mesh(mesh)\n        self.mesh_copy = mesh\n        self.set_mesh(vtx_pos, pos_idx,\n                      vtx_uv=vtx_uv, uv_idx=uv_idx,\n                      scale_factor=scale_factor, auto_center=auto_center\n                      )\n        if texture_data is not None:\n            self.set_texture(texture_data)\n\n    def save_mesh(self):\n        texture_data = self.get_texture()\n        texture_data = Image.fromarray((texture_data * 255).astype(np.uint8))\n        return save_mesh(self.mesh_copy, texture_data)\n\n    def set_mesh(\n        self,\n        vtx_pos, pos_idx,\n        vtx_uv=None, uv_idx=None,\n        scale_factor=1.15, auto_center=True\n    ):\n\n        self.vtx_pos = torch.from_numpy(vtx_pos).to(self.device).float()\n        self.pos_idx = torch.from_numpy(pos_idx).to(self.device).to(torch.int)\n        if (vtx_uv is not None) and (uv_idx is not None):\n            self.vtx_uv = torch.from_numpy(vtx_uv).to(self.device).float()\n            self.uv_idx = torch.from_numpy(uv_idx).to(self.device).to(torch.int)\n        else:\n            self.vtx_uv = None\n            self.uv_idx = None\n\n        self.vtx_pos[:, [0, 1]] = -self.vtx_pos[:, [0, 1]]\n        self.vtx_pos[:, [1, 2]] = self.vtx_pos[:, [2, 1]]\n        if (vtx_uv is not None) and (uv_idx is not None):\n            self.vtx_uv[:, 1] = 1.0 - self.vtx_uv[:, 1]\n\n        if auto_center:\n            max_bb = (self.vtx_pos - 0).max(0)[0]\n            min_bb = (self.vtx_pos - 0).min(0)[0]\n            center = (max_bb + min_bb) / 2\n            scale = torch.norm(self.vtx_pos - center, dim=1).max() * 2.0\n            self.vtx_pos = (self.vtx_pos - center) * \\\n                           (scale_factor / float(scale))\n            self.scale_factor = scale_factor\n\n    def set_texture(self, tex):\n        if isinstance(tex, np.ndarray):\n            tex = Image.fromarray((tex * 255).astype(np.uint8))\n        elif isinstance(tex, torch.Tensor):\n            tex = tex.cpu().numpy()\n            tex = Image.fromarray((tex * 255).astype(np.uint8))\n\n        tex = tex.resize(self.texture_size).convert('RGB')\n        tex = np.array(tex) / 255.0\n        self.tex = torch.from_numpy(tex).to(self.device)\n        self.tex = self.tex.float()\n\n    def set_default_render_resolution(self, default_resolution):\n        if isinstance(default_resolution, int):\n            default_resolution = (default_resolution, default_resolution)\n        self.default_resolution = default_resolution\n\n    def set_default_texture_resolution(self, texture_size):\n        if isinstance(texture_size, int):\n            texture_size = (texture_size, texture_size)\n        self.texture_size = texture_size\n\n    def get_mesh(self):\n        vtx_pos = self.vtx_pos.cpu().numpy()\n        pos_idx = self.pos_idx.cpu().numpy()\n        vtx_uv = self.vtx_uv.cpu().numpy()\n        uv_idx = self.uv_idx.cpu().numpy()\n\n        # 坐标变换的逆变换\n        vtx_pos[:, [1, 2]] = vtx_pos[:, [2, 1]]\n        vtx_pos[:, [0, 1]] = -vtx_pos[:, [0, 1]]\n\n        vtx_uv[:, 1] = 1.0 - vtx_uv[:, 1]\n        return vtx_pos, pos_idx, vtx_uv, uv_idx\n\n    def get_texture(self):\n        return self.tex.cpu().numpy()\n\n    def to(self, device):\n        self.device = device\n\n        for attr_name in dir(self):\n            attr_value = getattr(self, attr_name)\n            if isinstance(attr_value, torch.Tensor):\n                setattr(self, attr_name, attr_value.to(self.device))\n\n    def color_rgb_to_srgb(self, image):\n        if isinstance(image, Image.Image):\n            image_rgb = torch.tesnor(\n                np.array(image) /\n                255.0).float().to(\n                self.device)\n        elif isinstance(image, np.ndarray):\n            image_rgb = torch.tensor(image).float()\n        else:\n            image_rgb = image.to(self.device)\n\n        image_srgb = torch.where(\n            image_rgb <= 0.0031308,\n            12.92 * image_rgb,\n            1.055 * torch.pow(image_rgb, 1 / 2.4) - 0.055\n        )\n\n        if isinstance(image, Image.Image):\n            image_srgb = Image.fromarray(\n                (image_srgb.cpu().numpy() *\n                 255).astype(\n                    np.uint8))\n        elif isinstance(image, np.ndarray):\n            image_srgb = image_srgb.cpu().numpy()\n        else:\n            image_srgb = image_srgb.to(image.device)\n\n        return image_srgb\n\n    def _render(\n        self,\n        glctx,\n        mvp,\n        pos,\n        pos_idx,\n        uv,\n        uv_idx,\n        tex,\n        resolution,\n        max_mip_level,\n        keep_alpha,\n        filter_mode\n    ):\n        pos_clip = transform_pos(mvp, pos)\n        if isinstance(resolution, (int, float)):\n            resolution = [resolution, resolution]\n        rast_out, rast_out_db = self.raster_rasterize(\n            glctx, pos_clip, pos_idx, resolution=resolution)\n\n        tex = tex.contiguous()\n        if filter_mode == 'linear-mipmap-linear':\n            texc, texd = self.raster_interpolate(\n                uv[None, ...], rast_out, uv_idx, rast_db=rast_out_db, diff_attrs='all')\n            color = self.raster_texture(\n                tex[None, ...], texc, texd, filter_mode='linear-mipmap-linear', max_mip_level=max_mip_level)\n        else:\n            texc, _ = self.raster_interpolate(uv[None, ...], rast_out, uv_idx)\n            color = self.raster_texture(tex[None, ...], texc, filter_mode=filter_mode)\n\n        visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)\n        color = color * visible_mask  # Mask out background.\n        if self.use_antialias:\n            color = self.raster_antialias(color, rast_out, pos_clip, pos_idx)\n\n        if keep_alpha:\n            color = torch.cat([color, visible_mask], dim=-1)\n        return color[0, ...]\n\n    def render(\n        self,\n        elev,\n        azim,\n        camera_distance=None,\n        center=None,\n        resolution=None,\n        tex=None,\n        keep_alpha=True,\n        bgcolor=None,\n        filter_mode=None,\n        return_type='th'\n    ):\n\n        proj = self.camera_proj_mat\n        r_mv = get_mv_matrix(\n            elev=elev,\n            azim=azim,\n            camera_distance=self.camera_distance if camera_distance is None else camera_distance,\n            center=center)\n        r_mvp = np.matmul(proj, r_mv).astype(np.float32)\n        if tex is not None:\n            if isinstance(tex, Image.Image):\n                tex = torch.tensor(np.array(tex) / 255.0)\n            elif isinstance(tex, np.ndarray):\n                tex = torch.tensor(tex)\n            if tex.dim() == 2:\n                tex = tex.unsqueeze(-1)\n            tex = tex.float().to(self.device)\n        image = self._render(r_mvp, self.vtx_pos, self.pos_idx, self.vtx_uv, self.uv_idx,\n                             self.tex if tex is None else tex,\n                             self.default_resolution if resolution is None else resolution,\n                             self.max_mip_level, True, filter_mode if filter_mode else self.filter_mode)\n        mask = (image[..., [-1]] == 1).float()\n        if bgcolor is None:\n            bgcolor = [0 for _ in range(image.shape[-1] - 1)]\n        image = image * mask + (1 - mask) * \\\n                torch.tensor(bgcolor + [0]).to(self.device)\n        if keep_alpha == False:\n            image = image[..., :-1]\n        if return_type == 'np':\n            image = image.cpu().numpy()\n        elif return_type == 'pl':\n            image = image.squeeze(-1).cpu().numpy() * 255\n            image = Image.fromarray(image.astype(np.uint8))\n        return image\n\n    def render_normal(\n        self,\n        elev,\n        azim,\n        camera_distance=None,\n        center=None,\n        resolution=None,\n        bg_color=[1, 1, 1],\n        use_abs_coor=False,\n        normalize_rgb=True,\n        return_type='th'\n    ):\n\n        pos_camera, pos_clip = self.get_pos_from_mvp(elev, azim, camera_distance, center)\n        if resolution is None:\n            resolution = self.default_resolution\n        if isinstance(resolution, (int, float)):\n            resolution = [resolution, resolution]\n        rast_out, rast_out_db = self.raster_rasterize(\n            pos_clip, self.pos_idx, resolution=resolution)\n\n        if use_abs_coor:\n            mesh_triangles = self.vtx_pos[self.pos_idx[:, :3], :]\n        else:\n            pos_camera = pos_camera[:, :3] / pos_camera[:, 3:4]\n            mesh_triangles = pos_camera[self.pos_idx[:, :3], :]\n        face_normals = F.normalize(\n            torch.cross(mesh_triangles[:,\n                        1,\n                        :] - mesh_triangles[:,\n                             0,\n                             :],\n                        mesh_triangles[:,\n                        2,\n                        :] - mesh_triangles[:,\n                             0,\n                             :],\n                        dim=-1),\n            dim=-1)\n\n        vertex_normals = trimesh.geometry.mean_vertex_normals(vertex_count=self.vtx_pos.shape[0],\n                                                              faces=self.pos_idx.cpu(),\n                                                              face_normals=face_normals.cpu(), )\n        vertex_normals = torch.from_numpy(\n            vertex_normals).float().to(self.device).contiguous()\n\n        # Interpolate normal values across the rasterized pixels\n        normal, _ = self.raster_interpolate(\n            vertex_normals[None, ...], rast_out, self.pos_idx)\n\n        visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)\n        normal = normal * visible_mask + \\\n                 torch.tensor(bg_color, dtype=torch.float32, device=self.device) * (1 -\n                                                                                    visible_mask)  \n\n        if normalize_rgb:\n            normal = (normal + 1) * 0.5\n        if self.use_antialias:\n            normal = self.raster_antialias(normal, rast_out, pos_clip, self.pos_idx)\n\n        image = normal[0, ...]\n        if return_type == 'np':\n            image = image.cpu().numpy()\n        elif return_type == 'pl':\n            image = image.cpu().numpy() * 255\n            image = Image.fromarray(image.astype(np.uint8))\n\n        return image\n\n    def convert_normal_map(self, image):\n        # blue is front, red is left, green is top\n        if isinstance(image, Image.Image):\n            image = np.array(image)\n        mask = (image == [255, 255, 255]).all(axis=-1)\n\n        image = (image / 255.0) * 2.0 - 1.0\n\n        image[..., [1]] = -image[..., [1]]\n        image[..., [1, 2]] = image[..., [2, 1]]\n        image[..., [0]] = -image[..., [0]]\n\n        image = (image + 1.0) * 0.5\n\n        image = (image * 255).astype(np.uint8)\n        image[mask] = [127, 127, 255]\n\n        return Image.fromarray(image)\n\n    def get_pos_from_mvp(self, elev, azim, camera_distance, center):\n        proj = self.camera_proj_mat\n        r_mv = get_mv_matrix(\n            elev=elev,\n            azim=azim,\n            camera_distance=self.camera_distance if camera_distance is None else camera_distance,\n            center=center)\n\n        pos_camera = transform_pos(r_mv, self.vtx_pos, keepdim=True)\n        pos_clip = transform_pos(proj, pos_camera)\n\n        return pos_camera, pos_clip\n\n    def render_depth(\n        self,\n        elev,\n        azim,\n        camera_distance=None,\n        center=None,\n        resolution=None,\n        return_type='th'\n    ):\n        pos_camera, pos_clip = self.get_pos_from_mvp(elev, azim, camera_distance, center)\n\n        if resolution is None:\n            resolution = self.default_resolution\n        if isinstance(resolution, (int, float)):\n            resolution = [resolution, resolution]\n        rast_out, rast_out_db = self.raster_rasterize(\n            pos_clip, self.pos_idx, resolution=resolution)\n\n        pos_camera = pos_camera[:, :3] / pos_camera[:, 3:4]\n        tex_depth = pos_camera[:, 2].reshape(1, -1, 1).contiguous()\n\n        # Interpolate depth values across the rasterized pixels\n        depth, _ = self.raster_interpolate(tex_depth, rast_out, self.pos_idx)\n\n        visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)\n        depth_max, depth_min = depth[visible_mask >\n                                     0].max(), depth[visible_mask > 0].min()\n        depth = (depth - depth_min) / (depth_max - depth_min)\n\n        depth = depth * visible_mask  # Mask out background.\n        if self.use_antialias:\n            depth = self.raster_antialias(depth, rast_out, pos_clip, self.pos_idx)\n\n        image = depth[0, ...]\n        if return_type == 'np':\n            image = image.cpu().numpy()\n        elif return_type == 'pl':\n            image = image.squeeze(-1).cpu().numpy() * 255\n            image = Image.fromarray(image.astype(np.uint8))\n        return image\n\n    def render_position(self, elev, azim, camera_distance=None, center=None,\n                        resolution=None, bg_color=[1, 1, 1], return_type='th'):\n        pos_camera, pos_clip = self.get_pos_from_mvp(elev, azim, camera_distance, center)\n        if resolution is None:\n            resolution = self.default_resolution\n        if isinstance(resolution, (int, float)):\n            resolution = [resolution, resolution]\n        rast_out, rast_out_db = self.raster_rasterize(\n            pos_clip, self.pos_idx, resolution=resolution)\n\n        tex_position = 0.5 - self.vtx_pos[:, :3] / self.scale_factor\n        tex_position = tex_position.contiguous()\n\n        # Interpolate depth values across the rasterized pixels\n        position, _ = self.raster_interpolate(\n            tex_position[None, ...], rast_out, self.pos_idx)\n\n        visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)\n\n        position = position * visible_mask + \\\n                   torch.tensor(bg_color, dtype=torch.float32, device=self.device) * (1 -\n                                                                                      visible_mask) \n        if self.use_antialias:\n            position = self.raster_antialias(position, rast_out, pos_clip, self.pos_idx)\n\n        image = position[0, ...]\n\n        if return_type == 'np':\n            image = image.cpu().numpy()\n        elif return_type == 'pl':\n            image = image.squeeze(-1).cpu().numpy() * 255\n            image = Image.fromarray(image.astype(np.uint8))\n        return image\n\n    def render_uvpos(self, return_type='th'):\n        image = self.uv_feature_map(self.vtx_pos * 0.5 + 0.5)\n        if return_type == 'np':\n            image = image.cpu().numpy()\n        elif return_type == 'pl':\n            image = image.cpu().numpy() * 255\n            image = Image.fromarray(image.astype(np.uint8))\n        return image\n\n    def uv_feature_map(self, vert_feat, bg=None):\n        vtx_uv = self.vtx_uv * 2 - 1.0\n        vtx_uv = torch.cat(\n            [vtx_uv, torch.zeros_like(self.vtx_uv)], dim=1).unsqueeze(0)\n        vtx_uv[..., -1] = 1\n        uv_idx = self.uv_idx\n        rast_out, rast_out_db = self.raster_rasterize(\n            vtx_uv, uv_idx, resolution=self.texture_size)\n        feat_map, _ = self.raster_interpolate(vert_feat[None, ...], rast_out, uv_idx)\n        feat_map = feat_map[0, ...]\n        if bg is not None:\n            visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)[0, ...]\n            feat_map[visible_mask == 0] = bg\n        return feat_map\n\n    def render_sketch_from_geometry(self, normal_image, depth_image):\n        normal_image_np = normal_image.cpu().numpy()\n        depth_image_np = depth_image.cpu().numpy()\n\n        normal_image_np = (normal_image_np * 255).astype(np.uint8)\n        depth_image_np = (depth_image_np * 255).astype(np.uint8)\n        normal_image_np = cv2.cvtColor(normal_image_np, cv2.COLOR_RGB2GRAY)\n\n        normal_edges = cv2.Canny(normal_image_np, 80, 150)\n        depth_edges = cv2.Canny(depth_image_np, 30, 80)\n\n        combined_edges = np.maximum(normal_edges, depth_edges)\n\n        sketch_image = torch.from_numpy(combined_edges).to(\n            normal_image.device).float() / 255.0\n        sketch_image = sketch_image.unsqueeze(-1)\n\n        return sketch_image\n\n    def render_sketch_from_depth(self, depth_image):\n        depth_image_np = depth_image.cpu().numpy()\n        depth_image_np = (depth_image_np * 255).astype(np.uint8)\n        depth_edges = cv2.Canny(depth_image_np, 30, 80)\n        combined_edges = depth_edges\n        sketch_image = torch.from_numpy(combined_edges).to(\n            depth_image.device).float() / 255.0\n        sketch_image = sketch_image.unsqueeze(-1)\n        return sketch_image\n\n    def back_project(self, image, elev, azim,\n                     camera_distance=None, center=None, method=None):\n        if isinstance(image, Image.Image):\n            image = torch.tensor(np.array(image) / 255.0)\n        elif isinstance(image, np.ndarray):\n            image = torch.tensor(image)\n        if image.dim() == 2:\n            image = image.unsqueeze(-1)\n        image = image.float().to(self.device)\n        resolution = image.shape[:2]\n        channel = image.shape[-1]\n        texture = torch.zeros(self.texture_size + (channel,)).to(self.device)\n        cos_map = torch.zeros(self.texture_size + (1,)).to(self.device)\n\n        proj = self.camera_proj_mat\n        r_mv = get_mv_matrix(\n            elev=elev,\n            azim=azim,\n            camera_distance=self.camera_distance if camera_distance is None else camera_distance,\n            center=center)\n        pos_camera = transform_pos(r_mv, self.vtx_pos, keepdim=True)\n        pos_clip = transform_pos(proj, pos_camera)\n        pos_camera = pos_camera[:, :3] / pos_camera[:, 3:4]\n        v0 = pos_camera[self.pos_idx[:, 0], :]\n        v1 = pos_camera[self.pos_idx[:, 1], :]\n        v2 = pos_camera[self.pos_idx[:, 2], :]\n        face_normals = F.normalize(\n            torch.cross(\n                v1 - v0,\n                v2 - v0,\n                dim=-1),\n            dim=-1)\n        vertex_normals = trimesh.geometry.mean_vertex_normals(vertex_count=self.vtx_pos.shape[0],\n                                                              faces=self.pos_idx.cpu(),\n                                                              face_normals=face_normals.cpu(), )\n        vertex_normals = torch.from_numpy(\n            vertex_normals).float().to(self.device).contiguous()\n        tex_depth = pos_camera[:, 2].reshape(1, -1, 1).contiguous()\n        rast_out, rast_out_db = self.raster_rasterize(\n            pos_clip, self.pos_idx, resolution=resolution)\n        visible_mask = torch.clamp(rast_out[..., -1:], 0, 1)[0, ...]\n\n        normal, _ = self.raster_interpolate(\n            vertex_normals[None, ...], rast_out, self.pos_idx)\n        normal = normal[0, ...]\n        uv, _ = self.raster_interpolate(self.vtx_uv[None, ...], rast_out, self.uv_idx)\n        depth, _ = self.raster_interpolate(tex_depth, rast_out, self.pos_idx)\n        depth = depth[0, ...]\n\n        depth_max, depth_min = depth[visible_mask >\n                                     0].max(), depth[visible_mask > 0].min()\n        depth_normalized = (depth - depth_min) / (depth_max - depth_min)\n        depth_image = depth_normalized * visible_mask  # Mask out background.\n\n        sketch_image = self.render_sketch_from_depth(depth_image)\n\n        lookat = torch.tensor([[0, 0, -1]], device=self.device)\n        cos_image = torch.nn.functional.cosine_similarity(\n            lookat, normal.view(-1, 3))\n        cos_image = cos_image.view(normal.shape[0], normal.shape[1], 1)\n\n        cos_thres = np.cos(self.bake_angle_thres / 180 * np.pi)\n        cos_image[cos_image < cos_thres] = 0\n\n        # shrink\n        kernel_size = self.bake_unreliable_kernel_size * 2 + 1\n        kernel = torch.ones(\n            (1, 1, kernel_size, kernel_size), dtype=torch.float32).to(\n            sketch_image.device)\n\n        visible_mask = visible_mask.permute(2, 0, 1).unsqueeze(0).float()\n        visible_mask = F.conv2d(\n            1.0 - visible_mask,\n            kernel,\n            padding=kernel_size // 2)\n        visible_mask = 1.0 - (visible_mask > 0).float()  # 二值化\n        visible_mask = visible_mask.squeeze(0).permute(1, 2, 0)\n\n        sketch_image = sketch_image.permute(2, 0, 1).unsqueeze(0)\n        sketch_image = F.conv2d(sketch_image, kernel, padding=kernel_size // 2)\n        sketch_image = (sketch_image > 0).float()  # 二值化\n        sketch_image = sketch_image.squeeze(0).permute(1, 2, 0)\n        visible_mask = visible_mask * (sketch_image < 0.5)\n\n        cos_image[visible_mask == 0] = 0\n\n        method = self.bake_mode if method is None else method\n\n        if method == 'linear':\n            proj_mask = (visible_mask != 0).view(-1)\n            uv = uv.squeeze(0).contiguous().view(-1, 2)[proj_mask]\n            image = image.squeeze(0).contiguous().view(-1, channel)[proj_mask]\n            cos_image = cos_image.contiguous().view(-1, 1)[proj_mask]\n            sketch_image = sketch_image.contiguous().view(-1, 1)[proj_mask]\n\n            texture = linear_grid_put_2d(\n                self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], image)\n            cos_map = linear_grid_put_2d(\n                self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], cos_image)\n            boundary_map = linear_grid_put_2d(\n                self.texture_size[1], self.texture_size[0], uv[..., [1, 0]], sketch_image)\n        else:\n            raise f'No bake mode {method}'\n\n        return texture, cos_map, boundary_map\n\n    def bake_texture(self, colors, elevs, azims,\n                     camera_distance=None, center=None, exp=6, weights=None):\n        for i in range(len(colors)):\n            if isinstance(colors[i], Image.Image):\n                colors[i] = torch.tensor(\n                    np.array(\n                        colors[i]) / 255.0,\n                    device=self.device).float()\n        if weights is None:\n            weights = [1.0 for _ in range(colors)]\n        textures = []\n        cos_maps = []\n        for color, elev, azim, weight in zip(colors, elevs, azims, weights):\n            texture, cos_map, _ = self.back_project(\n                color, elev, azim, camera_distance, center)\n            cos_map = weight * (cos_map ** exp)\n            textures.append(texture)\n            cos_maps.append(cos_map)\n\n        texture_merge, trust_map_merge = self.fast_bake_texture(\n            textures, cos_maps)\n        return texture_merge, trust_map_merge\n\n    @torch.no_grad()\n    def fast_bake_texture(self, textures, cos_maps):\n\n        channel = textures[0].shape[-1]\n        texture_merge = torch.zeros(\n            self.texture_size + (channel,)).to(self.device)\n        trust_map_merge = torch.zeros(self.texture_size + (1,)).to(self.device)\n        for texture, cos_map in zip(textures, cos_maps):\n            view_sum = (cos_map > 0).sum()\n            painted_sum = ((cos_map > 0) * (trust_map_merge > 0)).sum()\n            if painted_sum / view_sum > 0.99:\n                continue\n            texture_merge += texture * cos_map\n            trust_map_merge += cos_map\n        texture_merge = texture_merge / torch.clamp(trust_map_merge, min=1E-8)\n\n        return texture_merge, trust_map_merge > 1E-8\n\n    def uv_inpaint(self, texture, mask):\n\n        if isinstance(texture, torch.Tensor):\n            texture_np = texture.cpu().numpy()\n        elif isinstance(texture, np.ndarray):\n            texture_np = texture\n        elif isinstance(texture, Image.Image):\n            texture_np = np.array(texture) / 255.0\n\n        vtx_pos, pos_idx, vtx_uv, uv_idx = self.get_mesh()\n\n        texture_np, mask = meshVerticeInpaint(\n            texture_np, mask, vtx_pos, vtx_uv, pos_idx, uv_idx)\n\n        texture_np = cv2.inpaint(\n            (texture_np *\n             255).astype(\n                np.uint8),\n            255 -\n            mask,\n            3,\n            cv2.INPAINT_NS)\n\n        return texture_np\n"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/mesh_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport trimesh\n\n\ndef load_mesh(mesh):\n    vtx_pos = mesh.vertices if hasattr(mesh, 'vertices') else None\n    pos_idx = mesh.faces if hasattr(mesh, 'faces') else None\n\n    vtx_uv = mesh.visual.uv if hasattr(mesh.visual, 'uv') else None\n    uv_idx = mesh.faces if hasattr(mesh, 'faces') else None\n\n    texture_data = None\n\n    return vtx_pos, pos_idx, vtx_uv, uv_idx, texture_data\n\n\ndef save_mesh(mesh, texture_data):\n    material = trimesh.visual.texture.SimpleMaterial(image=texture_data, diffuse=(255, 255, 255))\n    texture_visuals = trimesh.visual.TextureVisuals(uv=mesh.visual.uv, image=texture_data, material=material)\n    mesh.visual = texture_visuals\n    return mesh\n"
  },
  {
    "path": "hy3dgen/texgen/differentiable_renderer/setup.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom setuptools import setup, Extension\nimport pybind11\nimport sys\nimport platform\n\ndef get_platform_specific_args():\n    system = platform.system().lower()\n    cpp_std = 'c++14'  # Make configurable if needed\n    \n    if sys.platform == 'win32':\n        compile_args = ['/O2', f'/std:{cpp_std}', '/EHsc', '/MP', '/DWIN32_LEAN_AND_MEAN', '/bigobj']\n        link_args = []\n        extra_includes = []\n    elif system == 'linux':\n        compile_args = ['-O3', f'-std={cpp_std}', '-fPIC', '-Wall', '-Wextra', '-pthread']\n        link_args = ['-fPIC', '-pthread']\n        extra_includes = []\n    elif sys.platform == 'darwin':\n        compile_args = ['-O3', f'-std={cpp_std}', '-fPIC', '-Wall', '-Wextra',\n                       '-stdlib=libc++', '-mmacosx-version-min=10.14']\n        link_args = ['-fPIC', '-stdlib=libc++', '-mmacosx-version-min=10.14', '-dynamiclib']\n        extra_includes = []\n    else:\n        raise RuntimeError(f\"Unsupported platform: {system}\")\n    \n    return compile_args, link_args, extra_includes\n\nextra_compile_args, extra_link_args, platform_includes = get_platform_specific_args()\ninclude_dirs = [pybind11.get_include(), pybind11.get_include(user=True)]\ninclude_dirs.extend(platform_includes)\n\next_modules = [\n    Extension(\n        \"mesh_processor\",\n        [\"mesh_processor.cpp\"],\n        include_dirs=include_dirs,\n        language='c++',\n        extra_compile_args=extra_compile_args,\n        extra_link_args=extra_link_args,\n    ),\n]\n\nsetup(\n    name=\"mesh_processor\",\n    ext_modules=ext_modules,\n    install_requires=['pybind11>=2.6.0'],\n    python_requires='>=3.6',\n)"
  },
  {
    "path": "hy3dgen/texgen/hunyuanpaint/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT."
  },
  {
    "path": "hy3dgen/texgen/hunyuanpaint/pipeline.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom typing import Any, Callable, Dict, List, Optional, Union\n\nimport numpy\nimport numpy as np\nimport torch\nimport torch.distributed\nimport torch.utils.checkpoint\nimport transformers\nfrom PIL import Image\nimport diffusers\nfrom diffusers import (\n    AutoencoderKL,\n    DDPMScheduler,\n    DiffusionPipeline,\n    EulerAncestralDiscreteScheduler,\n    UNet2DConditionModel,\n    ImagePipelineOutput\n)\nfrom diffusers.callbacks import MultiPipelineCallbacks, PipelineCallback\nfrom diffusers.image_processor import PipelineImageInput\nfrom diffusers.image_processor import VaeImageProcessor\nfrom diffusers.pipelines.stable_diffusion.pipeline_output import StableDiffusionPipelineOutput\nfrom diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion import StableDiffusionPipeline, \\\n    retrieve_timesteps, rescale_noise_cfg\nfrom diffusers.schedulers import KarrasDiffusionSchedulers, LCMScheduler\nfrom diffusers.utils import deprecate\nfrom einops import rearrange\nfrom transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer, CLIPVisionModelWithProjection\n\nfrom .unet.modules import UNet2p5DConditionModel, \\\n    compute_multi_resolution_mask, compute_multi_resolution_discrete_voxel_indice\n\ndef guidance_scale_embedding(w, embedding_dim=512, dtype=torch.float32):\n    \"\"\"\n    See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298\n\n    Args:\n        timesteps (`torch.Tensor`):\n            generate embedding vectors at these timesteps\n        embedding_dim (`int`, *optional*, defaults to 512):\n            dimension of the embeddings to generate\n        dtype:\n            data type of the generated embeddings\n\n    Returns:\n        `torch.FloatTensor`: Embedding vectors with shape `(len(timesteps), embedding_dim)`\n    \"\"\"\n    assert len(w.shape) == 1\n    w = w * 1000.0\n\n    half_dim = embedding_dim // 2\n    emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1)\n    emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb)\n    emb = w.to(dtype)[:, None] * emb[None, :]\n    emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)\n    if embedding_dim % 2 == 1:  # zero pad\n        emb = torch.nn.functional.pad(emb, (0, 1))\n    assert emb.shape == (w.shape[0], embedding_dim)\n    return emb\n\n\ndef append_dims(x, target_dims):\n    \"\"\"Appends dimensions to the end of a tensor until it has target_dims dimensions.\"\"\"\n    dims_to_append = target_dims - x.ndim\n    if dims_to_append < 0:\n        raise ValueError(f\"input has {x.ndim} dims but target_dims is {target_dims}, which is less\")\n    return x[(...,) + (None,) * dims_to_append]\n\n\n# From LCMScheduler.get_scalings_for_boundary_condition_discrete\ndef scalings_for_boundary_conditions(timestep, sigma_data=0.5, timestep_scaling=10.0):\n    scaled_timestep = timestep_scaling * timestep\n    c_skip = sigma_data ** 2 / (scaled_timestep ** 2 + sigma_data ** 2)\n    c_out = scaled_timestep / (scaled_timestep ** 2 + sigma_data ** 2) ** 0.5\n    return c_skip, c_out\n\n\n# Compare LCMScheduler.step, Step 4\ndef get_predicted_original_sample(model_output, timesteps, sample, prediction_type, alphas, sigmas, N_gen):\n    alphas = extract_into_tensor(alphas, timesteps, sample.shape, N_gen)\n    sigmas = extract_into_tensor(sigmas, timesteps, sample.shape, N_gen)\n    model_output = rearrange(model_output, '(b n) c h w -> b n c h w', n=N_gen)\n    if prediction_type == \"epsilon\":\n        pred_x_0 = (sample - sigmas * model_output) / alphas\n    elif prediction_type == \"sample\":\n        pred_x_0 = model_output\n    elif prediction_type == \"v_prediction\":\n        pred_x_0 = alphas * sample - sigmas * model_output\n    else:\n        raise ValueError(\n            f\"Prediction type {prediction_type} is not supported; currently, `epsilon`, `sample`, and `v_prediction`\"\n            f\" are supported.\"\n        )\n\n    return pred_x_0\n\n\n# Based on step 4 in DDIMScheduler.step\ndef get_predicted_noise(model_output, timesteps, sample, prediction_type, alphas, sigmas, N_gen):\n    alphas = extract_into_tensor(alphas, timesteps, sample.shape, N_gen)\n    sigmas = extract_into_tensor(sigmas, timesteps, sample.shape, N_gen)\n    model_output = rearrange(model_output, '(b n) c h w -> b n c h w', n=N_gen)\n    if prediction_type == \"epsilon\":\n        pred_epsilon = model_output\n    elif prediction_type == \"sample\":\n        pred_epsilon = (sample - alphas * model_output) / sigmas\n    elif prediction_type == \"v_prediction\":\n        pred_epsilon = alphas * model_output + sigmas * sample\n    else:\n        raise ValueError(\n            f\"Prediction type {prediction_type} is not supported; currently, `epsilon`, `sample`, and `v_prediction`\"\n            f\" are supported.\"\n        )\n\n    return pred_epsilon\n    \ndef extract_into_tensor(a, t, x_shape, N_gen):\n    # b, *_ = t.shape\n    out = a.gather(-1, t)\n    out = out.repeat(N_gen)\n    out = rearrange(out, '(b n) -> b n', n=N_gen)\n    b, c, *_ = out.shape\n    return out.reshape(b, c, *((1,) * (len(x_shape) - 2)))\n\nclass DDIMSolver:\n    def __init__(self, alpha_cumprods, timesteps=1000, ddim_timesteps=50):\n        # DDIM sampling parameters\n        step_ratio = timesteps // ddim_timesteps\n        self.ddim_timesteps = (np.arange(1, ddim_timesteps + 1) * step_ratio).round().astype(np.int64) - 1\n        self.ddim_alpha_cumprods = alpha_cumprods[self.ddim_timesteps]\n        self.ddim_alpha_cumprods_prev = np.asarray(\n            [alpha_cumprods[0]] + alpha_cumprods[self.ddim_timesteps[:-1]].tolist()\n        )\n        # convert to torch tensors\n        self.ddim_timesteps = torch.from_numpy(self.ddim_timesteps).long()\n        self.ddim_alpha_cumprods = torch.from_numpy(self.ddim_alpha_cumprods)\n        self.ddim_alpha_cumprods_prev = torch.from_numpy(self.ddim_alpha_cumprods_prev)\n\n    def to(self, device):\n        self.ddim_timesteps = self.ddim_timesteps.to(device)\n        self.ddim_alpha_cumprods = self.ddim_alpha_cumprods.to(device)\n        self.ddim_alpha_cumprods_prev = self.ddim_alpha_cumprods_prev.to(device)\n        return self\n\n    def ddim_step(self, pred_x0, pred_noise, timestep_index, N_gen):\n        alpha_cumprod_prev = extract_into_tensor(self.ddim_alpha_cumprods_prev, timestep_index, pred_x0.shape, N_gen)\n        dir_xt = (1.0 - alpha_cumprod_prev).sqrt() * pred_noise\n        x_prev = alpha_cumprod_prev.sqrt() * pred_x0 + dir_xt\n        return x_prev\n\n\n@torch.no_grad()\ndef update_ema(target_params, source_params, rate=0.99):\n    \"\"\"\n    Update target parameters to be closer to those of source parameters using\n    an exponential moving average.\n\n    :param target_params: the target parameter sequence.\n    :param source_params: the source parameter sequence.\n    :param rate: the EMA rate (closer to 1 means slower).\n    \"\"\"\n\n    for targ, src in zip(target_params, source_params):\n        targ.detach().mul_(rate).add_(src, alpha=1 - rate)\n        \ndef to_rgb_image(maybe_rgba: Image.Image):\n    if maybe_rgba.mode == 'RGB':\n        return maybe_rgba\n    elif maybe_rgba.mode == 'RGBA':\n        rgba = maybe_rgba\n        img = numpy.random.randint(127, 128, size=[rgba.size[1], rgba.size[0], 3], dtype=numpy.uint8)\n        img = Image.fromarray(img, 'RGB')\n        img.paste(rgba, mask=rgba.getchannel('A'))\n        return img\n    else:\n        raise ValueError(\"Unsupported image type.\", maybe_rgba.mode)\n\n\nclass HunyuanPaintPipeline(StableDiffusionPipeline):\n\n    def __init__(\n        self,\n        vae: AutoencoderKL,\n        text_encoder: CLIPTextModel,\n        tokenizer: CLIPTokenizer,\n        unet: UNet2p5DConditionModel,\n        scheduler: KarrasDiffusionSchedulers,\n        feature_extractor: CLIPImageProcessor,\n        safety_checker=None,\n        use_torch_compile=False,\n    ):\n        DiffusionPipeline.__init__(self)\n\n        safety_checker = None\n        self.register_modules(\n            vae=torch.compile(vae) if use_torch_compile else vae,\n            text_encoder=text_encoder,\n            tokenizer=tokenizer,\n            unet=unet,\n            scheduler=scheduler,\n            safety_checker=safety_checker,\n            feature_extractor=torch.compile(feature_extractor) if use_torch_compile else feature_extractor,\n        )\n        self.solver = DDIMSolver(\n            scheduler.alphas_cumprod.numpy(),\n            timesteps=scheduler.config.num_train_timesteps,\n            ddim_timesteps=30,\n        ).to('cuda')\n        self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)\n        self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)\n        self.is_turbo = False\n\n    def set_turbo(self, is_turbo: bool):\n        self.is_turbo = is_turbo\n        \n    @torch.no_grad()\n    def encode_images(self, images):\n        B = images.shape[0]\n        images = rearrange(images, 'b n c h w -> (b n) c h w')\n\n        dtype = next(self.vae.parameters()).dtype\n        images = (images - 0.5) * 2.0\n        posterior = self.vae.encode(images.to(dtype)).latent_dist\n        latents = posterior.sample() * self.vae.config.scaling_factor\n\n        latents = rearrange(latents, '(b n) c h w -> b n c h w', b=B)\n        return latents\n\n    @torch.no_grad()\n    def __call__(\n        self,\n        image: Image.Image = None,\n        prompt=None,\n        negative_prompt='watermark, ugly, deformed, noisy, blurry, low contrast',\n        *args,\n        num_images_per_prompt: Optional[int] = 1,\n        guidance_scale=2.0,\n        output_type: Optional[str] = \"pil\",\n        width=512,\n        height=512,\n        num_inference_steps=28,\n        return_dict=True,\n        **cached_condition,\n    ):\n        device = self._execution_device\n\n        if image is None:\n            raise ValueError(\"Inputting embeddings not supported for this pipeline. Please pass an image.\")\n        assert not isinstance(image, torch.Tensor)\n\n        if not isinstance(image, List):\n            image = [image]\n            \n        image = [to_rgb_image(img) for img in image]\n\n        image_vae = [torch.tensor(np.array(img) / 255.0) for img in image]\n        image_vae = [img_vae.unsqueeze(0).permute(0, 3, 1, 2).unsqueeze(0) for img_vae in image_vae]\n        image_vae = torch.cat(image_vae, dim=1)\n        image_vae = image_vae.to(device=device, dtype=self.vae.dtype)\n\n        batch_size, N_ref = image_vae.shape[0], image_vae.shape[1]\n        assert batch_size == 1\n        assert num_images_per_prompt == 1\n\n        ref_latents = self.encode_images(image_vae)\n\n        def convert_pil_list_to_tensor(images):\n            bg_c = [1., 1., 1.]\n            images_tensor = []\n            for batch_imgs in images:\n                view_imgs = []\n                for pil_img in batch_imgs:\n                    img = numpy.asarray(pil_img, dtype=numpy.float32) / 255.\n                    if img.shape[2] > 3:\n                        alpha = img[:, :, 3:]\n                        img = img[:, :, :3] * alpha + bg_c * (1 - alpha)\n                    img = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0).contiguous().half().to(\"cuda\")\n                    view_imgs.append(img)\n                view_imgs = torch.cat(view_imgs, dim=0)\n                images_tensor.append(view_imgs.unsqueeze(0))\n\n            images_tensor = torch.cat(images_tensor, dim=0)\n            return images_tensor\n\n        if \"normal_imgs\" in cached_condition:\n\n            if isinstance(cached_condition[\"normal_imgs\"], List):\n                cached_condition[\"normal_imgs\"] = convert_pil_list_to_tensor(cached_condition[\"normal_imgs\"])\n\n            cached_condition['normal_imgs'] = self.encode_images(cached_condition[\"normal_imgs\"])\n\n        if \"position_imgs\" in cached_condition:\n\n            if isinstance(cached_condition[\"position_imgs\"], List):\n                cached_condition[\"position_imgs\"] = convert_pil_list_to_tensor(cached_condition[\"position_imgs\"])\n\n            cached_condition['position_maps'] = cached_condition['position_imgs']            \n            cached_condition[\"position_imgs\"] = self.encode_images(cached_condition[\"position_imgs\"])\n\n        if 'camera_info_gen' in cached_condition:\n            camera_info = cached_condition['camera_info_gen']  # B,N\n            if isinstance(camera_info, List):\n                camera_info = torch.tensor(camera_info)\n            camera_info = camera_info.to(device).to(torch.int64)\n            cached_condition['camera_info_gen'] = camera_info\n        if 'camera_info_ref' in cached_condition:\n            camera_info = cached_condition['camera_info_ref']  # B,N\n            if isinstance(camera_info, List):\n                camera_info = torch.tensor(camera_info)\n            camera_info = camera_info.to(device).to(torch.int64)\n            cached_condition['camera_info_ref'] = camera_info\n\n        cached_condition['ref_latents'] = ref_latents\n\n        if self.is_turbo:\n            if 'position_maps' in cached_condition:\n                cached_condition['position_attn_mask'] = (\n                    compute_multi_resolution_mask(cached_condition['position_maps'])\n                )\n                cached_condition['position_voxel_indices'] = (\n                    compute_multi_resolution_discrete_voxel_indice(cached_condition['position_maps'])\n                )\n            \n        if (guidance_scale > 1) and (not self.is_turbo):\n            negative_ref_latents = torch.zeros_like(cached_condition['ref_latents'])\n            cached_condition['ref_latents'] = torch.cat([negative_ref_latents, cached_condition['ref_latents']])\n            cached_condition['ref_scale'] = torch.as_tensor([0.0, 1.0]).to(cached_condition['ref_latents'])\n            if \"normal_imgs\" in cached_condition:\n                cached_condition['normal_imgs'] = torch.cat(\n                    (cached_condition['normal_imgs'], cached_condition['normal_imgs']))\n\n            if \"position_imgs\" in cached_condition:\n                cached_condition['position_imgs'] = torch.cat(\n                    (cached_condition['position_imgs'], cached_condition['position_imgs']))\n\n            if 'position_maps' in cached_condition:\n                cached_condition['position_maps'] = torch.cat(\n                    (cached_condition['position_maps'], cached_condition['position_maps']))\n\n            if 'camera_info_gen' in cached_condition:\n                cached_condition['camera_info_gen'] = torch.cat(\n                    (cached_condition['camera_info_gen'], cached_condition['camera_info_gen']))\n            if 'camera_info_ref' in cached_condition:\n                cached_condition['camera_info_ref'] = torch.cat(\n                    (cached_condition['camera_info_ref'], cached_condition['camera_info_ref']))\n\n        prompt_embeds = self.unet.learned_text_clip_gen.repeat(num_images_per_prompt, 1, 1)\n        negative_prompt_embeds = torch.zeros_like(prompt_embeds)\n\n        latents: torch.Tensor = self.denoise(\n            None,\n            *args,\n            cross_attention_kwargs=None,\n            guidance_scale=guidance_scale,\n            num_images_per_prompt=num_images_per_prompt,\n            prompt_embeds=prompt_embeds,\n            negative_prompt_embeds=negative_prompt_embeds,\n            num_inference_steps=num_inference_steps,\n            output_type='latent',\n            width=width,\n            height=height,\n            **cached_condition\n        ).images\n\n        if not output_type == \"latent\":\n            image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0]\n        else:\n            image = latents\n\n        image = self.image_processor.postprocess(image, output_type=output_type)\n        if not return_dict:\n            return (image,)\n\n        return ImagePipelineOutput(images=image)\n\n    def denoise(\n        self,\n        prompt: Union[str, List[str]] = None,\n        height: Optional[int] = None,\n        width: Optional[int] = None,\n        num_inference_steps: int = 50,\n        timesteps: List[int] = None,\n        sigmas: List[float] = None,\n        guidance_scale: float = 7.5,\n        negative_prompt: Optional[Union[str, List[str]]] = None,\n        num_images_per_prompt: Optional[int] = 1,\n        eta: float = 0.0,\n        generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,\n        latents: Optional[torch.Tensor] = None,\n        prompt_embeds: Optional[torch.Tensor] = None,\n        negative_prompt_embeds: Optional[torch.Tensor] = None,\n        ip_adapter_image: Optional[PipelineImageInput] = None,\n        ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None,\n        output_type: Optional[str] = \"pil\",\n        return_dict: bool = True,\n        cross_attention_kwargs: Optional[Dict[str, Any]] = None,\n        guidance_rescale: float = 0.0,\n        clip_skip: Optional[int] = None,\n        callback_on_step_end: Optional[\n            Union[Callable[[int, int, Dict], None], PipelineCallback, MultiPipelineCallbacks]\n        ] = None,\n        callback_on_step_end_tensor_inputs: List[str] = [\"latents\"],\n        **kwargs,\n    ):\n        r\"\"\"\n        The call function to the pipeline for generation.\n\n        Args:\n            prompt (`str` or `List[str]`, *optional*):\n                The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`.\n            height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):\n                The height in pixels of the generated image.\n            width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):\n                The width in pixels of the generated image.\n            num_inference_steps (`int`, *optional*, defaults to 50):\n                The number of denoising steps. More denoising steps usually lead to a higher quality image at the\n                expense of slower inference.\n            timesteps (`List[int]`, *optional*):\n                Custom timesteps to use for the denoising process with schedulers which support a `timesteps` argument\n                in their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is\n                passed will be used. Must be in descending order.\n            sigmas (`List[float]`, *optional*):\n                Custom sigmas to use for the denoising process with schedulers which support a `sigmas` argument in\n                their `set_timesteps` method. If not defined, the default behavior when `num_inference_steps` is passed\n                will be used.\n            guidance_scale (`float`, *optional*, defaults to 7.5):\n                A higher guidance scale value encourages the model to generate images closely linked to the text\n                `prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`.\n            negative_prompt (`str` or `List[str]`, *optional*):\n                The prompt or prompts to guide what to not include in image generation. If not defined, you need to\n                pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`).\n            num_images_per_prompt (`int`, *optional*, defaults to 1):\n                The number of images to generate per prompt.\n            eta (`float`, *optional*, defaults to 0.0):\n                Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies\n                to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers.\n            generator (`torch.Generator` or `List[torch.Generator]`, *optional*):\n                A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make\n                generation deterministic.\n            latents (`torch.Tensor`, *optional*):\n                Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image\n                generation. Can be used to tweak the same generation with different prompts. If not provided, a latents\n                tensor is generated by sampling using the supplied random `generator`.\n            prompt_embeds (`torch.Tensor`, *optional*):\n                Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not\n                provided, text embeddings are generated from the `prompt` input argument.\n            negative_prompt_embeds (`torch.Tensor`, *optional*):\n                Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If\n                not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument.\n            ip_adapter_image: (`PipelineImageInput`, *optional*): Optional image input to work with IP Adapters.\n            ip_adapter_image_embeds (`List[torch.Tensor]`, *optional*):\n                Pre-generated image embeddings for IP-Adapter. It should be a list of length same as number of\n                IP-adapters. Each element should be a tensor of shape `(batch_size, num_images, emb_dim)`. It should\n                contain the negative image embedding if `do_classifier_free_guidance` is set to `True`. If not\n                provided, embeddings are computed from the `ip_adapter_image` input argument.\n            output_type (`str`, *optional*, defaults to `\"pil\"`):\n                The output format of the generated image. Choose between `PIL.Image` or `np.array`.\n            return_dict (`bool`, *optional*, defaults to `True`):\n                Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a\n                plain tuple.\n            cross_attention_kwargs (`dict`, *optional*):\n                A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in\n                [`self.processor`]\n                (https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).\n            guidance_rescale (`float`, *optional*, defaults to 0.0):\n                Guidance rescale factor from [Common Diffusion Noise Schedules and Sample Steps are\n                Flawed](https://arxiv.org/pdf/2305.08891.pdf). Guidance rescale factor should fix overexposure when\n                using zero terminal SNR.\n            clip_skip (`int`, *optional*):\n                Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that\n                the output of the pre-final layer will be used for computing the prompt embeddings.\n            callback_on_step_end (`Callable`, `PipelineCallback`, `MultiPipelineCallbacks`, *optional*):\n                A function or a subclass of `PipelineCallback` or `MultiPipelineCallbacks` that is called at the end of\n                each denoising step during the inference. with the following arguments: `callback_on_step_end(self:\n                DiffusionPipeline, step: int, timestep: int, callback_kwargs: Dict)`. `callback_kwargs` will include a\n                list of all tensors as specified by `callback_on_step_end_tensor_inputs`.\n            callback_on_step_end_tensor_inputs (`List`, *optional*):\n                The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list\n                will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the\n                `._callback_tensor_inputs` attribute of your pipeline class.\n\n        Examples:\n\n        Returns:\n            [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`:\n                If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] is returned,\n                otherwise a `tuple` is returned where the first element is a list with the generated images and the\n                second element is a list of `bool`s indicating whether the corresponding generated image contains\n                \"not-safe-for-work\" (nsfw) content.\n        \"\"\"\n\n        callback = kwargs.pop(\"callback\", None)\n        callback_steps = kwargs.pop(\"callback_steps\", None)\n\n        if callback is not None:\n            deprecate(\n                \"callback\",\n                \"1.0.0\",\n                \"Passing `callback` as an input argument to `__call__` is deprecated,\",\n                \"consider using `callback_on_step_end`\",\n            )\n        if callback_steps is not None:\n            deprecate(\n                \"callback_steps\",\n                \"1.0.0\",\n                \"Passing `callback_steps` as an input argument to `__call__` is deprecated,\",\n                \"consider using `callback_on_step_end`\",\n            )\n\n        if isinstance(callback_on_step_end, (PipelineCallback, MultiPipelineCallbacks)):\n            callback_on_step_end_tensor_inputs = callback_on_step_end.tensor_inputs\n\n        # 0. Default height and width to unet\n        height = height or self.unet.config.sample_size * self.vae_scale_factor\n        width = width or self.unet.config.sample_size * self.vae_scale_factor\n        # to deal with lora scaling and other possible forward hooks\n\n        # 1. Check inputs. Raise error if not correct\n        self.check_inputs(\n            prompt,\n            height,\n            width,\n            callback_steps,\n            negative_prompt,\n            prompt_embeds,\n            negative_prompt_embeds,\n            ip_adapter_image,\n            ip_adapter_image_embeds,\n            callback_on_step_end_tensor_inputs,\n        )\n\n        self._guidance_scale = guidance_scale\n        self._guidance_rescale = guidance_rescale\n        self._clip_skip = clip_skip\n        self._cross_attention_kwargs = cross_attention_kwargs\n        self._interrupt = False\n\n        # 2. Define call parameters\n        if prompt is not None and isinstance(prompt, str):\n            batch_size = 1\n        elif prompt is not None and isinstance(prompt, list):\n            batch_size = len(prompt)\n        else:\n            batch_size = prompt_embeds.shape[0]\n\n        device = self._execution_device\n\n        # 3. Encode input prompt\n        lora_scale = (\n            self.cross_attention_kwargs.get(\"scale\", None) if self.cross_attention_kwargs is not None else None\n        )\n\n        prompt_embeds, negative_prompt_embeds = self.encode_prompt(\n            prompt,\n            device,\n            num_images_per_prompt,\n            self.do_classifier_free_guidance if self.is_turbo else False,\n            negative_prompt,\n            prompt_embeds=prompt_embeds,\n            negative_prompt_embeds=negative_prompt_embeds,\n            lora_scale=lora_scale,\n            clip_skip=self.clip_skip,\n        )\n\n        # For classifier free guidance, we need to do two forward passes.\n        # Here we concatenate the unconditional and text embeddings into a single batch\n        # to avoid doing two forward passes\n        if (self.do_classifier_free_guidance) and (not self.is_turbo):\n            prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])\n\n        if ip_adapter_image is not None or ip_adapter_image_embeds is not None:\n            image_embeds = self.prepare_ip_adapter_image_embeds(\n                ip_adapter_image,\n                ip_adapter_image_embeds,\n                device,\n                batch_size * num_images_per_prompt,\n                self.do_classifier_free_guidance if self.is_turbo else False,\n            )\n\n        # 4. Prepare \n        if self.is_turbo:\n            bsz = 3\n            N_gen = 15\n            index = torch.range(29, 0, -bsz, device='cuda').long()\n            timesteps = self.solver.ddim_timesteps[index]\n            self.scheduler.set_timesteps(timesteps=timesteps.cpu(), device='cuda')\n        else:\n            timesteps, num_inference_steps = retrieve_timesteps(\n                self.scheduler, num_inference_steps, device, timesteps, sigmas\n            )\n            \n        assert num_images_per_prompt == 1\n        # 5. Prepare latent variables\n        num_channels_latents = self.unet.config.in_channels\n        latents = self.prepare_latents(\n            batch_size * kwargs['num_in_batch'],  # num_images_per_prompt,\n            num_channels_latents,\n            height,\n            width,\n            prompt_embeds.dtype,\n            device,\n            generator,\n            latents,\n        )\n\n        # 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline\n        extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)\n\n        # 6.1 Add image embeds for IP-Adapter\n        added_cond_kwargs = (\n            {\"image_embeds\": image_embeds}\n            if (ip_adapter_image is not None or ip_adapter_image_embeds is not None)\n            else None\n        )\n\n        # 6.2 Optionally get Guidance Scale Embedding\n        timestep_cond = None\n        if self.unet.config.time_cond_proj_dim is not None:\n            guidance_scale_tensor = torch.tensor(self.guidance_scale - 1).repeat(batch_size * num_images_per_prompt)\n            timestep_cond = self.get_guidance_scale_embedding(\n                guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim\n            ).to(device=device, dtype=latents.dtype)\n\n        # 7. Denoising loop\n        num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order\n        self._num_timesteps = len(timesteps)\n        with self.progress_bar(total=num_inference_steps) as progress_bar:\n            for i, t in enumerate(timesteps):\n                if self.interrupt:\n                    continue\n\n                # expand the latents if we are doing classifier free guidance\n                latents = rearrange(latents, '(b n) c h w -> b n c h w', n=kwargs['num_in_batch'])\n                latent_model_input = (\n                    torch.cat([latents] * 2) \n                    if ((self.do_classifier_free_guidance) and (not self.is_turbo)) \n                    else latents\n                )\n                latent_model_input = rearrange(latent_model_input, 'b n c h w -> (b n) c h w')\n                latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)\n                latent_model_input = rearrange(latent_model_input, '(b n) c h w ->b n c h w', n=kwargs['num_in_batch'])\n\n                # predict the noise residual\n\n                noise_pred = self.unet(\n                    latent_model_input,\n                    t,\n                    encoder_hidden_states=prompt_embeds,\n                    timestep_cond=timestep_cond,\n                    cross_attention_kwargs=self.cross_attention_kwargs,\n                    added_cond_kwargs=added_cond_kwargs,\n                    return_dict=False, **kwargs\n                )[0]\n                latents = rearrange(latents, 'b n c h w -> (b n) c h w')\n                # perform guidance\n                if (self.do_classifier_free_guidance) and (not self.is_turbo):\n                    noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)\n                    noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_text - noise_pred_uncond)\n\n                if (self.do_classifier_free_guidance) and (self.guidance_rescale > 0.0) and (not self.is_turbo):\n                    # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf\n                    noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=self.guidance_rescale)\n\n                # compute the previous noisy sample x_t -> x_t-1\n                latents = \\\n                    self.scheduler.step(noise_pred, t, latents[:, :num_channels_latents, :, :], **extra_step_kwargs,\n                                        return_dict=False)[0]\n\n                if callback_on_step_end is not None:\n                    callback_kwargs = {}\n                    for k in callback_on_step_end_tensor_inputs:\n                        callback_kwargs[k] = locals()[k]\n                    callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)\n\n                    latents = callback_outputs.pop(\"latents\", latents)\n                    prompt_embeds = callback_outputs.pop(\"prompt_embeds\", prompt_embeds)\n                    negative_prompt_embeds = callback_outputs.pop(\"negative_prompt_embeds\", negative_prompt_embeds)\n\n                # call the callback, if provided\n                if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):\n                    progress_bar.update()\n                    if callback is not None and i % callback_steps == 0:\n                        step_idx = i // getattr(self.scheduler, \"order\", 1)\n                        callback(step_idx, t, latents)\n\n        if not output_type == \"latent\":\n            image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False, generator=generator)[\n                0\n            ]\n            image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype)\n        else:\n            image = latents\n            has_nsfw_concept = None\n\n        if has_nsfw_concept is None:\n            do_denormalize = [True] * image.shape[0]\n        else:\n            do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept]\n\n        image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize)\n\n        # Offload all models\n        self.maybe_free_model_hooks()\n\n        if not return_dict:\n            return (image, has_nsfw_concept)\n\n        return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)\n"
  },
  {
    "path": "hy3dgen/texgen/hunyuanpaint/unet/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT."
  },
  {
    "path": "hy3dgen/texgen/hunyuanpaint/unet/modules.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport copy\nimport json\nimport os\nfrom typing import Any, Dict, List, Optional, Tuple, Union\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom diffusers.models import UNet2DConditionModel\nfrom diffusers.models.attention_processor import Attention\nfrom diffusers.models.transformers.transformer_2d import BasicTransformerBlock\nfrom einops import rearrange\n\n\ndef _chunked_feed_forward(ff: nn.Module, hidden_states: torch.Tensor, chunk_dim: int, chunk_size: int):\n    # \"feed_forward_chunk_size\" can be used to save memory\n    if hidden_states.shape[chunk_dim] % chunk_size != 0:\n        raise ValueError(\n            f\"`hidden_states` dimension to be chunked: {hidden_states.shape[chunk_dim]}\"\n            f\"has to be divisible by chunk size: {chunk_size}.\"\n            f\" Make sure to set an appropriate `chunk_size` when calling `unet.enable_forward_chunking`.\"\n        )\n\n    num_chunks = hidden_states.shape[chunk_dim] // chunk_size\n    ff_output = torch.cat(\n        [ff(hid_slice) for hid_slice in hidden_states.chunk(num_chunks, dim=chunk_dim)],\n        dim=chunk_dim,\n    )\n    return ff_output\n\n\nclass Basic2p5DTransformerBlock(torch.nn.Module):\n    def __init__(self, transformer: BasicTransformerBlock,layer_name,use_ma=True,use_ra=True,is_turbo=False) -> None:\n        super().__init__()\n        self.transformer = transformer\n        self.layer_name = layer_name\n        self.use_ma = use_ma\n        self.use_ra = use_ra\n        self.is_turbo = is_turbo\n\n        # multiview attn\n        if self.use_ma:\n            self.attn_multiview = Attention(\n                query_dim=self.dim,\n                heads=self.num_attention_heads,\n                dim_head=self.attention_head_dim,\n                dropout=self.dropout,\n                bias=self.attention_bias,\n                cross_attention_dim=None,\n                upcast_attention=self.attn1.upcast_attention,\n                out_bias=True,\n            )\n\n        # ref attn\n        if self.use_ra:\n            self.attn_refview = Attention(\n                query_dim=self.dim,\n                heads=self.num_attention_heads,\n                dim_head=self.attention_head_dim,\n                dropout=self.dropout,\n                bias=self.attention_bias,\n                cross_attention_dim=None,\n                upcast_attention=self.attn1.upcast_attention,\n                out_bias=True,\n            )\n        if self.is_turbo:\n            self._initialize_attn_weights()\n\n    def _initialize_attn_weights(self):\n\n        if self.use_ma:\n            self.attn_multiview.load_state_dict(self.attn1.state_dict()) \n            with torch.no_grad():\n                for layer in self.attn_multiview.to_out:\n                    for param in layer.parameters():\n                        param.zero_()\n        if self.use_ra:\n            self.attn_refview.load_state_dict(self.attn1.state_dict()) \n            with torch.no_grad():\n                for layer in self.attn_refview.to_out:\n                    for param in layer.parameters():\n                        param.zero_()\n\n    def __getattr__(self, name: str):\n        try:\n            return super().__getattr__(name)\n        except AttributeError:\n            return getattr(self.transformer, name)\n\n    def forward(\n        self,\n        hidden_states: torch.Tensor,\n        attention_mask: Optional[torch.Tensor] = None,\n        encoder_hidden_states: Optional[torch.Tensor] = None,\n        encoder_attention_mask: Optional[torch.Tensor] = None,\n        timestep: Optional[torch.LongTensor] = None,\n        cross_attention_kwargs: Dict[str, Any] = None,\n        class_labels: Optional[torch.LongTensor] = None,\n        added_cond_kwargs: Optional[Dict[str, torch.Tensor]] = None,\n    ) -> torch.Tensor:\n\n        # Notice that normalization is always applied before the real computation in the following blocks.\n        # 0. Self-Attention\n        batch_size = hidden_states.shape[0]\n\n        cross_attention_kwargs = cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}\n        num_in_batch = cross_attention_kwargs.pop('num_in_batch', 1)\n        mode = cross_attention_kwargs.pop('mode', None)\n        if not self.is_turbo:\n            mva_scale = cross_attention_kwargs.pop('mva_scale', 1.0)\n            ref_scale = cross_attention_kwargs.pop('ref_scale', 1.0)\n        else:\n            position_attn_mask = cross_attention_kwargs.pop(\"position_attn_mask\", None)\n            position_voxel_indices = cross_attention_kwargs.pop(\"position_voxel_indices\", None)\n            mva_scale = 1.0\n            ref_scale = 1.0\n            \n        condition_embed_dict = cross_attention_kwargs.pop(\"condition_embed_dict\", None)\n\n        if self.norm_type == \"ada_norm\":\n            norm_hidden_states = self.norm1(hidden_states, timestep)\n        elif self.norm_type == \"ada_norm_zero\":\n            norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(\n                hidden_states, timestep, class_labels, hidden_dtype=hidden_states.dtype\n            )\n        elif self.norm_type in [\"layer_norm\", \"layer_norm_i2vgen\"]:\n            norm_hidden_states = self.norm1(hidden_states)\n        elif self.norm_type == \"ada_norm_continuous\":\n            norm_hidden_states = self.norm1(hidden_states, added_cond_kwargs[\"pooled_text_emb\"])\n        elif self.norm_type == \"ada_norm_single\":\n            shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (\n                self.scale_shift_table[None] + timestep.reshape(batch_size, 6, -1)\n            ).chunk(6, dim=1)\n            norm_hidden_states = self.norm1(hidden_states)\n            norm_hidden_states = norm_hidden_states * (1 + scale_msa) + shift_msa\n        else:\n            raise ValueError(\"Incorrect norm used\")\n\n        if self.pos_embed is not None:\n            norm_hidden_states = self.pos_embed(norm_hidden_states)\n\n        # 1. Prepare GLIGEN inputs\n        cross_attention_kwargs = cross_attention_kwargs.copy() if cross_attention_kwargs is not None else {}\n        gligen_kwargs = cross_attention_kwargs.pop(\"gligen\", None)\n\n        attn_output = self.attn1(\n            norm_hidden_states,\n            encoder_hidden_states=encoder_hidden_states if self.only_cross_attention else None,\n            attention_mask=attention_mask,\n            **cross_attention_kwargs,\n        )\n\n        if self.norm_type == \"ada_norm_zero\":\n            attn_output = gate_msa.unsqueeze(1) * attn_output\n        elif self.norm_type == \"ada_norm_single\":\n            attn_output = gate_msa * attn_output\n\n        hidden_states = attn_output + hidden_states\n        if hidden_states.ndim == 4:\n            hidden_states = hidden_states.squeeze(1)\n\n        # 1.2 Reference Attention\n        if 'w' in mode:\n            condition_embed_dict[self.layer_name] = rearrange(\n                norm_hidden_states, '(b n) l c -> b (n l) c',\n                n=num_in_batch\n            )  # B, (N L), C\n\n        if 'r' in mode and self.use_ra:\n            condition_embed = condition_embed_dict[self.layer_name].unsqueeze(1).repeat(1, num_in_batch, 1,\n                                                                                        1)  # B N L C\n            condition_embed = rearrange(condition_embed, 'b n l c -> (b n) l c')\n\n            attn_output = self.attn_refview(\n                norm_hidden_states,\n                encoder_hidden_states=condition_embed,\n                attention_mask=None,\n                **cross_attention_kwargs\n            )\n            if not self.is_turbo:\n                ref_scale_timing = ref_scale\n                if isinstance(ref_scale, torch.Tensor):\n                    ref_scale_timing = ref_scale.unsqueeze(1).repeat(1, num_in_batch).view(-1)\n                    for _ in range(attn_output.ndim - 1):\n                        ref_scale_timing = ref_scale_timing.unsqueeze(-1)\n                        \n            hidden_states = ref_scale_timing * attn_output + hidden_states\n\n            if hidden_states.ndim == 4:\n                hidden_states = hidden_states.squeeze(1)\n\n        # 1.3 Multiview Attention\n        if num_in_batch > 1 and self.use_ma:\n            multivew_hidden_states = rearrange(norm_hidden_states, '(b n) l c -> b (n l) c', n=num_in_batch)\n\n            if self.is_turbo:\n                position_mask = None\n                if position_attn_mask is not None:\n                    if multivew_hidden_states.shape[1] in position_attn_mask:\n                        position_mask = position_attn_mask[multivew_hidden_states.shape[1]]\n                position_indices = None\n                if position_voxel_indices is not None:\n                    if multivew_hidden_states.shape[1] in position_voxel_indices:\n                        position_indices = position_voxel_indices[multivew_hidden_states.shape[1]]\n                attn_output = self.attn_multiview(\n                    multivew_hidden_states,\n                    encoder_hidden_states=multivew_hidden_states,\n                    attention_mask=position_mask,\n                    position_indices=position_indices,\n                    **cross_attention_kwargs\n                )\n            else:\n                attn_output = self.attn_multiview(\n                    multivew_hidden_states,\n                    encoder_hidden_states=multivew_hidden_states,\n                    **cross_attention_kwargs\n                )\n\n            attn_output = rearrange(attn_output, 'b (n l) c -> (b n) l c', n=num_in_batch)\n            \n            hidden_states = mva_scale * attn_output + hidden_states\n            if hidden_states.ndim == 4:\n                hidden_states = hidden_states.squeeze(1)\n\n        # 1.2 GLIGEN Control\n        if gligen_kwargs is not None:\n            hidden_states = self.fuser(hidden_states, gligen_kwargs[\"objs\"])\n\n        # 3. Cross-Attention\n        if self.attn2 is not None:\n            if self.norm_type == \"ada_norm\":\n                norm_hidden_states = self.norm2(hidden_states, timestep)\n            elif self.norm_type in [\"ada_norm_zero\", \"layer_norm\", \"layer_norm_i2vgen\"]:\n                norm_hidden_states = self.norm2(hidden_states)\n            elif self.norm_type == \"ada_norm_single\":\n                # For PixArt norm2 isn't applied here:\n                # https://github.com/PixArt-alpha/PixArt-alpha/blob/0f55e922376d8b797edd44d25d0e7464b260dcab/diffusion/model/nets/PixArtMS.py#L70C1-L76C103\n                norm_hidden_states = hidden_states\n            elif self.norm_type == \"ada_norm_continuous\":\n                norm_hidden_states = self.norm2(hidden_states, added_cond_kwargs[\"pooled_text_emb\"])\n            else:\n                raise ValueError(\"Incorrect norm\")\n\n            if self.pos_embed is not None and self.norm_type != \"ada_norm_single\":\n                norm_hidden_states = self.pos_embed(norm_hidden_states)\n\n            attn_output = self.attn2(\n                norm_hidden_states,\n                encoder_hidden_states=encoder_hidden_states,\n                attention_mask=encoder_attention_mask,\n                **cross_attention_kwargs,\n            )\n\n            hidden_states = attn_output + hidden_states\n\n        # 4. Feed-forward\n        # i2vgen doesn't have this norm 🤷‍♂️\n        if self.norm_type == \"ada_norm_continuous\":\n            norm_hidden_states = self.norm3(hidden_states, added_cond_kwargs[\"pooled_text_emb\"])\n        elif not self.norm_type == \"ada_norm_single\":\n            norm_hidden_states = self.norm3(hidden_states)\n\n        if self.norm_type == \"ada_norm_zero\":\n            norm_hidden_states = norm_hidden_states * (1 + scale_mlp[:, None]) + shift_mlp[:, None]\n\n        if self.norm_type == \"ada_norm_single\":\n            norm_hidden_states = self.norm2(hidden_states)\n            norm_hidden_states = norm_hidden_states * (1 + scale_mlp) + shift_mlp\n\n        if self._chunk_size is not None:\n            # \"feed_forward_chunk_size\" can be used to save memory\n            ff_output = _chunked_feed_forward(self.ff, norm_hidden_states, self._chunk_dim, self._chunk_size)\n        else:\n            ff_output = self.ff(norm_hidden_states)\n\n        if self.norm_type == \"ada_norm_zero\":\n            ff_output = gate_mlp.unsqueeze(1) * ff_output\n        elif self.norm_type == \"ada_norm_single\":\n            ff_output = gate_mlp * ff_output\n\n        hidden_states = ff_output + hidden_states\n        if hidden_states.ndim == 4:\n            hidden_states = hidden_states.squeeze(1)\n\n        return hidden_states\n\n@torch.no_grad()\ndef compute_voxel_grid_mask(position, grid_resolution=8):\n\n    position = position.half()    \n    B,N,_,H,W = position.shape\n    assert H%grid_resolution==0 and W%grid_resolution==0\n\n    valid_mask = (position != 1).all(dim=2, keepdim=True)\n    valid_mask = valid_mask.expand_as(position)\n    position[valid_mask==False] = 0\n\n    \n    position = rearrange(\n        position,\n        'b n c (num_h grid_h) (num_w grid_w) -> b n num_h num_w c grid_h grid_w', \n        num_h=grid_resolution, num_w=grid_resolution\n    )\n    valid_mask = rearrange(\n        valid_mask, \n        'b n c (num_h grid_h) (num_w grid_w) -> b n num_h num_w c grid_h grid_w', \n        num_h=grid_resolution, num_w=grid_resolution\n    )\n\n    grid_position = position.sum(dim=(-2, -1))\n    count_masked = valid_mask.sum(dim=(-2, -1))\n\n    grid_position = grid_position / count_masked.clamp(min=1)\n    grid_position[count_masked<5] = 0\n\n    grid_position = grid_position.permute(0,1,4,2,3)\n    grid_position = rearrange(grid_position, 'b n c h w -> b n (h w) c')\n\n    grid_position_expanded_1 = grid_position.unsqueeze(2).unsqueeze(4)  # 形状变为 B, N, 1, L, 1, 3\n    grid_position_expanded_2 = grid_position.unsqueeze(1).unsqueeze(3)  # 形状变为 B, 1, N, 1, L, 3\n\n    # 计算欧氏距离\n    distances = torch.norm(grid_position_expanded_1 - grid_position_expanded_2, dim=-1)  # 形状为 B, N, N, L, L\n\n    weights = distances\n    grid_distance = 1.73/grid_resolution\n    \n    #weights = weights*-32\n    #weights = weights.clamp(min=-10000.0)\n    \n    weights = weights< grid_distance\n\n    return weights\n    \ndef compute_multi_resolution_mask(position_maps, grid_resolutions=[32, 16, 8]):\n    position_attn_mask = {}\n    with torch.no_grad():\n        for grid_resolution in grid_resolutions:\n            position_mask = compute_voxel_grid_mask(position_maps, grid_resolution)\n            position_mask = rearrange(position_mask, 'b ni nj li lj -> b (ni li) (nj lj)')\n            position_attn_mask[position_mask.shape[1]] = position_mask\n    return position_attn_mask\n\n@torch.no_grad()\ndef compute_discrete_voxel_indice(position, grid_resolution=8, voxel_resolution=128):\n\n    position = position.half()    \n    B,N,_,H,W = position.shape\n    assert H%grid_resolution==0 and W%grid_resolution==0\n\n    valid_mask = (position != 1).all(dim=2, keepdim=True)\n    valid_mask = valid_mask.expand_as(position)\n    position[valid_mask==False] = 0\n    \n    position = rearrange(\n        position, \n        'b n c (num_h grid_h) (num_w grid_w) -> b n num_h num_w c grid_h grid_w', \n        num_h=grid_resolution, num_w=grid_resolution\n    )\n    valid_mask = rearrange(\n        valid_mask, \n        'b n c (num_h grid_h) (num_w grid_w) -> b n num_h num_w c grid_h grid_w', \n        num_h=grid_resolution, num_w=grid_resolution\n    )\n\n    grid_position = position.sum(dim=(-2, -1))\n    count_masked = valid_mask.sum(dim=(-2, -1))\n\n    grid_position = grid_position / count_masked.clamp(min=1)\n    grid_position[count_masked<5] = 0\n\n    grid_position = grid_position.permute(0,1,4,2,3).clamp(0, 1) # B N C H W\n    voxel_indices = grid_position * (voxel_resolution - 1)\n    voxel_indices = torch.round(voxel_indices).long()\n    return voxel_indices\n    \ndef compute_multi_resolution_discrete_voxel_indice(\n    position_maps, \n    grid_resolutions=[64, 32, 16, 8], \n    voxel_resolutions=[512, 256, 128, 64]\n):\n    voxel_indices = {}\n    with torch.no_grad():\n        for grid_resolution, voxel_resolution in zip(grid_resolutions, voxel_resolutions):\n            voxel_indice = compute_discrete_voxel_indice(position_maps, grid_resolution, voxel_resolution)\n            voxel_indice = rearrange(voxel_indice, 'b n c h w -> b (n h w) c')\n            voxel_indices[voxel_indice.shape[1]] = {'voxel_indices':voxel_indice, 'voxel_resolution':voxel_resolution}\n    return voxel_indices\n\nclass UNet2p5DConditionModel(torch.nn.Module):\n    def __init__(self, unet: UNet2DConditionModel) -> None:\n        super().__init__()\n        self.unet = unet\n\n        self.use_ma = True\n        self.use_ra = True\n        self.use_camera_embedding = True\n        self.use_dual_stream = True\n        self.is_turbo = False\n\n        if self.use_dual_stream:\n            self.unet_dual = copy.deepcopy(unet)\n            self.init_attention(self.unet_dual)\n        self.init_attention(self.unet, use_ma=self.use_ma, use_ra=self.use_ra, is_turbo=self.is_turbo)\n        self.init_condition()\n        self.init_camera_embedding()\n\n    @staticmethod\n    def from_pretrained(pretrained_model_name_or_path, **kwargs):\n        torch_dtype = kwargs.pop('torch_dtype', torch.float32)\n        config_path = os.path.join(pretrained_model_name_or_path, 'config.json')\n        unet_ckpt_path = os.path.join(pretrained_model_name_or_path, 'diffusion_pytorch_model.bin')\n        with open(config_path, 'r', encoding='utf-8') as file:\n            config = json.load(file)\n        unet = UNet2DConditionModel(**config)\n        unet = UNet2p5DConditionModel(unet)\n        unet_ckpt = torch.load(unet_ckpt_path, map_location='cpu', weights_only=True)\n        unet.load_state_dict(unet_ckpt, strict=True)\n        unet = unet.to(torch_dtype)\n        return unet\n\n    def init_condition(self):\n        self.unet.conv_in = torch.nn.Conv2d(\n            12,\n            self.unet.conv_in.out_channels,\n            kernel_size=self.unet.conv_in.kernel_size,\n            stride=self.unet.conv_in.stride,\n            padding=self.unet.conv_in.padding,\n            dilation=self.unet.conv_in.dilation,\n            groups=self.unet.conv_in.groups,\n            bias=self.unet.conv_in.bias is not None)\n\n        self.unet.learned_text_clip_gen = nn.Parameter(torch.randn(1, 77, 1024))\n        self.unet.learned_text_clip_ref = nn.Parameter(torch.randn(1, 77, 1024))\n\n    def init_camera_embedding(self):\n\n        if self.use_camera_embedding:\n            time_embed_dim = 1280\n            self.max_num_ref_image = 5\n            self.max_num_gen_image = 12 * 3 + 4 * 2\n            self.unet.class_embedding = nn.Embedding(self.max_num_ref_image + self.max_num_gen_image, time_embed_dim)\n\n    def init_attention(self, unet, use_ma=False, use_ra=False, is_turbo=False):\n\n        for down_block_i, down_block in enumerate(unet.down_blocks):\n            if hasattr(down_block, \"has_cross_attention\") and down_block.has_cross_attention:\n                for attn_i, attn in enumerate(down_block.attentions):\n                    for transformer_i, transformer in enumerate(attn.transformer_blocks):\n                        if isinstance(transformer, BasicTransformerBlock):\n                            attn.transformer_blocks[transformer_i] = Basic2p5DTransformerBlock(\n                                transformer,\n                                f'down_{down_block_i}_{attn_i}_{transformer_i}',\n                                use_ma, use_ra, is_turbo\n                            )\n\n        if hasattr(unet.mid_block, \"has_cross_attention\") and unet.mid_block.has_cross_attention:\n            for attn_i, attn in enumerate(unet.mid_block.attentions):\n                for transformer_i, transformer in enumerate(attn.transformer_blocks):\n                    if isinstance(transformer, BasicTransformerBlock):\n                        attn.transformer_blocks[transformer_i] = Basic2p5DTransformerBlock(\n                            transformer,\n                            f'mid_{attn_i}_{transformer_i}',\n                            use_ma, use_ra, is_turbo\n                        )\n\n        for up_block_i, up_block in enumerate(unet.up_blocks):\n            if hasattr(up_block, \"has_cross_attention\") and up_block.has_cross_attention:\n                for attn_i, attn in enumerate(up_block.attentions):\n                    for transformer_i, transformer in enumerate(attn.transformer_blocks):\n                        if isinstance(transformer, BasicTransformerBlock):\n                            attn.transformer_blocks[transformer_i] = Basic2p5DTransformerBlock(\n                                transformer,\n                                f'up_{up_block_i}_{attn_i}_{transformer_i}',\n                                use_ma, use_ra, is_turbo\n                            )\n\n    def __getattr__(self, name: str):\n        try:\n            return super().__getattr__(name)\n        except AttributeError:\n            return getattr(self.unet, name)\n\n    def forward(\n        self, sample, timestep, encoder_hidden_states,\n        *args, down_intrablock_additional_residuals=None,\n        down_block_res_samples=None, mid_block_res_sample=None,\n        **cached_condition,\n    ):\n        B, N_gen, _, H, W = sample.shape\n        assert H == W\n\n        if self.use_camera_embedding:\n            camera_info_gen = cached_condition['camera_info_gen'] + self.max_num_ref_image\n            camera_info_gen = rearrange(camera_info_gen, 'b n -> (b n)')\n        else:\n            camera_info_gen = None\n\n        sample = [sample]\n        if 'normal_imgs' in cached_condition:\n            sample.append(cached_condition[\"normal_imgs\"])\n        if 'position_imgs' in cached_condition:\n            sample.append(cached_condition[\"position_imgs\"])\n        sample = torch.cat(sample, dim=2)\n\n        sample = rearrange(sample, 'b n c h w -> (b n) c h w')\n\n        encoder_hidden_states_gen = encoder_hidden_states.unsqueeze(1).repeat(1, N_gen, 1, 1)\n        encoder_hidden_states_gen = rearrange(encoder_hidden_states_gen, 'b n l c -> (b n) l c')\n\n        if self.use_ra:\n            if 'condition_embed_dict' in cached_condition:\n                condition_embed_dict = cached_condition['condition_embed_dict']\n            else:\n                condition_embed_dict = {}\n                ref_latents = cached_condition['ref_latents']\n                N_ref = ref_latents.shape[1]\n                if self.use_camera_embedding:\n                    camera_info_ref = cached_condition['camera_info_ref']\n                    camera_info_ref = rearrange(camera_info_ref, 'b n -> (b n)')\n                else:\n                    camera_info_ref = None\n\n                ref_latents = rearrange(ref_latents, 'b n c h w -> (b n) c h w')\n\n                encoder_hidden_states_ref = self.unet.learned_text_clip_ref.unsqueeze(1).repeat(B, N_ref, 1, 1)\n                encoder_hidden_states_ref = rearrange(encoder_hidden_states_ref, 'b n l c -> (b n) l c')\n\n                noisy_ref_latents = ref_latents\n                timestep_ref = 0\n\n                if self.use_dual_stream:\n                    unet_ref = self.unet_dual\n                else:\n                    unet_ref = self.unet\n                unet_ref(\n                    noisy_ref_latents, timestep_ref,\n                    encoder_hidden_states=encoder_hidden_states_ref,\n                    class_labels=camera_info_ref,\n                    # **kwargs\n                    return_dict=False,\n                    cross_attention_kwargs={\n                        'mode': 'w', 'num_in_batch': N_ref,\n                        'condition_embed_dict': condition_embed_dict},\n                )\n                cached_condition['condition_embed_dict'] = condition_embed_dict\n        else:\n            condition_embed_dict = None\n\n        mva_scale = cached_condition.get('mva_scale', 1.0)\n        ref_scale = cached_condition.get('ref_scale', 1.0)\n\n        if self.is_turbo:\n            cross_attention_kwargs_ = {\n                'mode': 'r', 'num_in_batch': N_gen,\n                'condition_embed_dict': condition_embed_dict,\n                'position_attn_mask':position_attn_mask, \n                'position_voxel_indices':position_voxel_indices,\n                'mva_scale': mva_scale,\n                'ref_scale': ref_scale,\n            }\n        else:\n            cross_attention_kwargs_ = {\n                'mode': 'r', 'num_in_batch': N_gen,\n                'condition_embed_dict': condition_embed_dict,\n                'mva_scale': mva_scale,\n                'ref_scale': ref_scale,\n            }\n        return self.unet(\n            sample, timestep,\n            encoder_hidden_states_gen, *args,\n            class_labels=camera_info_gen,\n            down_intrablock_additional_residuals=[\n                sample.to(dtype=self.unet.dtype) for sample in down_intrablock_additional_residuals\n            ] if down_intrablock_additional_residuals is not None else None,\n            down_block_additional_residuals=[\n                sample.to(dtype=self.unet.dtype) for sample in down_block_res_samples\n            ] if down_block_res_samples is not None else None,\n            mid_block_additional_residual=(\n                mid_block_res_sample.to(dtype=self.unet.dtype)\n                if mid_block_res_sample is not None else None\n            ),\n            return_dict=False,\n            cross_attention_kwargs=cross_attention_kwargs_,\n        )\n"
  },
  {
    "path": "hy3dgen/texgen/pipelines.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nimport logging\nimport numpy as np\nimport os\nimport torch\nfrom PIL import Image\nfrom typing import List, Union, Optional\n\n\nfrom .differentiable_renderer.mesh_render import MeshRender\nfrom .utils.dehighlight_utils import Light_Shadow_Remover\nfrom .utils.multiview_utils import Multiview_Diffusion_Net\nfrom .utils.imagesuper_utils import Image_Super_Net\nfrom .utils.uv_warp_utils import mesh_uv_wrap\n\nlogger = logging.getLogger(__name__)\n\n\nclass Hunyuan3DTexGenConfig:\n\n    def __init__(self, light_remover_ckpt_path, multiview_ckpt_path, subfolder_name):\n        self.device = 'cuda'\n        self.light_remover_ckpt_path = light_remover_ckpt_path\n        self.multiview_ckpt_path = multiview_ckpt_path\n\n        self.candidate_camera_azims = [0, 90, 180, 270, 0, 180]\n        self.candidate_camera_elevs = [0, 0, 0, 0, 90, -90]\n        self.candidate_view_weights = [1, 0.1, 0.5, 0.1, 0.05, 0.05]\n\n        self.render_size = 2048\n        self.texture_size = 2048\n        self.bake_exp = 4\n        self.merge_method = 'fast'\n\n        self.pipe_dict = {'hunyuan3d-paint-v2-0': 'hunyuanpaint', 'hunyuan3d-paint-v2-0-turbo': 'hunyuanpaint-turbo'}\n        self.pipe_name = self.pipe_dict[subfolder_name]\n\n\nclass Hunyuan3DPaintPipeline:\n    @classmethod\n    def from_pretrained(cls, model_path, subfolder='hunyuan3d-paint-v2-0-turbo'):\n        original_model_path = model_path\n        if not os.path.exists(model_path):\n            # try local path\n            base_dir = os.environ.get('HY3DGEN_MODELS', '~/.cache/hy3dgen')\n            model_path = os.path.expanduser(os.path.join(base_dir, model_path))\n\n            delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')\n            multiview_model_path = os.path.join(model_path, subfolder)\n\n            if not os.path.exists(delight_model_path) or not os.path.exists(multiview_model_path):\n                try:\n                    import huggingface_hub\n                    # download from huggingface\n                    model_path = huggingface_hub.snapshot_download(\n                        repo_id=original_model_path, allow_patterns=[\"hunyuan3d-delight-v2-0/*\"]\n                    )\n                    model_path = huggingface_hub.snapshot_download(\n                        repo_id=original_model_path, allow_patterns=[f'{subfolder}/*']\n                    )\n                    delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')\n                    multiview_model_path = os.path.join(model_path, subfolder)\n                    return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path, subfolder))\n                except Exception:\n                    import traceback\n                    traceback.print_exc()\n                    raise RuntimeError(f\"Something wrong while loading {model_path}\")\n            else:\n                return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path, subfolder))\n        else:\n            delight_model_path = os.path.join(model_path, 'hunyuan3d-delight-v2-0')\n            multiview_model_path = os.path.join(model_path, subfolder)\n            return cls(Hunyuan3DTexGenConfig(delight_model_path, multiview_model_path, subfolder))\n            \n    def __init__(self, config):\n        self.config = config\n        self.models = {}\n        self.render = MeshRender(\n            default_resolution=self.config.render_size,\n            texture_size=self.config.texture_size)\n\n        self.load_models()\n\n    def load_models(self):\n        # empty cude cache\n        torch.cuda.empty_cache()\n        # Load model\n        self.models['delight_model'] = Light_Shadow_Remover(self.config)\n        self.models['multiview_model'] = Multiview_Diffusion_Net(self.config)\n        # self.models['super_model'] = Image_Super_Net(self.config)\n\n    def enable_model_cpu_offload(self, gpu_id: Optional[int] = None, device: Union[torch.device, str] = \"cuda\"):\n        self.models['delight_model'].pipeline.enable_model_cpu_offload(gpu_id=gpu_id, device=device)\n        self.models['multiview_model'].pipeline.enable_model_cpu_offload(gpu_id=gpu_id, device=device)\n\n    def render_normal_multiview(self, camera_elevs, camera_azims, use_abs_coor=True):\n        normal_maps = []\n        for elev, azim in zip(camera_elevs, camera_azims):\n            normal_map = self.render.render_normal(\n                elev, azim, use_abs_coor=use_abs_coor, return_type='pl')\n            normal_maps.append(normal_map)\n\n        return normal_maps\n\n    def render_position_multiview(self, camera_elevs, camera_azims):\n        position_maps = []\n        for elev, azim in zip(camera_elevs, camera_azims):\n            position_map = self.render.render_position(\n                elev, azim, return_type='pl')\n            position_maps.append(position_map)\n\n        return position_maps\n\n    def bake_from_multiview(self, views, camera_elevs,\n                            camera_azims, view_weights, method='graphcut'):\n        project_textures, project_weighted_cos_maps = [], []\n        project_boundary_maps = []\n        for view, camera_elev, camera_azim, weight in zip(\n            views, camera_elevs, camera_azims, view_weights):\n            project_texture, project_cos_map, project_boundary_map = self.render.back_project(\n                view, camera_elev, camera_azim)\n            project_cos_map = weight * (project_cos_map ** self.config.bake_exp)\n            project_textures.append(project_texture)\n            project_weighted_cos_maps.append(project_cos_map)\n            project_boundary_maps.append(project_boundary_map)\n\n        if method == 'fast':\n            texture, ori_trust_map = self.render.fast_bake_texture(\n                project_textures, project_weighted_cos_maps)\n        else:\n            raise f'no method {method}'\n        return texture, ori_trust_map > 1E-8\n\n    def texture_inpaint(self, texture, mask):\n\n        texture_np = self.render.uv_inpaint(texture, mask)\n        texture = torch.tensor(texture_np / 255).float().to(texture.device)\n\n        return texture\n\n    def recenter_image(self, image, border_ratio=0.2):\n        if image.mode == 'RGB':\n            return image\n        elif image.mode == 'L':\n            image = image.convert('RGB')\n            return image\n\n        alpha_channel = np.array(image)[:, :, 3]\n        non_zero_indices = np.argwhere(alpha_channel > 0)\n        if non_zero_indices.size == 0:\n            raise ValueError(\"Image is fully transparent\")\n\n        min_row, min_col = non_zero_indices.min(axis=0)\n        max_row, max_col = non_zero_indices.max(axis=0)\n\n        cropped_image = image.crop((min_col, min_row, max_col + 1, max_row + 1))\n\n        width, height = cropped_image.size\n        border_width = int(width * border_ratio)\n        border_height = int(height * border_ratio)\n\n        new_width = width + 2 * border_width\n        new_height = height + 2 * border_height\n\n        square_size = max(new_width, new_height)\n\n        new_image = Image.new('RGBA', (square_size, square_size), (255, 255, 255, 0))\n\n        paste_x = (square_size - new_width) // 2 + border_width\n        paste_y = (square_size - new_height) // 2 + border_height\n\n        new_image.paste(cropped_image, (paste_x, paste_y))\n        return new_image\n\n    @torch.no_grad()\n    def __call__(self, mesh, image):\n\n        if not isinstance(image, List):\n            image = [image]\n\n        images_prompt = []\n        for i in range(len(image)):\n            if isinstance(image[i], str):\n                image_prompt = Image.open(image[i])\n            else:\n                image_prompt = image[i]\n            images_prompt.append(image_prompt)\n            \n        images_prompt = [self.recenter_image(image_prompt) for image_prompt in images_prompt]\n\n        images_prompt = [self.models['delight_model'](image_prompt) for image_prompt in images_prompt]\n\n        mesh = mesh_uv_wrap(mesh)\n\n        self.render.load_mesh(mesh)\n\n        selected_camera_elevs, selected_camera_azims, selected_view_weights = \\\n            self.config.candidate_camera_elevs, self.config.candidate_camera_azims, self.config.candidate_view_weights\n\n        normal_maps = self.render_normal_multiview(\n            selected_camera_elevs, selected_camera_azims, use_abs_coor=True)\n        position_maps = self.render_position_multiview(\n            selected_camera_elevs, selected_camera_azims)\n\n        camera_info = [(((azim // 30) + 9) % 12) // {-20: 1, 0: 1, 20: 1, -90: 3, 90: 3}[\n            elev] + {-20: 0, 0: 12, 20: 24, -90: 36, 90: 40}[elev] for azim, elev in\n                       zip(selected_camera_azims, selected_camera_elevs)]\n        multiviews = self.models['multiview_model'](images_prompt, normal_maps + position_maps, camera_info)\n\n        for i in range(len(multiviews)):\n            # multiviews[i] = self.models['super_model'](multiviews[i])\n            multiviews[i] = multiviews[i].resize(\n                (self.config.render_size, self.config.render_size))\n\n        texture, mask = self.bake_from_multiview(multiviews,\n                                                 selected_camera_elevs, selected_camera_azims, selected_view_weights,\n                                                 method=self.config.merge_method)\n\n        mask_np = (mask.squeeze(-1).cpu().numpy() * 255).astype(np.uint8)\n\n        texture = self.texture_inpaint(texture, mask_np)\n\n        self.render.set_texture(texture)\n        textured_mesh = self.render.save_mesh()\n\n        return textured_mesh\n"
  },
  {
    "path": "hy3dgen/texgen/utils/__init__.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT."
  },
  {
    "path": "hy3dgen/texgen/utils/alignImg4Tex_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport torch\nfrom diffusers import EulerAncestralDiscreteScheduler\nfrom diffusers import StableDiffusionControlNetPipeline, StableDiffusionXLControlNetImg2ImgPipeline, ControlNetModel, \\\n    AutoencoderKL\n\n\nclass Img2img_Control_Ip_adapter:\n    def __init__(self, device):\n        controlnet = ControlNetModel.from_pretrained('lllyasviel/control_v11f1p_sd15_depth', torch_dtype=torch.float16,\n                                                     variant=\"fp16\", use_safetensors=True)\n        pipe = StableDiffusionControlNetPipeline.from_pretrained(\n            'runwayml/stable-diffusion-v1-5', controlnet=controlnet, torch_dtype=torch.float16, use_safetensors=True\n        )\n        pipe.load_ip_adapter('h94/IP-Adapter', subfolder=\"models\", weight_name=\"ip-adapter-plus_sd15.safetensors\")\n        pipe.set_ip_adapter_scale(0.7)\n\n        pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)\n        # pipe.enable_model_cpu_offload()\n        self.pipe = pipe.to(device)\n\n    def __call__(\n        self,\n        prompt,\n        control_image,\n        ip_adapter_image,\n        negative_prompt,\n        height=512,\n        width=512,\n        num_inference_steps=20,\n        guidance_scale=8.0,\n        controlnet_conditioning_scale=1.0,\n        output_type=\"pil\",\n        **kwargs,\n    ):\n        results = self.pipe(\n            prompt=prompt,\n            negative_prompt=negative_prompt,\n            image=control_image,\n            ip_adapter_image=ip_adapter_image,\n            generator=torch.manual_seed(42),\n            seed=42,\n            num_inference_steps=num_inference_steps,\n            guidance_scale=guidance_scale,\n            controlnet_conditioning_scale=controlnet_conditioning_scale,\n            strength=1,\n            # clip_skip=2,\n            height=height,\n            width=width,\n            output_type=output_type,\n            **kwargs,\n        ).images[0]\n        return results\n\n\n################################################################\n\nclass HesModel:\n    def __init__(self, ):\n        controlnet_depth = ControlNetModel.from_pretrained(\n            'diffusers/controlnet-depth-sdxl-1.0',\n            torch_dtype=torch.float16,\n            variant=\"fp16\",\n            use_safetensors=True\n        )\n        self.pipe = StableDiffusionXLControlNetImg2ImgPipeline.from_pretrained(\n            'stabilityai/stable-diffusion-xl-base-1.0',\n            torch_dtype=torch.float16,\n            variant=\"fp16\",\n            controlnet=controlnet_depth,\n            use_safetensors=True,\n        )\n        self.pipe.vae = AutoencoderKL.from_pretrained(\n            'madebyollin/sdxl-vae-fp16-fix',\n            torch_dtype=torch.float16\n        )\n\n        self.pipe.load_ip_adapter('h94/IP-Adapter', subfolder=\"sdxl_models\", weight_name=\"ip-adapter_sdxl.safetensors\")\n        self.pipe.set_ip_adapter_scale(0.7)\n        self.pipe.to(\"cuda\")\n\n    def __call__(self,\n                 init_image,\n                 control_image,\n                 ip_adapter_image=None,\n                 prompt='3D image',\n                 negative_prompt='2D image',\n                 seed=42,\n                 strength=0.8,\n                 num_inference_steps=40,\n                 guidance_scale=7.5,\n                 controlnet_conditioning_scale=0.5,\n                 **kwargs\n                 ):\n        image = self.pipe(\n            prompt=prompt,\n            image=init_image,\n            control_image=control_image,\n            ip_adapter_image=ip_adapter_image,\n            negative_prompt=negative_prompt,\n            num_inference_steps=num_inference_steps,\n            guidance_scale=guidance_scale,\n            strength=strength,\n            controlnet_conditioning_scale=controlnet_conditioning_scale,\n            seed=seed,\n            **kwargs\n        ).images[0]\n        return image\n"
  },
  {
    "path": "hy3dgen/texgen/utils/counter_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\n\nclass RunningStats():\n    def __init__(self) -> None:\n        self.count = 0\n        self.sum = 0\n        self.mean = 0\n        self.min = None\n        self.max = None\n\n    def add_value(self, value):\n        self.count += 1\n        self.sum += value\n        self.mean = self.sum / self.count\n\n        if self.min is None or value < self.min:\n            self.min = value\n\n        if self.max is None or value > self.max:\n            self.max = value\n\n    def get_count(self):\n        return self.count\n\n    def get_sum(self):\n        return self.sum\n\n    def get_mean(self):\n        return self.mean\n\n    def get_min(self):\n        return self.min\n\n    def get_max(self):\n        return self.max\n"
  },
  {
    "path": "hy3dgen/texgen/utils/dehighlight_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport cv2\nimport numpy as np\nimport torch\nfrom PIL import Image\nfrom diffusers import StableDiffusionInstructPix2PixPipeline, EulerAncestralDiscreteScheduler\n\n\nclass Light_Shadow_Remover():\n    def __init__(self, config):\n        self.device = config.device\n        self.cfg_image = 1.5\n        self.cfg_text = 1.0\n\n        pipeline = StableDiffusionInstructPix2PixPipeline.from_pretrained(\n            config.light_remover_ckpt_path,\n            torch_dtype=torch.float16,\n            safety_checker=None,\n        )\n        pipeline.scheduler = EulerAncestralDiscreteScheduler.from_config(pipeline.scheduler.config)\n        pipeline.set_progress_bar_config(disable=True)\n\n        self.pipeline = pipeline.to(self.device, torch.float16)\n    \n    def recorrect_rgb(self, src_image, target_image, alpha_channel, scale=0.95):\n        \n        def flat_and_mask(bgr, a):\n            mask = torch.where(a > 0.5, True, False)\n            bgr_flat = bgr.reshape(-1, bgr.shape[-1])\n            mask_flat = mask.reshape(-1)\n            bgr_flat_masked = bgr_flat[mask_flat, :]\n            return bgr_flat_masked\n        \n        src_flat = flat_and_mask(src_image, alpha_channel)\n        target_flat = flat_and_mask(target_image, alpha_channel)\n        corrected_bgr = torch.zeros_like(src_image)\n\n        for i in range(3): \n            src_mean, src_stddev = torch.mean(src_flat[:, i]), torch.std(src_flat[:, i])\n            target_mean, target_stddev = torch.mean(target_flat[:, i]), torch.std(target_flat[:, i])\n            corrected_bgr[:, :, i] = torch.clamp(\n                (src_image[:, :, i] - scale * src_mean) * \n                (target_stddev / src_stddev) + scale * target_mean, \n                0, 1)\n\n        src_mse = torch.mean((src_image - target_image) ** 2)\n        modify_mse = torch.mean((corrected_bgr - target_image) ** 2)\n        if src_mse < modify_mse:\n            corrected_bgr = torch.cat([src_image, alpha_channel], dim=-1)\n        else: \n            corrected_bgr = torch.cat([corrected_bgr, alpha_channel], dim=-1)\n\n        return corrected_bgr\n\n    @torch.no_grad()\n    def __call__(self, image):\n\n        image = image.resize((512, 512))\n\n        if image.mode == 'RGBA':\n            image_array = np.array(image)\n            alpha_channel = image_array[:, :, 3]\n            erosion_size = 3\n            kernel = np.ones((erosion_size, erosion_size), np.uint8)\n            alpha_channel = cv2.erode(alpha_channel, kernel, iterations=1)\n            image_array[alpha_channel == 0, :3] = 255\n            image_array[:, :, 3] = alpha_channel\n            image = Image.fromarray(image_array)\n\n            image_tensor = torch.tensor(np.array(image) / 255.0).to(self.device)\n            alpha = image_tensor[:, :, 3:]\n            rgb_target = image_tensor[:, :, :3]\n        else:\n            image_tensor = torch.tensor(np.array(image) / 255.0).to(self.device)\n            alpha = torch.ones_like(image_tensor)[:, :, :1]\n            rgb_target = image_tensor[:, :, :3]\n\n        image = image.convert('RGB')\n\n        image = self.pipeline(\n            prompt=\"\",\n            image=image,\n            generator=torch.manual_seed(42),\n            height=512,\n            width=512,\n            num_inference_steps=50,\n            image_guidance_scale=self.cfg_image,\n            guidance_scale=self.cfg_text,\n        ).images[0]\n\n        image_tensor = torch.tensor(np.array(image)/255.0).to(self.device)\n        rgb_src = image_tensor[:,:,:3]\n        image = self.recorrect_rgb(rgb_src, rgb_target, alpha)\n        image = image[:,:,:3]*image[:,:,3:] + torch.ones_like(image[:,:,:3])*(1.0-image[:,:,3:])\n        image = Image.fromarray((image.cpu().numpy()*255).astype(np.uint8))\n\n        return image\n"
  },
  {
    "path": "hy3dgen/texgen/utils/imagesuper_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport torch\nfrom diffusers import StableDiffusionUpscalePipeline\n\nclass Image_Super_Net():\n    def __init__(self, config):\n        self.up_pipeline_x4 = StableDiffusionUpscalePipeline.from_pretrained(\n                        'stabilityai/stable-diffusion-x4-upscaler',\n                        torch_dtype=torch.float16,\n                    ).to(config.device)\n        self.up_pipeline_x4.set_progress_bar_config(disable=True)\n\n    def __call__(self, image, prompt=''):\n        with torch.no_grad():\n            upscaled_image = self.up_pipeline_x4(\n                prompt=[prompt],\n                image=image,\n                num_inference_steps=5,\n            ).images[0]\n\n        return upscaled_image\n"
  },
  {
    "path": "hy3dgen/texgen/utils/multiview_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport os\nimport random\n\nimport numpy as np\nimport torch\nfrom typing import List\nfrom diffusers import DiffusionPipeline\nfrom diffusers import EulerAncestralDiscreteScheduler, LCMScheduler\n\n\nclass Multiview_Diffusion_Net():\n    def __init__(self, config) -> None:\n        self.device = config.device\n        self.view_size = 512\n        multiview_ckpt_path = config.multiview_ckpt_path\n\n        current_file_path = os.path.abspath(__file__)\n        custom_pipeline_path = os.path.join(os.path.dirname(current_file_path), '..', 'hunyuanpaint')\n\n        pipeline = DiffusionPipeline.from_pretrained(\n            multiview_ckpt_path,\n            custom_pipeline=custom_pipeline_path, torch_dtype=torch.float16)\n\n        if config.pipe_name in ['hunyuanpaint']:\n            pipeline.scheduler = EulerAncestralDiscreteScheduler.from_config(pipeline.scheduler.config,\n                                                                             timestep_spacing='trailing')\n        elif config.pipe_name in ['hunyuanpaint-turbo']:\n            pipeline.scheduler = LCMScheduler.from_config(pipeline.scheduler.config,\n                                                        timestep_spacing='trailing')\n            pipeline.set_turbo(True)\n            # pipeline.prepare() \n\n        pipeline.set_progress_bar_config(disable=True)\n        self.pipeline = pipeline.to(self.device)\n\n    def seed_everything(self, seed):\n        random.seed(seed)\n        np.random.seed(seed)\n        torch.manual_seed(seed)\n        os.environ[\"PL_GLOBAL_SEED\"] = str(seed)\n\n    def __call__(self, input_images, control_images, camera_info):\n\n        self.seed_everything(0)\n\n        if not isinstance(input_images, List):\n            input_images = [input_images]\n\n        input_images = [input_image.resize((self.view_size, self.view_size)) for input_image in input_images]\n        for i in range(len(control_images)):\n            control_images[i] = control_images[i].resize((self.view_size, self.view_size))\n            if control_images[i].mode == 'L':\n                control_images[i] = control_images[i].point(lambda x: 255 if x > 1 else 0, mode='1')\n\n        kwargs = dict(generator=torch.Generator(device=self.pipeline.device).manual_seed(0))\n\n        num_view = len(control_images) // 2\n        normal_image = [[control_images[i] for i in range(num_view)]]\n        position_image = [[control_images[i + num_view] for i in range(num_view)]]\n\n        camera_info_gen = [camera_info]\n        camera_info_ref = [[0]]\n        kwargs['width'] = self.view_size\n        kwargs['height'] = self.view_size\n        kwargs['num_in_batch'] = num_view\n        kwargs['camera_info_gen'] = camera_info_gen\n        kwargs['camera_info_ref'] = camera_info_ref\n        kwargs[\"normal_imgs\"] = normal_image\n        kwargs[\"position_imgs\"] = position_image\n\n        mvd_image = self.pipeline(input_images, num_inference_steps=30, **kwargs).images\n\n        return mvd_image\n"
  },
  {
    "path": "hy3dgen/texgen/utils/simplify_mesh_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport trimesh\n\n\ndef remesh_mesh(mesh_path, remesh_path, method='trimesh'):\n    if method == 'trimesh':\n        mesh_simplify_trimesh(mesh_path, remesh_path)\n    else:\n        raise f'Method {method} has not been implemented.'\n\n\ndef mesh_simplify_trimesh(inputpath, outputpath):\n    import pymeshlab\n    ms = pymeshlab.MeshSet()\n    ms.load_new_mesh(inputpath, load_in_a_single_layer=True)\n    ms.save_current_mesh(outputpath.replace('.glb', '.obj'), save_textures=False)\n\n    courent = trimesh.load(outputpath.replace('.glb', '.obj'), force='mesh')\n    face_num = courent.faces.shape[0]\n\n    if face_num > 100000:\n        courent = courent.simplify_quadric_decimation(40000)\n    courent.export(outputpath)\n"
  },
  {
    "path": "hy3dgen/texgen/utils/uv_warp_utils.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport trimesh\nimport xatlas\n\n\ndef mesh_uv_wrap(mesh):\n    if isinstance(mesh, trimesh.Scene):\n        mesh = mesh.dump(concatenate=True)\n\n    if len(mesh.faces) > 500000000:\n        raise ValueError(\"The mesh has more than 500,000,000 faces, which is not supported.\")\n\n    vmapping, indices, uvs = xatlas.parametrize(mesh.vertices, mesh.faces)\n\n    mesh.vertices = mesh.vertices[vmapping]\n    mesh.faces = indices\n    mesh.visual.uv = uvs\n\n    return mesh\n"
  },
  {
    "path": "hy3dgen/text2image.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nimport os\nimport random\n\nimport numpy as np\nimport torch\nfrom diffusers import AutoPipelineForText2Image\n\n\ndef seed_everything(seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    os.environ[\"PL_GLOBAL_SEED\"] = str(seed)\n\n\nclass HunyuanDiTPipeline:\n    def __init__(\n        self,\n        model_path=\"Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled\",\n        device='cuda'\n    ):\n        self.device = device\n        self.pipe = AutoPipelineForText2Image.from_pretrained(\n            model_path,\n            torch_dtype=torch.float16,\n            enable_pag=True,\n            pag_applied_layers=[\"blocks.(16|17|18|19)\"]\n        ).to(device)\n        self.pos_txt = \",白色背景,3D风格,最佳质量\"\n        self.neg_txt = \"文本,特写,裁剪,出框,最差质量,低质量,JPEG伪影,PGLY,重复,病态,\" \\\n                       \"残缺,多余的手指,变异的手,画得不好的手,画得不好的脸,变异,畸形,模糊,脱水,糟糕的解剖学,\" \\\n                       \"糟糕的比例,多余的肢体,克隆的脸,毁容,恶心的比例,畸形的肢体,缺失的手臂,缺失的腿,\" \\\n                       \"额外的手臂,额外的腿,融合的手指,手指太多,长脖子\"\n\n    def compile(self):\n        # accelarate hunyuan-dit transformer,first inference will cost long time\n        torch.set_float32_matmul_precision('high')\n        self.pipe.transformer = torch.compile(self.pipe.transformer, fullgraph=True)\n        # self.pipe.vae.decode = torch.compile(self.pipe.vae.decode, fullgraph=True)\n        generator = torch.Generator(device=self.pipe.device)  # infer once for hot-start\n        out_img = self.pipe(\n            prompt='美少女战士',\n            negative_prompt='模糊',\n            num_inference_steps=25,\n            pag_scale=1.3,\n            width=1024,\n            height=1024,\n            generator=generator,\n            return_dict=False\n        )[0][0]\n\n    @torch.no_grad()\n    def __call__(self, prompt, seed=0):\n        seed_everything(seed)\n        generator = torch.Generator(device=self.pipe.device)\n        generator = generator.manual_seed(int(seed))\n        out_img = self.pipe(\n            prompt=prompt[:60] + self.pos_txt,\n            negative_prompt=self.neg_txt,\n            num_inference_steps=25,\n            pag_scale=1.3,\n            width=1024,\n            height=1024,\n            generator=generator,\n            return_dict=False\n        )[0][0]\n        return out_img\n"
  },
  {
    "path": "minimal_demo.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom PIL import Image\n\nfrom hy3dgen.rembg import BackgroundRemover\nfrom hy3dgen.shapegen import Hunyuan3DDiTFlowMatchingPipeline\nfrom hy3dgen.texgen import Hunyuan3DPaintPipeline\n\nmodel_path = 'tencent/Hunyuan3D-2'\npipeline_shapegen = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained(model_path)\npipeline_texgen = Hunyuan3DPaintPipeline.from_pretrained(model_path)\n\nimage_path = 'assets/demo.png'\nimage = Image.open(image_path).convert(\"RGBA\")\nif image.mode == 'RGB':\n    rembg = BackgroundRemover()\n    image = rembg(image)\n\nmesh = pipeline_shapegen(image=image)[0]\nmesh = pipeline_texgen(mesh, image=image)\nmesh.export('demo.glb')\n"
  },
  {
    "path": "minimal_vae_demo.py",
    "content": "import torch\n\nfrom hy3dgen.shapegen.models.autoencoders import ShapeVAE\nfrom hy3dgen.shapegen.surface_loaders import SharpEdgeSurfaceLoader\n\n# vae = ShapeVAE.from_pretrained(\n#     'tencent/Hunyuan3D-2',\n#     subfolder='hunyuan3d-vae-v2-0-withencoder',\n#     use_safetensors=False,\n#     pc_size = 30720,\n#     pc_sharpedge_size= 30720,\n# )\n# loader = SharpEdgeSurfaceLoader(\n#     num_sharp_points=30720,\n#     num_uniform_points=30720,\n# )\n\nvae = ShapeVAE.from_pretrained(\n    'tencent/Hunyuan3D-2mini',\n    subfolder='hunyuan3d-vae-v2-mini-withencoder',\n    use_safetensors=False,\n)\nloader = SharpEdgeSurfaceLoader(\n    num_sharp_points=5120,\n    num_uniform_points=5120,\n)\nsurface = loader('demo.glb').to('cuda', dtype=torch.float16)\n\nlatents = vae.encode(surface)\nlatents = vae.decode(latents)\nmesh = vae.latents2mesh(\n    latents,\n    output_type='trimesh',\n    bounds=1.01,\n    mc_level=0.0,\n    num_chunks=20000,\n    octree_resolution=256,\n    mc_algo='mc',\n    enable_pbar=True\n)\nfrom hy3dgen.shapegen.pipelines import export_to_trimesh\n\nmesh = export_to_trimesh(mesh)[0]\nmesh.export('output.glb')\n"
  },
  {
    "path": "requirements.txt",
    "content": "ninja\npybind11\n\ndiffusers\neinops\nopencv-python\nnumpy\ntorch\ntransformers\ntorchvision\n#taming-transformers-rom1504\n#ConfigArgParse\n#ipdb\nomegaconf\n\n#sentencepiece\ntqdm\n\n# Mesh Processing\ntrimesh\npymeshlab\npygltflib\nxatlas\n#kornia\n#facexlib\n\n# Training\naccelerate\n#pytorch_lightning\n#scikit-learn\n#scikit-image\n\n# Demo only\ngradio\nfastapi\nuvicorn\nrembg\nonnxruntime\n#gevent\n#geventhttpclient"
  },
  {
    "path": "setup.py",
    "content": "# Hunyuan 3D is licensed under the TENCENT HUNYUAN NON-COMMERCIAL LICENSE AGREEMENT\n# except for the third-party components listed below.\n# Hunyuan 3D does not impose any additional limitations beyond what is outlined\n# in the repsective licenses of these third-party components.\n# Users must comply with all terms and conditions of original licenses of these third-party\n# components and must ensure that the usage of the third party components adheres to\n# all relevant laws and regulations.\n\n# For avoidance of doubts, Hunyuan 3D means the large language models and\n# their software and algorithms, including trained model weights, parameters (including\n# optimizer states), machine-learning model code, inference-enabling code, training-enabling code,\n# fine-tuning enabling code and other elements of the foregoing made publicly available\n# by Tencent in accordance with TENCENT HUNYUAN COMMUNITY LICENSE AGREEMENT.\n\nfrom setuptools import setup, find_packages\n\nsetup(\n    name=\"hy3dgen\",\n    version=\"2.0.2\",\n    url=\"https://github.com/Tencent/Hunyuan3D-2\",\n    packages=find_packages(),\n    include_package_data=True,\n    package_data={\"hy3dgen\": [\"assets/*\", \"assets/**/*\"]},\n    install_requires=[\n        'gradio',\n        \"tqdm>=4.66.3\",\n        'numpy',\n        'ninja',\n        'diffusers',\n        'pybind11',\n        'opencv-python',\n        'einops',\n        \"transformers>=4.48.0\",\n        'omegaconf',\n        'trimesh',\n        'pymeshlab',\n        'pygltflib',\n        'xatlas',\n        'accelerate',\n        'gradio',\n        'fastapi',\n        'uvicorn',\n        'rembg',\n        'onnxruntime'\n    ]\n)\n"
  }
]