diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 000000000..1d04054fd --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,160 @@ +name: Python + +on: + push: + branches: [ master, python ] + tags: [ v* ] + +jobs: + python-sdist: + name: python sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Pack + working-directory: cython + run: | + python -m pip install -U setuptools build + python -m build --sdist + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: sdist + path: cython/dist/*.tar.gz + python-wheel: + name: ${{ matrix.os_short }} python ${{ matrix.architecture }} cp${{ matrix.python_version }} + runs-on: ${{ matrix.os }} + timeout-minutes: 240 + strategy: + fail-fast: false + matrix: + os_arch: [ + "windows-ia32", + "windows-x64", + # "windows-arm64", + "macos-x86_64", + "macos-arm64", + "linux-x86_64", + "linux-aarch64", + ] + python_version: [ + "37", + "38", + "39", + "310", + "311", + ] + exclude: + - os_arch: "macos-arm64" + python_version: "37" + include: + - os_arch: "windows-ia32" + os: "windows-2022" + os_short: "windows" + architecture: "ia32" + cibuildwheel_architecture: "x86" + cmake_generator_platform: "Win32" + - os_arch: "windows-x64" + os: "windows-2022" + os_short: "windows" + architecture: "x64" + cibuildwheel_architecture: "AMD64" + cmake_generator_platform: "x64" + # - os_arch: "windows-arm64" + # os: "windows-2022" + # os_short: "windows" + # architecture: "arm64" + # cibuildwheel_architecture: "ARM64" + # cmake_generator_platform: "ARM64" + - os_arch: "macos-x86_64" + os: "macos-11.0" + os_short: "macos" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: "macos-arm64" + os: "macos-11.0" + os_short: "macos" + cibuildwheel_architecture: "arm64" + architecture: "arm64" + - os_arch: linux-x86_64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: linux-aarch64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "aarch64" + architecture: "aarch64" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - name: Set version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + version="${GITHUB_REF##*/}" + else + version="$(git describe --tags).dev${GITHUB_RUN_NUMBER}" + fi + cd cython && sed -i.bak "s/^version = .*/version = \"${version}\"/g" pyproject.toml && rm pyproject.toml.bak + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + if: matrix.architecture == 'aarch64' + with: + platforms: arm64 + - name: Build wheels + uses: pypa/cibuildwheel@v2.11.2 + with: + package-dir: cython + env: + CIBW_BUILD: "cp${{ matrix.python_version }}-*" + CIBW_PLATFORM: "${{ matrix.os_short }}" + CIBW_BUILD_VERBOSITY: "1" + CIBW_ARCHS: "${{ matrix.cibuildwheel_architecture }}" + CIBW_ENVIRONMENT_WINDOWS: > + CMAKE_GENERATOR="Visual Studio 17 2022" + CMAKE_GENERATOR_PLATFORM="${{ matrix.cmake_generator_platform }}" + CIBW_ENVIRONMENT_PASS_WINDOWS: "CMAKE_GENERATOR CMAKE_GENERATOR_PLATFORM" + CIBW_ENVIRONMENT_LINUX: > + CMAKE_GENERATOR="Unix Makefiles" + CIBW_ENVIRONMENT_PASS_LINUX: "CMAKE_GENERATOR" + - uses: actions/upload-artifact@v3 + with: + name: wheel-${{ matrix.os_short }}-${{ matrix.architecture }} + path: | + ./wheelhouse/*.whl + publish-pypi: + name: publish to PyPi + needs: [ + python-sdist, + python-wheel, + ] + runs-on: ubuntu-22.04 + steps: + - uses: actions/download-artifact@v3 + with: + path: prebuilds + - name: prepare + shell: bash + run: | + mkdir dist + ls prebuilds + mv prebuilds/*/* dist + ls dist + - name: Publish wheels to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + - name: Publish wheels to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.gitignore b/.gitignore index 59755d77c..46df887d0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /obj-*/ /*.slvs .vscode/ +.idea/ # Visual Studio out/ diff --git a/cython/.gitignore b/cython/.gitignore new file mode 100644 index 000000000..4dbd38fa7 --- /dev/null +++ b/cython/.gitignore @@ -0,0 +1,134 @@ +/CMakeCache.txt +/build*/ +/test/**/*.diff.* +/test/**/*.curr.* +*.trace +/debian/tmp/ +/debian/*.log +/debian/*.substvars +/debian/*.debhelper +/debian/files +/debian/solvespace/ +/debian/libslvs1/ +/debian/libslvs1-dev/ +/obj-*/ +/*.slvs +solvespace/*.cpp +solvespace/src/ +solvespace/include/ +solvespace/extlib/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so +*.o* +*.cxx +*.def +*.lib + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +AppImageAssistant + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ +out/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +#PyCharm cache +.idea/ + +# Others +.DS_Store +*/.DS_Store diff --git a/cython/COPYING.txt b/cython/COPYING.txt new file mode 100644 index 000000000..a737dcfed --- /dev/null +++ b/cython/COPYING.txt @@ -0,0 +1,675 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in new file mode 100644 index 000000000..e7bddb14f --- /dev/null +++ b/cython/MANIFEST.in @@ -0,0 +1,3 @@ +include pyproject.toml setup.cfg +include README.md +recursive-include . *.h *.c */Eigen/* diff --git a/cython/README.md b/cython/README.md new file mode 100644 index 000000000..45d2cfb06 --- /dev/null +++ b/cython/README.md @@ -0,0 +1,83 @@ +[![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) +[![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE) + +# solvespace + +Python library from the solver of SolveSpace, an open source CAD software. + ++ [Python API](https://pyslvs-ui.readthedocs.io/en/stable/python-solvespace-api/) ++ [C API](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt) + +The example extracted from unit test: + +```python +from solvespace import SolverSystem, ResultFlag + +sys = SolverSystem() +wp = sys.create_2d_base() # Workplane (Entity) +p0 = sys.add_point_2d(0, 0, wp) # Entity +sys.dragged(p0, wp) # Make a constraint with the entity +... +line0 = sys.add_line_2d(p0, p1, wp) # Create entity with others +... +line1 = sys.add_line_2d(p0, p3, wp) +sys.angle(line0, line1, 45, wp) # Constrain two entities +line1 = sys.entity(-1) # Entity handle can be re-generated and negatively indexed +... +if sys.solve() == ResultFlag.OKAY: + # Get the result (unpack from the entity or parameters) + # x and y are actually float type + dof = sys.dof() + x, y = sys.params(p2.params) + ... +else: + # Error! + # Get the list of all constraints + failures = sys.failures() + ... +``` + +Solver can also be serialized and copied, but can not modify or undo last step. + +```python +import pickle +print(pickle.dumps(sys)) + +sys_new = sys.copy() +``` + +The entity and parameter handles should have the same lifetime to the solver. + +# Install + +```bash +pip install solvespace +``` + +# Build and Test (Repository) + +First build and install the module from the repo: + +```bash +git submodule update --init extlib/mimalloc extlib/eigen +cd cython +pip install -e . +``` + +Rebuild the module: + +```bash +pip install -e . --no-deps +``` + +Run the unit tests: + +```bash +python -m unittest +``` + +Uninstall the module: + +```bash +pip uninstall solvespace +``` diff --git a/cython/platform/cygwinccompiler.diff b/cython/platform/cygwinccompiler.diff new file mode 100644 index 000000000..e7b1f9c14 --- /dev/null +++ b/cython/platform/cygwinccompiler.diff @@ -0,0 +1,22 @@ +--- cygwinccompiler.py ++++ cygwinccompiler.py +@@ -84,7 +84,21 @@ def get_msvcr(): + elif msc_ver == '1600': + # VS2010 / MSVC 10.0 + return ['msvcr100'] ++ elif msc_ver == '1700': ++ # Visual Studio 2012 / Visual C++ 11.0 ++ return ['msvcr110'] ++ elif msc_ver == '1800': ++ # Visual Studio 2013 / Visual C++ 12.0 ++ return ['msvcr120'] ++ elif msc_ver == '1900': ++ # Visual Studio 2015 / Visual C++ 14.0 ++ # "msvcr140.dll no longer exists" http://blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx ++ return ['vcruntime140'] ++ elif 1910 <= int(msc_ver) <= 1916: ++ return ['vcruntime140'] ++ elif 1920 <= int(msc_ver) <= 1928: ++ return ['vcruntime140'] + else: + raise ValueError("Unknown MS Compiler version %s " % msc_ver) diff --git a/cython/platform/pyconfig.diff b/cython/platform/pyconfig.diff new file mode 100644 index 000000000..0a31106cc --- /dev/null +++ b/cython/platform/pyconfig.diff @@ -0,0 +1,15 @@ +--- pyconfig.h 2016-01-27 10:54:56.000000000 +0300 ++++ pyconfig.h 2016-03-28 11:46:48.000000000 +0300 +@@ -100,6 +100,12 @@ + + /* Compiler specific defines */ + ++#ifdef __MINGW32__ ++#ifdef _WIN64 ++#define MS_WIN64 ++#endif ++#endif ++ + /* ------------------------------------------------------------------------*/ + /* Microsoft C defines _MSC_VER */ + #ifdef _MSC_VER diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat new file mode 100644 index 000000000..8b8513efc --- /dev/null +++ b/cython/platform/set_pycompiler.bat @@ -0,0 +1,25 @@ +echo off + +REM Usage: set_pycompiler C:\Python37 mingw32 +REM Where %PYTHON_DIR% is the directory of your Python installation. +REM Compiler option can be "mingw32" or "msvc". +REM In Pyslvs project. +set HERE=%~dp0 +set PYTHON_DIR=%1 +set COMPILER=%2 + +REM Create "distutils.cfg" +set DISTUTILS=%PYTHON_DIR%\Lib\distutils\distutils.cfg +if exist "%DISTUTILS%" del "%DISTUTILS%" /Q /S +echo [build]>> "%DISTUTILS%" +echo compiler=%COMPILER%>> "%DISTUTILS%" +echo patched file "%DISTUTILS%" +REM Apply the patch of "cygwinccompiler.py". +REM Unix "patch" command of Msys. +set patch="C:\Program Files\Git\usr\bin\patch.exe" +%patch% -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\cygwinccompiler.diff" +%patch% -N "%PYTHON_DIR%\include\pyconfig.h" "%HERE%\pyconfig.diff" + +REM Copy "vcruntime140.dll" to "libs". +copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" +echo copied "vcruntime140.dll". diff --git a/cython/pyproject.toml b/cython/pyproject.toml new file mode 100644 index 000000000..e2f968540 --- /dev/null +++ b/cython/pyproject.toml @@ -0,0 +1,50 @@ +[project] +name = "solvespace" +version = "3.1.0" +description = "Parametric 2d/3d CAD solver" +readme = "README.md" +requires-python = ">=3.6" +keywords = ["cad", "mechanical-engineering", "2d", "3d"] +license = { file = "COPYING.txt" } +authors = [ + { name = "Jonathan Westhues", email="jwesthues@cq.cx" } +] +maintainers = [ + { name = "Yuan Chang", email="pyslvs@gmail.com" } +] + +classifiers = [ + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Cython", + "Topic :: Scientific/Engineering", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Operating System :: OS Independent", + "Typing :: Typed" +] + +[project.urls] +homepage = "https://github.com/solvespace/solvespace" +documentation = "https://github.com/solvespace/solvespace" +repository = "https://github.com/solvespace/solvespace" + +[build-system] +requires = ["wheel", "setuptools"] +build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +build = ["cp37*", "cp38*", "cp39*", "cp310*", "cp311*"] +skip = ["pp*", "*-musllinux*", "*-manylinux_i686"] + +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86", "ARM64"] + +[tool.cibuildwheel.linux] +archs = ["x86_64", "aarch64"] + +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64"] \ No newline at end of file diff --git a/cython/setup.cfg b/cython/setup.cfg new file mode 100644 index 000000000..7f48ea3cf --- /dev/null +++ b/cython/setup.cfg @@ -0,0 +1,48 @@ +[metadata] +name = solvespace +version = attr: solvespace.__version__ +description = Python library of Solvespace. +long_description = file: README.md +long_description_content_type = text/markdown +keywords = cad,mechanical-engineering,2d,3d +license = GPLv3+ +author = Yuan Chang +author_email = pyslvs@gmail.com +url = https://github.com/KmolYuan/solvespace +classifiers = + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Cython + Topic :: Scientific/Engineering + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Operating System :: OS Independent + Typing :: Typed + +[options] +zip_safe = False +packages = find: +python_requires = >=3.6 +setup_requires = + cython + +[options.package_data] +* = *.pyi, *.pxd, *.pyx +solvespace = py.typed + +[options.packages.find] +exclude = + test + +[mypy] +pretty = True +show_error_codes = True +show_column_numbers = True +ignore_missing_imports = True +allow_redefinition = True +warn_redundant_casts = True +warn_unreachable = True +strict_equality = True diff --git a/cython/setup.py b/cython/setup.py new file mode 100644 index 000000000..e8389331a --- /dev/null +++ b/cython/setup.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +"""Compile the Cython libraries of Solvespace.""" + +__author__ = "Yuan Chang" +__copyright__ = "Copyright (C) 2016-2019" +__license__ = "GPLv3+" +__email__ = "pyslvs@gmail.com" + +import sys +from os import walk +from os.path import dirname, isdir, join +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from setuptools.command.sdist import sdist +from distutils import file_util, dir_util +from platform import system + +m_path = 'solvespace' +include_path = join(m_path, 'include') +src_path = join(m_path, 'src') +platform_path = join(src_path, 'platform') +extlib_path = join(m_path, 'extlib') +mimalloc_path = join(extlib_path, 'mimalloc') +mimalloc_include_path = join(mimalloc_path, 'include') +mimalloc_src_path = join(mimalloc_path, 'src') +eigen_path = join(include_path, 'Eigen') +build_dir = 'build' +macros = [ + ('M_PI', 'PI'), + ('_USE_MATH_DEFINES', None), + ('ISOLATION_AWARE_ENABLED', None), + ('LIBRARY', None), + ('EXPORT_DLL', None), + ('_CRT_SECURE_NO_WARNINGS', None), +] +compile_args = [ + '-O3', + '-Wno-cpp', + '-g', + '-Wno-write-strings', + '-fpermissive', + '-fPIC', + '-std=c++17', +] +compile_args_msvc = [ + '/O2', + '/std:c++17', +] +link_args = ['-static-libgcc', '-static-libstdc++', + '-Wl,-Bstatic,--whole-archive', + '-lwinpthread', + '-Wl,--no-whole-archive', + '-lbcrypt', + '-lpsapi', + '-Wl,-Bdynamic'] +sources = [ + join(m_path, 'slvs.pyx'), + join(src_path, 'util.cpp'), + join(src_path, 'entity.cpp'), + join(src_path, 'expr.cpp'), + join(src_path, 'constraint.cpp'), + join(src_path, 'constrainteq.cpp'), + join(src_path, 'system.cpp'), + join(src_path, 'lib.cpp'), + join(platform_path, 'platform.cpp'), +] +mimalloc_sources = [ + # MiMalloc + join(mimalloc_src_path, 'stats.c'), + join(mimalloc_src_path, 'random.c'), + join(mimalloc_src_path, 'os.c'), + join(mimalloc_src_path, 'bitmap.c'), + join(mimalloc_src_path, 'arena.c'), + join(mimalloc_src_path, 'segment-cache.c'), + join(mimalloc_src_path, 'segment.c'), + join(mimalloc_src_path, 'page.c'), + join(mimalloc_src_path, 'alloc.c'), + join(mimalloc_src_path, 'alloc-aligned.c'), + join(mimalloc_src_path, 'alloc-posix.c'), + join(mimalloc_src_path, 'heap.c'), + join(mimalloc_src_path, 'options.c'), + join(mimalloc_src_path, 'init.c'), +] +if {'sdist', 'bdist'} & set(sys.argv): + sources.append(join(platform_path, 'platform.cpp')) +elif system() == 'Windows': + # Disable format warning + compile_args.append('-Wno-format') + # Solvespace arguments + macros.append(('WIN32', None)) + if sys.version_info < (3, 7): + macros.append(('_hypot', 'hypot')) +else: + macros.append(('UNIX_DATADIR', '"solvespace"')) +compiler_directives = {'binding': True, 'cdivision': True} + + +def copy_source(dry_run): + dir_util.copy_tree(join('..', 'include'), include_path, dry_run=dry_run) + dir_util.copy_tree(join('..', 'extlib', 'eigen', 'Eigen'), eigen_path, dry_run=dry_run) + dir_util.copy_tree(join('..', 'extlib', 'mimalloc', 'include'), + mimalloc_include_path, + dry_run=dry_run) + dir_util.mkpath(src_path) + dir_util.mkpath(mimalloc_src_path) + for path in (join('..', 'src'), join('..', 'extlib', 'mimalloc', 'src')): + for root, _, files in walk(path): + for f in files: + if not (f.endswith('.h') or f.endswith('.c')): + continue + f = join(root, f) + f_new = f.replace('..', m_path) + if not isdir(dirname(f_new)): + dir_util.mkpath(dirname(f_new)) + file_util.copy_file(f, f_new, dry_run=dry_run) + for f in sources[1:] + mimalloc_sources: + file_util.copy_file(f.replace(m_path, '..'), f, dry_run=dry_run) + # Create an empty header + open(join(platform_path, 'config.h'), 'a').close() + + +class Build(build_ext): + + def build_extensions(self): + compiler = self.compiler.compiler_type + for e in self.extensions: + e.cython_directives = compiler_directives + e.libraries = ['mimalloc'] + e.library_dirs = [build_dir] + if compiler in {'mingw32', 'unix'}: + e.define_macros = macros + e.extra_compile_args = compile_args + if compiler == 'mingw32': + e.extra_link_args = link_args + elif compiler == 'msvc': + e.define_macros = macros[1:] + e.libraries.extend(['shell32', 'advapi32', 'Ws2_32']) + e.extra_compile_args = compile_args_msvc + has_src = isdir(include_path) and isdir(src_path) and isdir(extlib_path) + if not has_src: + copy_source(self.dry_run) + # Pre-build MiMalloc + if compiler in {'mingw32', 'unix'}: + args = ['-fPIC'] + else: + args = [] + objects = self.compiler.compile( + mimalloc_sources, + extra_postargs=args, + include_dirs=[mimalloc_include_path, mimalloc_src_path] + ) + dir_util.mkpath(build_dir) + self.compiler.create_static_lib(objects, 'mimalloc', target_lang='c', + output_dir=build_dir) + super(Build, self).build_extensions() + if not has_src: + dir_util.remove_tree(include_path, dry_run=self.dry_run) + dir_util.remove_tree(src_path, dry_run=self.dry_run) + dir_util.remove_tree(extlib_path, dry_run=self.dry_run) + + +class PackSource(sdist): + + def run(self): + copy_source(self.dry_run) + super(PackSource, self).run() + if not self.keep_temp: + dir_util.remove_tree(include_path, dry_run=self.dry_run) + dir_util.remove_tree(src_path, dry_run=self.dry_run) + dir_util.remove_tree(extlib_path, dry_run=self.dry_run) + + +setup(ext_modules=[Extension( + "solvespace.slvs", + sources, + language="c++", + include_dirs=[include_path, src_path, mimalloc_include_path, + mimalloc_src_path] +)], cmdclass={'build_ext': Build, 'sdist': PackSource}) diff --git a/cython/solvespace/__init__.pxd b/cython/solvespace/__init__.pxd new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/cython/solvespace/__init__.pxd @@ -0,0 +1 @@ + diff --git a/cython/solvespace/__init__.py b/cython/solvespace/__init__.py new file mode 100644 index 000000000..73ef2a3a4 --- /dev/null +++ b/cython/solvespace/__init__.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +"""'solvespace' module is a wrapper of +Python binding Solvespace solver libraries. +""" + +__author__ = "Yuan Chang" +__copyright__ = "Copyright (C) 2016-2019" +__license__ = "GPLv3+" +__email__ = "pyslvs@gmail.com" +__version__ = "3.0.8" + +from .slvs import ( + quaternion_u, + quaternion_v, + quaternion_n, + make_quaternion, + Constraint, + ResultFlag, + Params, + Entity, + SolverSystem, +) + +__all__ = [ + 'quaternion_u', + 'quaternion_v', + 'quaternion_n', + 'make_quaternion', + 'Constraint', + 'ResultFlag', + 'Params', + 'Entity', + 'SolverSystem', +] diff --git a/cython/solvespace/py.typed b/cython/solvespace/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/cython/solvespace/slvs.pxd b/cython/solvespace/slvs.pxd new file mode 100644 index 000000000..685a4947f --- /dev/null +++ b/cython/solvespace/slvs.pxd @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +# cython: language_level=3 + +"""Wrapper header of Solvespace. + +author: Yuan Chang +copyright: Copyright (C) 2016-2019 +license: GPLv3+ +email: pyslvs@gmail.com +""" + +from libc.stdint cimport uint32_t +from libcpp.vector cimport vector + +cdef extern from "slvs.h" nogil: + + ctypedef uint32_t Slvs_hParam + ctypedef uint32_t Slvs_hEntity + ctypedef uint32_t Slvs_hConstraint + ctypedef uint32_t Slvs_hGroup + + # Virtual work plane entity + Slvs_hEntity SLVS_FREE_IN_3D + + ctypedef struct Slvs_Param: + Slvs_hParam h + Slvs_hGroup group + double val + + # Entity type + int SLVS_E_POINT_IN_3D + int SLVS_E_POINT_IN_2D + int SLVS_E_NORMAL_IN_2D + int SLVS_E_NORMAL_IN_3D + int SLVS_E_DISTANCE + int SLVS_E_WORKPLANE + int SLVS_E_LINE_SEGMENT + int SLVS_E_CUBIC + int SLVS_E_CIRCLE + int SLVS_E_ARC_OF_CIRCLE + + ctypedef struct Slvs_Entity: + Slvs_hEntity h + Slvs_hGroup group + int type + Slvs_hEntity wrkpl + Slvs_hEntity point[4] + Slvs_hEntity normal + Slvs_hEntity distance + Slvs_hParam param[4] + + int SLVS_C_POINTS_COINCIDENT + int SLVS_C_PT_PT_DISTANCE + int SLVS_C_PT_PLANE_DISTANCE + int SLVS_C_PT_LINE_DISTANCE + int SLVS_C_PT_FACE_DISTANCE + int SLVS_C_PT_IN_PLANE + int SLVS_C_PT_ON_LINE + int SLVS_C_PT_ON_FACE + int SLVS_C_EQUAL_LENGTH_LINES + int SLVS_C_LENGTH_RATIO + int SLVS_C_EQ_LEN_PT_LINE_D + int SLVS_C_EQ_PT_LN_DISTANCES + int SLVS_C_EQUAL_ANGLE + int SLVS_C_EQUAL_LINE_ARC_LEN + int SLVS_C_SYMMETRIC + int SLVS_C_SYMMETRIC_HORIZ + int SLVS_C_SYMMETRIC_VERT + int SLVS_C_SYMMETRIC_LINE + int SLVS_C_AT_MIDPOINT + int SLVS_C_HORIZONTAL + int SLVS_C_VERTICAL + int SLVS_C_DIAMETER + int SLVS_C_PT_ON_CIRCLE + int SLVS_C_SAME_ORIENTATION + int SLVS_C_ANGLE + int SLVS_C_PARALLEL + int SLVS_C_PERPENDICULAR + int SLVS_C_ARC_LINE_TANGENT + int SLVS_C_CUBIC_LINE_TANGENT + int SLVS_C_EQUAL_RADIUS + int SLVS_C_PROJ_PT_DISTANCE + int SLVS_C_WHERE_DRAGGED + int SLVS_C_CURVE_CURVE_TANGENT + int SLVS_C_LENGTH_DIFFERENCE + + ctypedef struct Slvs_Constraint: + Slvs_hConstraint h + Slvs_hGroup group + int type + Slvs_hEntity wrkpl + double valA + Slvs_hEntity ptA + Slvs_hEntity ptB + Slvs_hEntity entityA + Slvs_hEntity entityB + Slvs_hEntity entityC + Slvs_hEntity entityD + int other + int other2 + + ctypedef struct Slvs_System: + Slvs_Param *param + int params + Slvs_Entity *entity + int entities + Slvs_Constraint *constraint + int constraints + Slvs_hParam dragged[4] + int calculateFaileds + Slvs_hConstraint *failed + int faileds + int dof + int result + + void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg) + void Slvs_QuaternionU( + double qw, double qx, double qy, double qz, + double *x, double *y, double *z + ) + void Slvs_QuaternionV( + double qw, double qx, double qy, double qz, + double *x, double *y, double *z + ) + void Slvs_QuaternionN( + double qw, double qx, double qy, double qz, + double *x, double *y, double *z + ) + void Slvs_MakeQuaternion( + double ux, double uy, double uz, + double vx, double vy, double vz, + double *qw, double *qx, double *qy, double *qz + ) + Slvs_Param Slvs_MakeParam(Slvs_hParam h, Slvs_hGroup group, double val) + Slvs_Entity Slvs_MakePoint2d( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, + Slvs_hParam u, Slvs_hParam v + ) + Slvs_Entity Slvs_MakePoint3d( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hParam x, Slvs_hParam y, Slvs_hParam z + ) + Slvs_Entity Slvs_MakeNormal3d( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hParam qw, Slvs_hParam qx, + Slvs_hParam qy, Slvs_hParam qz + ) + Slvs_Entity Slvs_MakeNormal2d( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl + ) + Slvs_Entity Slvs_MakeDistance( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, Slvs_hParam d + ) + Slvs_Entity Slvs_MakeLineSegment( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, + Slvs_hEntity ptA, Slvs_hEntity ptB + ) + Slvs_Entity Slvs_MakeCubic( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, + Slvs_hEntity pt0, Slvs_hEntity pt1, + Slvs_hEntity pt2, Slvs_hEntity pt3 + ) + Slvs_Entity Slvs_MakeArcOfCircle( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, + Slvs_hEntity normal, + Slvs_hEntity center, + Slvs_hEntity start, Slvs_hEntity end + ) + Slvs_Entity Slvs_MakeCircle( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity wrkpl, + Slvs_hEntity center, + Slvs_hEntity normal, Slvs_hEntity radius + ) + Slvs_Entity Slvs_MakeWorkplane( + Slvs_hEntity h, Slvs_hGroup group, + Slvs_hEntity origin, Slvs_hEntity normal + ) + Slvs_Constraint Slvs_MakeConstraint( + Slvs_hConstraint h, + Slvs_hGroup group, + int type, + Slvs_hEntity wrkpl, + double valA, + Slvs_hEntity ptA, + Slvs_hEntity ptB, + Slvs_hEntity entityA, + Slvs_hEntity entityB + ) + +cpdef tuple quaternion_u(double qw, double qx, double qy, double qz) +cpdef tuple quaternion_v(double qw, double qx, double qy, double qz) +cpdef tuple quaternion_n(double qw, double qx, double qy, double qz) +cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz) + +cdef class Params: + + cdef vector[Slvs_hParam] param_list + + @staticmethod + cdef Params create(Slvs_hParam *p, size_t count) + +cdef class Entity: + + cdef int t + cdef Slvs_hEntity h, wp + cdef Slvs_hGroup g + cdef readonly Params params + + @staticmethod + cdef Entity create(Slvs_Entity *e) + + cpdef bint is_3d(self) + cpdef bint is_none(self) + cpdef bint is_point_2d(self) + cpdef bint is_point_3d(self) + cpdef bint is_point(self) + cpdef bint is_normal_2d(self) + cpdef bint is_normal_3d(self) + cpdef bint is_normal(self) + cpdef bint is_distance(self) + cpdef bint is_work_plane(self) + cpdef bint is_line_2d(self) + cpdef bint is_line_3d(self) + cpdef bint is_line(self) + cpdef bint is_cubic(self) + cpdef bint is_circle(self) + cpdef bint is_arc(self) + + +cdef class SolverSystem: + + cdef int dof_v + cdef Slvs_hGroup g + cdef vector[Slvs_Param] param_list + cdef vector[Slvs_Entity] entity_list + cdef vector[Slvs_Constraint] cons_list + cdef vector[Slvs_hConstraint] failed_list + + cpdef SolverSystem copy(self) + cpdef void clear(self) + cpdef void set_group(self, size_t g) + cpdef int group(self) + cpdef void set_params(self, Params p, object params) + cpdef list params(self, Params p) + cpdef int dof(self) + cpdef object constraints(self) + cpdef list failures(self) + cdef int solve_c(self) nogil + + cpdef size_t param_len(self) + cpdef size_t entity_len(self) + cpdef size_t cons_len(self) + + cpdef Entity create_2d_base(self) + cdef Slvs_hParam new_param(self, double val) nogil + cdef Slvs_hEntity eh(self) nogil + + cpdef Entity add_point_2d(self, double u, double v, Entity wp) + cpdef Entity add_point_3d(self, double x, double y, double z) + cpdef Entity add_normal_2d(self, Entity wp) + cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz) + cpdef Entity add_distance(self, double d, Entity wp) + cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp) + cpdef Entity add_line_3d(self, Entity p1, Entity p2) + cpdef Entity add_cubic(self, Entity p1, Entity p2, Entity p3, Entity p4, Entity wp) + cpdef Entity add_arc(self, Entity nm, Entity ct, Entity start, Entity end, Entity wp) + cpdef Entity add_circle(self, Entity nm, Entity ct, Entity radius, Entity wp) + cpdef Entity add_work_plane(self, Entity origin, Entity nm) + cpdef int add_constraint( + self, + int c_type, + Entity wp, + double v, + Entity p1, + Entity p2, + Entity e1, + Entity e2, + Entity e3 = *, + Entity e4 = *, + int other = *, + int other2 = * + ) + + cpdef int coincident(self, Entity e1, Entity e2, Entity wp = *) + cpdef int distance(self, Entity e1, Entity e2, double value, Entity wp = *) + cpdef int equal(self, Entity e1, Entity e2, Entity wp = *) + cpdef int equal_angle(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp = *) + cpdef int equal_point_to_line(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp = *) + cpdef int ratio(self, Entity e1, Entity e2, double value, Entity wp = *) + cpdef int symmetric(self, Entity e1, Entity e2, Entity e3 = *, Entity wp = *) + cpdef int symmetric_h(self, Entity e1, Entity e2, Entity wp) + cpdef int symmetric_v(self, Entity e1, Entity e2, Entity wp) + cpdef int midpoint(self, Entity e1, Entity e2, Entity wp = *) + cpdef int horizontal(self, Entity e1, Entity wp) + cpdef int vertical(self, Entity e1, Entity wp) + cpdef int diameter(self, Entity e1, double value) + cpdef int same_orientation(self, Entity e1, Entity e2) + cpdef int angle(self, Entity e1, Entity e2, double value, Entity wp = *, bint inverse = *) + cpdef int perpendicular(self, Entity e1, Entity e2, Entity wp = *, bint inverse = *) + cpdef int parallel(self, Entity e1, Entity e2, Entity wp = *) + cpdef int tangent(self, Entity e1, Entity e2, Entity wp = *) + cpdef int distance_proj(self, Entity e1, Entity e2, double value) + cpdef int dragged(self, Entity e1, Entity wp = *) + cpdef int length_diff(self, Entity e1, Entity e2, double value, Entity wp = *) diff --git a/cython/solvespace/slvs.pyi b/cython/solvespace/slvs.pyi new file mode 100644 index 000000000..034daf9ae --- /dev/null +++ b/cython/solvespace/slvs.pyi @@ -0,0 +1,323 @@ +# -*- coding: utf-8 -*- + +from typing import Tuple, List, Sequence, Counter, ClassVar +from enum import IntEnum, auto + +def quaternion_u( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def quaternion_v( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def quaternion_n( + qw: float, + qx: float, + qy: float, + qz: float +) -> Tuple[float, float, float]: + ... + +def make_quaternion( + ux: float, + uy: float, + uz: float, + vx: float, + vy: float, + vz: float +) -> Tuple[float, float, float, float]: + ... + +class Constraint(IntEnum): + """Symbol of the constraint types.""" + POINTS_COINCIDENT = 100000 + PT_PT_DISTANCE = auto() + PT_PLANE_DISTANCE = auto() + PT_LINE_DISTANCE = auto() + PT_FACE_DISTANCE = auto() + PT_IN_PLANE = auto() + PT_ON_LINE = auto() + PT_ON_FACE = auto() + EQUAL_LENGTH_LINES = auto() + LENGTH_RATIO = auto() + EQ_LEN_PT_LINE_D = auto() + EQ_PT_LN_DISTANCES = auto() + EQUAL_ANGLE = auto() + EQUAL_LINE_ARC_LEN = auto() + SYMMETRIC = auto() + SYMMETRIC_HORIZ = auto() + SYMMETRIC_VERT = auto() + SYMMETRIC_LINE = auto() + AT_MIDPOINT = auto() + HORIZONTAL = auto() + VERTICAL = auto() + DIAMETER = auto() + PT_ON_CIRCLE = auto() + SAME_ORIENTATION = auto() + ANGLE = auto() + PARALLEL = auto() + PERPENDICULAR = auto() + ARC_LINE_TANGENT = auto() + CUBIC_LINE_TANGENT = auto() + EQUAL_RADIUS = auto() + PROJ_PT_DISTANCE = auto() + WHERE_DRAGGED = auto() + CURVE_CURVE_TANGENT = auto() + LENGTH_DIFFERENCE = auto() + + +class ResultFlag(IntEnum): + """Symbol of the result flags.""" + OKAY = 0 + INCONSISTENT = auto() + DIDNT_CONVERGE = auto() + TOO_MANY_UNKNOWNS = auto() + +class Params: + pass + +class Entity: + + FREE_IN_3D: ClassVar[Entity] = ... + NONE: ClassVar[Entity] = ... + params: Params + + def is_3d(self) -> bool: + ... + + def is_none(self) -> bool: + ... + + def is_point_2d(self) -> bool: + ... + + def is_point_3d(self) -> bool: + ... + + def is_point(self) -> bool: + ... + + def is_normal_2d(self) -> bool: + ... + + def is_normal_3d(self) -> bool: + ... + + def is_normal(self) -> bool: + ... + + def is_distance(self) -> bool: + ... + + def is_work_plane(self) -> bool: + ... + + def is_line_2d(self) -> bool: + ... + + def is_line_3d(self) -> bool: + ... + + def is_line(self) -> bool: + ... + + def is_cubic(self) -> bool: + ... + + def is_circle(self) -> bool: + ... + + def is_arc(self) -> bool: + ... + +class SolverSystem: + + def __init__(self) -> None: + """Create a solver system.""" + ... + + def __reduce__(self): + ... + + def entity(self, i: int) -> Entity: + ... + + def copy(self) -> SolverSystem: + ... + + def clear(self) -> None: + ... + + def set_group(self, g: int) -> None: + ... + + def group(self) -> int: + ... + + def set_params(self, p: Params, params: Sequence[float]) -> None: + ... + + def params(self, p: Params) -> List[float]: + ... + + def dof(self) -> int: + ... + + def constraints(self) -> Counter[str]: + ... + + def failures(self) -> List[int]: + ... + + def solve(self) -> int: + ... + + def param_len(self) -> int: + ... + + def entity_len(self) -> int: + ... + + def cons_len(self) -> int: + ... + + def create_2d_base(self) -> Entity: + ... + + def add_point_2d(self, u: float, v: float, wp: Entity) -> Entity: + ... + + def add_point_3d(self, x: float, y: float, z: float) -> Entity: + ... + + def add_normal_2d(self, wp: Entity) -> Entity: + ... + + def add_normal_3d(self, qw: float, qx: float, qy: float, qz: float) -> Entity: + ... + + def add_distance(self, d: float, wp: Entity) -> Entity: + ... + + def add_line_2d(self, p1: Entity, p2: Entity, wp: Entity) -> Entity: + ... + + def add_line_3d(self, p1: Entity, p2: Entity) -> Entity: + ... + + def add_cubic(self, p1: Entity, p2: Entity, p3: Entity, p4: Entity, wp: Entity) -> Entity: + ... + + def add_arc(self, nm: Entity, ct: Entity, start: Entity, end: Entity, wp: Entity) -> Entity: + ... + + def add_circle(self, nm: Entity, ct: Entity, radius: Entity, wp: Entity) -> Entity: + ... + + def add_work_plane(self, origin: Entity, nm: Entity) -> Entity: + ... + + def add_constraint( + self, + c_type: int, + wp: Entity, + v: float, + p1: Entity, + p2: Entity, + e1: Entity, + e2: Entity, + e3: Entity = Entity.NONE, + e4: Entity = Entity.NONE, + other: int = 0, + other2: int = 0 + ) -> int: + ... + + def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def distance( + self, + e1: Entity, + e2: Entity, + value: float, + wp: Entity = Entity.FREE_IN_3D + ) -> int: + ... + + def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def equal_angle( + self, + e1: Entity, + e2: Entity, + e3: Entity, + e4: Entity, + wp: Entity = Entity.FREE_IN_3D + ) -> int: + ... + + def equal_point_to_line( + self, + e1: Entity, + e2: Entity, + e3: Entity, + e4: Entity, + wp: Entity = Entity.FREE_IN_3D + ) -> int: + ... + + def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def symmetric(self, e1: Entity, e2: Entity, e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> int: + ... + + def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> int: + ... + + def midpoint(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def horizontal(self, e1: Entity, wp: Entity) -> int: + ... + + def vertical(self, e1: Entity, wp: Entity) -> int: + ... + + def diameter(self, e1: Entity, value: float) -> int: + ... + + def same_orientation(self, e1: Entity, e2: Entity) -> int: + ... + + def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> int: + ... + + def perpendicular(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> int: + ... + + def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... + + def distance_proj(self, e1: Entity, e2: Entity, value: float) -> int: + ... + + def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: + ... diff --git a/cython/solvespace/slvs.pyx b/cython/solvespace/slvs.pyx new file mode 100644 index 000000000..b984c7aef --- /dev/null +++ b/cython/solvespace/slvs.pyx @@ -0,0 +1,1008 @@ +# -*- coding: utf-8 -*- +# cython: language_level=3 + +"""Wrapper source code of Solvespace. + +author: Yuan Chang +copyright: Copyright (C) 2016-2019 +license: GPLv3+ +email: pyslvs@gmail.com +""" + +from cpython.object cimport Py_EQ, Py_NE +from enum import IntEnum, auto +from collections import Counter + + +def _create_sys(dof_v, g, param_list, entity_list, cons_list): + cdef SolverSystem s = SolverSystem.__new__(SolverSystem) + s.dof_v = dof_v + s.g = g + s.param_list = param_list + s.entity_list = entity_list + s.cons_list = cons_list + return s + + +cpdef tuple quaternion_u(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of U axis. + + Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of + quaternion. + """ + cdef double x, y, z + Slvs_QuaternionU(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple quaternion_v(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of V axis. + + Signature is same as [quaternion_u](#quaternion_u). + """ + cdef double x, y, z + Slvs_QuaternionV(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple quaternion_n(double qw, double qx, double qy, double qz): + """Input quaternion, return unit vector of normal. + + Signature is same as [quaternion_u](#quaternion_u). + """ + cdef double x, y, z + Slvs_QuaternionN(qw, qx, qy, qz, &x, &y, &z) + return x, y, z + + +cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz): + """Input two unit vector, return quaternion. + + Where `ux`, `uy`, `uz` are corresponded to the value of U vector; + `vx`, `vy`, `vz` are corresponded to the value of V vector. + """ + cdef double qw, qx, qy, qz + Slvs_MakeQuaternion(ux, uy, uz, vx, vy, vz, &qw, &qx, &qy, &qz) + return qw, qx, qy, qz + + +class Constraint(IntEnum): + """Symbol of the constraint types.""" + # Expose macro of constraint types + POINTS_COINCIDENT = 100000 + PT_PT_DISTANCE = auto() + PT_PLANE_DISTANCE = auto() + PT_LINE_DISTANCE = auto() + PT_FACE_DISTANCE = auto() + PT_IN_PLANE = auto() + PT_ON_LINE = auto() + PT_ON_FACE = auto() + EQUAL_LENGTH_LINES = auto() + LENGTH_RATIO = auto() + EQ_LEN_PT_LINE_D = auto() + EQ_PT_LN_DISTANCES = auto() + EQUAL_ANGLE = auto() + EQUAL_LINE_ARC_LEN = auto() + SYMMETRIC = auto() + SYMMETRIC_HORIZ = auto() + SYMMETRIC_VERT = auto() + SYMMETRIC_LINE = auto() + AT_MIDPOINT = auto() + HORIZONTAL = auto() + VERTICAL = auto() + DIAMETER = auto() + PT_ON_CIRCLE = auto() + SAME_ORIENTATION = auto() + ANGLE = auto() + PARALLEL = auto() + PERPENDICULAR = auto() + ARC_LINE_TANGENT = auto() + CUBIC_LINE_TANGENT = auto() + EQUAL_RADIUS = auto() + PROJ_PT_DISTANCE = auto() + WHERE_DRAGGED = auto() + CURVE_CURVE_TANGENT = auto() + LENGTH_DIFFERENCE = auto() + + +class ResultFlag(IntEnum): + """Symbol of the result flags.""" + # Expose macro of result flags + OKAY = 0 + INCONSISTENT = auto() + DIDNT_CONVERGE = auto() + TOO_MANY_UNKNOWNS = auto() + + +cdef class Params: + + """Python object to handle multiple parameter handles.""" + + @staticmethod + cdef Params create(Slvs_hParam *p, size_t count): + """Constructor.""" + cdef Params params = Params.__new__(Params) + cdef size_t i + for i in range(count): + params.param_list.push_back(p[i]) + return params + + def __richcmp__(self, Params other, int op) -> bint: + """Compare the parameters.""" + if op == Py_EQ: + return self.param_list == other.param_list + elif op == Py_NE: + return self.param_list != other.param_list + else: + raise TypeError( + f"'{op}' not support between instances of " + f"{type(self)} and {type(other)}" + ) + + def __repr__(self) -> str: + m = f"{type(self).__name__}([" + cdef size_t i + cdef size_t s = self.param_list.size() + for i in range(s): + m += str(self.param_list[i]) + if i != s - 1: + m += ", " + m += "])" + return m + +# A virtual work plane that present 3D entity or constraint. +cdef Entity _E_FREE_IN_3D = Entity.__new__(Entity) +_E_FREE_IN_3D.t = SLVS_E_WORKPLANE +_E_FREE_IN_3D.h = SLVS_FREE_IN_3D +_E_FREE_IN_3D.g = 0 +_E_FREE_IN_3D.params = Params.create(NULL, 0) + +# A "None" entity used to fill in constraint option. +cdef Entity _E_NONE = Entity.__new__(Entity) +_E_NONE.t = 0 +_E_NONE.h = 0 +_E_NONE.g = 0 +_E_NONE.params = Params.create(NULL, 0) + +# Entity names +_NAME_OF_ENTITIES = { + SLVS_E_POINT_IN_3D: "point 3d", + SLVS_E_POINT_IN_2D: "point 2d", + SLVS_E_NORMAL_IN_2D: "normal 2d", + SLVS_E_NORMAL_IN_3D: "normal 3d", + SLVS_E_DISTANCE: "distance", + SLVS_E_WORKPLANE: "work plane", + SLVS_E_LINE_SEGMENT: "line segment", + SLVS_E_CUBIC: "cubic", + SLVS_E_CIRCLE: "circle", + SLVS_E_ARC_OF_CIRCLE: "arc", +} + +# Constraint names +_NAME_OF_CONSTRAINTS = { + SLVS_C_POINTS_COINCIDENT: "points coincident", + SLVS_C_PT_PT_DISTANCE: "point point distance", + SLVS_C_PT_PLANE_DISTANCE: "point plane distance", + SLVS_C_PT_LINE_DISTANCE: "point line distance", + SLVS_C_PT_FACE_DISTANCE: "point face distance", + SLVS_C_PT_IN_PLANE: "point in plane", + SLVS_C_PT_ON_LINE: "point on line", + SLVS_C_PT_ON_FACE: "point on face", + SLVS_C_EQUAL_LENGTH_LINES: "equal length lines", + SLVS_C_LENGTH_RATIO: "length ratio", + SLVS_C_EQ_LEN_PT_LINE_D: "equal length point line distance", + SLVS_C_EQ_PT_LN_DISTANCES: "equal point line distance", + SLVS_C_EQUAL_ANGLE: "equal angle", + SLVS_C_EQUAL_LINE_ARC_LEN: "equal line arc length", + SLVS_C_SYMMETRIC: "symmetric", + SLVS_C_SYMMETRIC_HORIZ: "symmetric horizontal", + SLVS_C_SYMMETRIC_VERT: "symmetric vertical", + SLVS_C_SYMMETRIC_LINE: "symmetric line", + SLVS_C_AT_MIDPOINT: "at midpoint", + SLVS_C_HORIZONTAL: "horizontal", + SLVS_C_VERTICAL: "vertical", + SLVS_C_DIAMETER: "diameter", + SLVS_C_PT_ON_CIRCLE: "point on circle", + SLVS_C_SAME_ORIENTATION: "same orientation", + SLVS_C_ANGLE: "angle", + SLVS_C_PARALLEL: "parallel", + SLVS_C_PERPENDICULAR: "perpendicular", + SLVS_C_ARC_LINE_TANGENT: "arc line tangent", + SLVS_C_CUBIC_LINE_TANGENT: "cubic line tangent", + SLVS_C_EQUAL_RADIUS: "equal radius", + SLVS_C_PROJ_PT_DISTANCE: "project point distance", + SLVS_C_WHERE_DRAGGED: "where dragged", + SLVS_C_CURVE_CURVE_TANGENT: "curve curve tangent", + SLVS_C_LENGTH_DIFFERENCE: "length difference", +} + + +cdef class Entity: + + """The handles of entities. + + This handle **should** be dropped after system removed. + """ + + FREE_IN_3D = _E_FREE_IN_3D + NONE = _E_NONE + + @staticmethod + cdef Entity create(Slvs_Entity *e): + """Constructor.""" + cdef Entity entity = Entity.__new__(Entity) + with nogil: + entity.t = e.type + entity.h = e.h + entity.wp = e.wrkpl + entity.g = e.group + cdef size_t p_size + if e.type == SLVS_E_DISTANCE: + p_size = 1 + elif e.type == SLVS_E_POINT_IN_2D: + p_size = 2 + elif e.type == SLVS_E_POINT_IN_3D: + p_size = 3 + elif e.type == SLVS_E_NORMAL_IN_3D: + p_size = 4 + else: + p_size = 0 + entity.params = Params.create(e.param, p_size) + return entity + + def __richcmp__(self, Entity other, int op) -> bint: + """Compare the entities.""" + if op == Py_EQ: + return ( + self.t == other.t and + self.h == other.h and + self.wp == other.wp and + self.g == other.g and + self.params == other.params + ) + elif op == Py_NE: + return not (self == other) + else: + raise TypeError( + f"'{op}' not support between instances of " + f"{type(self)} and {type(other)}" + ) + + cpdef bint is_3d(self): + """Return True if this is a 3D entity.""" + return self.wp == SLVS_FREE_IN_3D + + cpdef bint is_none(self): + """Return True if this is a empty entity.""" + return self.h == 0 + + cpdef bint is_point_2d(self): + """Return True if this is a 2D point.""" + return self.t == SLVS_E_POINT_IN_2D + + cpdef bint is_point_3d(self): + """Return True if this is a 3D point.""" + return self.t == SLVS_E_POINT_IN_3D + + cpdef bint is_point(self): + """Return True if this is a point.""" + return self.is_point_2d() or self.is_point_3d() + + cpdef bint is_normal_2d(self): + """Return True if this is a 2D normal.""" + return self.t == SLVS_E_NORMAL_IN_2D + + cpdef bint is_normal_3d(self): + """Return True if this is a 3D normal.""" + return self.t == SLVS_E_NORMAL_IN_3D + + cpdef bint is_normal(self): + """Return True if this is a normal.""" + return self.is_normal_2d() or self.is_normal_3d() + + cpdef bint is_distance(self): + """Return True if this is a distance.""" + return self.t == SLVS_E_DISTANCE + + cpdef bint is_work_plane(self): + """Return True if this is a work plane.""" + return self.t == SLVS_E_WORKPLANE + + cpdef bint is_line_2d(self): + """Return True if this is a 2D line.""" + return self.is_line() and not self.is_3d() + + cpdef bint is_line_3d(self): + """Return True if this is a 3D line.""" + return self.is_line() and self.is_3d() + + cpdef bint is_line(self): + """Return True if this is a line.""" + return self.t == SLVS_E_LINE_SEGMENT + + cpdef bint is_cubic(self): + """Return True if this is a cubic.""" + return self.t == SLVS_E_CUBIC + + cpdef bint is_circle(self): + """Return True if this is a circle.""" + return self.t == SLVS_E_CIRCLE + + cpdef bint is_arc(self): + """Return True if this is a arc.""" + return self.t == SLVS_E_ARC_OF_CIRCLE + + def __repr__(self) -> str: + cdef int h = self.h + cdef int g = self.g + cdef str t = _NAME_OF_ENTITIES[self.t] + return ( + f"{type(self).__name__}" + f"(handle={h}, group={g}, type=<{t}>, is_3d={self.is_3d()}, params={self.params})" + ) + + +cdef class SolverSystem: + + """A solver system of Solvespace. + + The operation of entities and constraints are using the methods of this class. + """ + + def __cinit__(self): + self.g = 0 + + def __reduce__(self): + return (_create_sys, (self.dof_v, self.g, self.param_list, self.entity_list, self.cons_list)) + + def entity(self, int i) -> Entity: + """Generate entity handle, it can only be used with this system. + + Negative index is allowed. + """ + if i < 0: + return Entity.create(&self.entity_list[self.entity_list.size() + i]) + elif i >= self.entity_list.size(): + raise IndexError(f"index {i} is out of bound {self.entity_list.size()}") + else: + return Entity.create(&self.entity_list[i]) + + cpdef SolverSystem copy(self): + """Copy the solver.""" + return _create_sys(self.dof_v, self.g, self.param_list, self.entity_list, self.cons_list) + + cpdef void clear(self): + """Clear the system.""" + self.dof_v = 0 + self.g = 0 + self.param_list.clear() + self.entity_list.clear() + self.cons_list.clear() + self.failed_list.clear() + + cpdef void set_group(self, size_t g): + """Set the current group (`g`).""" + self.g = g + + cpdef int group(self): + """Return the current group.""" + return self.g + + cpdef void set_params(self, Params p, object params): + """Set the parameters from a [Params] handle (`p`) belong to this system. + The values is come from `params`, length must be equal to the handle. + """ + params = list(params) + if p.param_list.size() != len(params): + raise ValueError(f"number of parameters {len(params)} are not match {p.param_list.size()}") + + cdef int i = 0 + cdef Slvs_hParam h + for h in p.param_list: + self.param_list[h - 1].val = params[i] + i += 1 + + cpdef list params(self, Params p): + """Get the parameters from a [Params] handle (`p`) belong to this + system. + The length of tuple is decided by handle. + """ + param_list = [] + cdef Slvs_hParam h + for h in p.param_list: + param_list.append(self.param_list[h - 1].val) + return param_list + + cpdef int dof(self): + """Return the degrees of freedom of current group. + Only can be called after solved. + """ + return self.dof_v + + cpdef object constraints(self): + """Return the number of each constraint type. + The name of constraints is represented by string. + """ + cons_list = [] + cdef Slvs_Constraint con + for con in self.cons_list: + cons_list.append(_NAME_OF_CONSTRAINTS[con.type]) + return Counter(cons_list) + + cpdef list failures(self): + """Return a list of failed constraint numbers.""" + return self.failed_list + + cdef int solve_c(self) nogil: + """Start the solving, return the result flag.""" + cdef Slvs_System sys + # Parameters + sys.param = self.param_list.data() + sys.params = self.param_list.size() + # Entities + sys.entity = self.entity_list.data() + sys.entities = self.entity_list.size() + # Constraints + sys.constraint = self.cons_list.data() + sys.constraints = self.cons_list.size() + # Faileds + self.failed_list = vector[Slvs_hConstraint](self.cons_list.size(), 0) + sys.failed = self.failed_list.data() + sys.faileds = self.failed_list.size() + # Solve + Slvs_Solve(&sys, self.g) + self.failed_list.resize(sys.faileds) + self.dof_v = sys.dof + return sys.result + + def solve(self): + return ResultFlag(self.solve_c()) + + cpdef size_t param_len(self): + """The length of parameter list.""" + return self.param_list.size() + + cpdef size_t entity_len(self): + """The length of parameter list.""" + return self.entity_list.size() + + cpdef size_t cons_len(self): + """The length of parameter list.""" + return self.cons_list.size() + + cpdef Entity create_2d_base(self): + """Create a 2D system on current group, + return the handle of work plane. + """ + cdef double qw, qx, qy, qz + qw, qx, qy, qz = make_quaternion(1, 0, 0, 0, 1, 0) + cdef Entity nm = self.add_normal_3d(qw, qx, qy, qz) + return self.add_work_plane(self.add_point_3d(0, 0, 0), nm) + + cdef inline Slvs_hParam new_param(self, double val) nogil: + """Add a parameter.""" + cdef Slvs_hParam h = self.param_list.size() + 1 + self.param_list.push_back(Slvs_MakeParam(h, self.g, val)) + return h + + cdef inline Slvs_hEntity eh(self) nogil: + """Return new entity handle.""" + return self.entity_list.size() + 1 + + cpdef Entity add_point_2d(self, double u, double v, Entity wp): + """Add a 2D point to specific work plane (`wp`) then return the handle. + + Where `u`, `v` are corresponded to the value of U, V axis on the work + plane. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + + cdef Slvs_hParam u_p = self.new_param(u) + cdef Slvs_hParam v_p = self.new_param(v) + self.entity_list.push_back(Slvs_MakePoint2d(self.eh(), self.g, wp.h, u_p, v_p)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_point_3d(self, double x, double y, double z): + """Add a 3D point then return the handle. + + Where `x`, `y`, `z` are corresponded to the value of X, Y, Z axis. + """ + cdef Slvs_hParam x_p = self.new_param(x) + cdef Slvs_hParam y_p = self.new_param(y) + cdef Slvs_hParam z_p = self.new_param(z) + self.entity_list.push_back(Slvs_MakePoint3d(self.eh(), self.g, x_p, y_p, z_p)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_normal_2d(self, Entity wp): + """Add a 2D normal orthogonal to specific work plane (`wp`) + then return the handle. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + + self.entity_list.push_back(Slvs_MakeNormal2d(self.eh(), self.g, wp.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz): + """Add a 3D normal from quaternion then return the handle. + + Where `qw`, `qx`, `qy`, `qz` are corresponded to + the W, X, Y, Z value of quaternion. + """ + cdef Slvs_hParam w_p = self.new_param(qw) + cdef Slvs_hParam x_p = self.new_param(qx) + cdef Slvs_hParam y_p = self.new_param(qy) + cdef Slvs_hParam z_p = self.new_param(qz) + self.entity_list.push_back(Slvs_MakeNormal3d( + self.eh(), self.g, w_p, x_p, y_p, z_p)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_distance(self, double d, Entity wp): + """Add a distance to specific work plane (`wp`) then return the handle. + + Where `d` is distance value. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + + cdef Slvs_hParam d_p = self.new_param(d) + self.entity_list.push_back(Slvs_MakeDistance( + self.eh(), self.g, wp.h, d_p)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp): + """Add a 2D line to specific work plane (`wp`) then return the handle. + + Where `p1` is the start point; `p2` is the end point. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + if p1 is None or not p1.is_point_2d(): + raise TypeError(f"{p1} is not a 2d point") + if p2 is None or not p2.is_point_2d(): + raise TypeError(f"{p2} is not a 2d point") + + self.entity_list.push_back(Slvs_MakeLineSegment( + self.eh(), self.g, wp.h, p1.h, p2.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_line_3d(self, Entity p1, Entity p2): + """Add a 3D line then return the handle. + + Where `p1` is the start point; `p2` is the end point. + """ + if p1 is None or not p1.is_point_3d(): + raise TypeError(f"{p1} is not a 3d point") + if p2 is None or not p2.is_point_3d(): + raise TypeError(f"{p2} is not a 3d point") + + self.entity_list.push_back(Slvs_MakeLineSegment( + self.eh(), self.g, SLVS_FREE_IN_3D, p1.h, p2.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_cubic(self, Entity p1, Entity p2, Entity p3, Entity p4, Entity wp): + """Add a cubic curve to specific work plane (`wp`) then return the + handle. + + Where `p1` to `p4` is the control points. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + if p1 is None or not p1.is_point_2d(): + raise TypeError(f"{p1} is not a 2d point") + if p2 is None or not p2.is_point_2d(): + raise TypeError(f"{p2} is not a 2d point") + if p3 is None or not p3.is_point_2d(): + raise TypeError(f"{p3} is not a 2d point") + if p4 is None or not p4.is_point_2d(): + raise TypeError(f"{p4} is not a 2d point") + + self.entity_list.push_back(Slvs_MakeCubic( + self.eh(), self.g, wp.h, p1.h, p2.h, p3.h, p4.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_arc(self, Entity nm, Entity ct, Entity start, Entity end, Entity wp): + """Add an arc to specific work plane (`wp`) then return the handle. + + Where `nm` is the orthogonal normal; `ct` is the center point; + `start` is the start point; `end` is the end point. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + if nm is None or not nm.is_normal_3d(): + raise TypeError(f"{nm} is not a 3d normal") + if ct is None or not ct.is_point_2d(): + raise TypeError(f"{ct} is not a 2d point") + if start is None or not start.is_point_2d(): + raise TypeError(f"{start} is not a 2d point") + if end is None or not end.is_point_2d(): + raise TypeError(f"{end} is not a 2d point") + self.entity_list.push_back(Slvs_MakeArcOfCircle( + self.eh(), self.g, wp.h, nm.h, ct.h, start.h, end.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_circle(self, Entity nm, Entity ct, Entity radius, Entity wp): + """Add an circle to specific work plane (`wp`) then return the handle. + + Where `nm` is the orthogonal normal; + `ct` is the center point; + `radius` is the distance value represent radius. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + if nm is None or not nm.is_normal_3d(): + raise TypeError(f"{nm} is not a 3d normal") + if ct is None or not ct.is_point_2d(): + raise TypeError(f"{ct} is not a 2d point") + if radius is None or not radius.is_distance(): + raise TypeError(f"{radius} is not a distance") + + self.entity_list.push_back(Slvs_MakeCircle(self.eh(), self.g, wp.h, + ct.h, nm.h, radius.h)) + return Entity.create(&self.entity_list.back()) + + cpdef Entity add_work_plane(self, Entity origin, Entity nm): + """Add a work plane then return the handle. + + Where `origin` is the origin point of the plane; + `nm` is the orthogonal normal. + """ + if origin is None or origin.t != SLVS_E_POINT_IN_3D: + raise TypeError(f"{origin} is not a 3d point") + if nm is None or nm.t != SLVS_E_NORMAL_IN_3D: + raise TypeError(f"{nm} is not a 3d normal") + + self.entity_list.push_back(Slvs_MakeWorkplane(self.eh(), self.g, origin.h, nm.h)) + return Entity.create(&self.entity_list.back()) + + cpdef int add_constraint( + self, + int c_type, + Entity wp, + double v, + Entity p1, + Entity p2, + Entity e1, + Entity e2, + Entity e3 = _E_NONE, + Entity e4 = _E_NONE, + int other = 0, + int other2 = 0 + ): + """Add a constraint by type code `c_type`. + This is an origin function mapping to different constraint methods. + + Where `wp` represents work plane; `v` represents constraint value; + `p1` and `p2` represent point entities; `e1` to `e4` represent other + types of entity; + `other` and `other2` are control options of the constraint. + """ + if wp is None or not wp.is_work_plane(): + raise TypeError(f"{wp} is not a work plane") + + cdef Entity e + for e in (p1, p2): + if e is None or not (e.is_none() or e.is_point()): + raise TypeError(f"{e} is not a point") + for e in (e1, e2, e3, e4): + if e is None: + raise TypeError(f"{e} is not a entity") + + cdef Slvs_Constraint c + c.h = self.cons_list.size() + 1 + c.group = self.g + c.type = c_type + c.wrkpl = wp.h + c.valA = v + c.ptA = p1.h + c.ptB = p2.h + c.entityA = e1.h + c.entityB = e2.h + c.entityC = e3.h + c.entityD = e4.h + c.other = other + c.other2 = other2 + self.cons_list.push_back(c) + return self.cons_list.size() + + ##### + # Constraint methods. + ##### + + cpdef int coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): + """Coincident two entities. + + | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | + |:---------------:|:---------------:|:-----------------:| + | [is_point] | [is_point] | Optional | + | [is_point] | [is_work_plane] | [Entity.FREE_IN_3D] | + | [is_point] | [is_line] | Optional | + | [is_point] | [is_circle] | Optional | + """ + if e1.is_point() and e2.is_point(): + return self.add_constraint(SLVS_C_POINTS_COINCIDENT, wp, 0., e1, e2, _E_NONE, _E_NONE) + elif e1.is_point() and e2.is_work_plane(): + return self.add_constraint(SLVS_C_PT_IN_PLANE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) + elif e1.is_point() and e2.is_line(): + return self.add_constraint(SLVS_C_PT_ON_LINE, wp, 0., e1, _E_NONE, e2, _E_NONE) + elif e1.is_point() and e2.is_circle(): + return self.add_constraint(SLVS_C_PT_ON_CIRCLE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int distance( + self, + Entity e1, + Entity e2, + double value, + Entity wp = _E_FREE_IN_3D + ): + """Distance constraint between two entities. + + If `value` is equal to zero, then turn into + [coincident](#solversystemcoincident) + + | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | + |:---------------:|:---------------:|:-----------------:| + | [is_point] | [is_point] | Optional | + | [is_point] | [is_work_plane] | [Entity.FREE_IN_3D] | + | [is_point] | [is_line] | Optional | + """ + if value == 0.: + return self.coincident(e1, e2, wp) + if e1.is_point() and e2.is_point(): + return self.add_constraint(SLVS_C_PT_PT_DISTANCE, wp, value, e1, e2, _E_NONE, _E_NONE) + elif e1.is_point() and e2.is_work_plane() and wp is _E_FREE_IN_3D: + return self.add_constraint(SLVS_C_PT_PLANE_DISTANCE, e2, value, e1, _E_NONE, e2, _E_NONE) + elif e1.is_point() and e2.is_line(): + return self.add_constraint(SLVS_C_PT_LINE_DISTANCE, wp, value, e1, _E_NONE, e2, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int equal(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): + """Equal constraint between two entities. + + | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | + |:---------------:|:---------------:|:-----------------:| + | [is_line] | [is_line] | Optional | + | [is_line] | [is_arc] | Optional | + | [is_line] | [is_circle] | Optional | + | [is_arc] | [is_arc] | Optional | + | [is_arc] | [is_circle] | Optional | + | [is_circle] | [is_circle] | Optional | + | [is_circle] | [is_arc] | Optional | + """ + if e1.is_line() and e2.is_line(): + return self.add_constraint(SLVS_C_EQUAL_LENGTH_LINES, wp, 0., _E_NONE, + _E_NONE, e1, e2) + elif e1.is_line() and (e2.is_arc() or e2.is_circle()): + return self.add_constraint(SLVS_C_EQUAL_LINE_ARC_LEN, wp, 0., _E_NONE, + _E_NONE, e1, e2) + elif (e1.is_arc() or e1.is_circle()) and (e2.is_arc() or e2.is_circle()): + return self.add_constraint(SLVS_C_EQUAL_RADIUS, wp, 0., _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int equal_angle( + self, + Entity e1, + Entity e2, + Entity e3, + Entity e4, + Entity wp = _E_FREE_IN_3D + ): + """Constraint that 2D line 1 (`e1`) and line 2 (`e2`), + line 3 (`e3`) and line 4 (`e4`) must have same included angle on work + plane `wp`. + """ + if e1.is_line_2d() and e2.is_line_2d() and e3.is_line_2d() and e4.is_line_2d(): + return self.add_constraint(SLVS_C_EQUAL_ANGLE, wp, 0., _E_NONE, _E_NONE, + e1, e2, e3, e4) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {e4}, {wp}") + + cpdef int equal_point_to_line( + self, + Entity e1, + Entity e2, + Entity e3, + Entity e4, + Entity wp = _E_FREE_IN_3D + ): + """Constraint that point 1 (`e1`) and line 1 (`e2`), + point 2 (`e3`) and line 2 (`e4`) must have same distance on work plane `wp`. + """ + if e1.is_point_2d() and e2.is_line_2d() and e3.is_point_2d() and e4.is_line_2d(): + return self.add_constraint(SLVS_C_EQ_PT_LN_DISTANCES, wp, 0., e1, e3, e2, e4) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {e4}, {wp}") + + cpdef int ratio(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): + """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`).""" + if e1.is_line_2d() and e2.is_line_2d(): + return self.add_constraint(SLVS_C_LENGTH_RATIO, wp, value, _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int symmetric( + self, + Entity e1, + Entity e2, + Entity e3 = _E_NONE, + Entity wp = _E_FREE_IN_3D + ): + """Symmetric constraint between two points. + + | Entity 1 (`e1`) | Entity 2 (`e2`) | Entity 3 (`e3`) | Work plane (`wp`) | + |:---------------:|:---------------:|:---------------:|:-----------------:| + | [is_point_3d] | [is_point_3d] | [is_work_plane] | [Entity.FREE_IN_3D] | + | [is_point_2d] | [is_point_2d] | [is_work_plane] | [Entity.FREE_IN_3D] | + | [is_point_2d] | [is_point_2d] | [is_line_2d] | not [Entity.FREE_IN_3D] | + """ + if e1.is_point_3d() and e2.is_point_3d() and e3.is_work_plane() and wp is _E_FREE_IN_3D: + return self.add_constraint(SLVS_C_SYMMETRIC, wp, 0., e1, e2, e3, _E_NONE) + elif e1.is_point_2d() and e2.is_point_2d() and e3.is_work_plane() and wp is _E_FREE_IN_3D: + return self.add_constraint(SLVS_C_SYMMETRIC, e3, 0., e1, e2, e3, _E_NONE) + elif e1.is_point_2d() and e2.is_point_2d() and e3.is_line_2d(): + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + return self.add_constraint(SLVS_C_SYMMETRIC_LINE, wp, 0., e1, e2, e3, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {wp}") + + cpdef int symmetric_h(self, Entity e1, Entity e2, Entity wp): + """Symmetric constraint between two 2D points (`e1` and `e2`) + with horizontal line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + if e1.is_point_2d() and e2.is_point_2d(): + return self.add_constraint(SLVS_C_SYMMETRIC_HORIZ, wp, 0., e1, e2, _E_NONE, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int symmetric_v(self, Entity e1, Entity e2, Entity wp): + """Symmetric constraint between two 2D points (`e1` and `e2`) + with vertical line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + if e1.is_point_2d() and e2.is_point_2d(): + return self.add_constraint(SLVS_C_SYMMETRIC_VERT, wp, 0., e1, e2, _E_NONE, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int midpoint( + self, + Entity e1, + Entity e2, + Entity wp = _E_FREE_IN_3D + ): + """Midpoint constraint between a point (`e1`) and + a line (`e2`) on work plane (`wp`). + """ + if e1.is_point() and e2.is_line(): + return self.add_constraint(SLVS_C_AT_MIDPOINT, wp, 0., e1, _E_NONE, e2, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int horizontal(self, Entity e1, Entity wp): + """Vertical constraint of a 2d point (`e1`) on + work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + if e1.is_line_2d(): + return self.add_constraint(SLVS_C_HORIZONTAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {wp}") + + cpdef int vertical(self, Entity e1, Entity wp): + """Vertical constraint of a 2d point (`e1`) on + work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + if e1.is_line_2d(): + return self.add_constraint(SLVS_C_VERTICAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {wp}") + + cpdef int diameter(self, Entity e1, double value): + """Diameter (`value`) constraint of a circular entities. + + | Entity 1 (`e1`) | Work plane (`wp`) | + |:---------------:|:-----------------:| + | [is_arc] | Optional | + | [is_circle] | Optional | + """ + if e1.is_arc() or e1.is_circle(): + return self.add_constraint(SLVS_C_DIAMETER, _E_FREE_IN_3D, value, _E_NONE, _E_NONE, + e1, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}") + + cpdef int same_orientation(self, Entity e1, Entity e2): + """Equal orientation constraint between two 3d normals (`e1` and `e2`).""" + if e1.is_normal_3d() and e2.is_normal_3d(): + return self.add_constraint(SLVS_C_SAME_ORIENTATION, _E_FREE_IN_3D, 0., + _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}") + + cpdef int angle(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D, bint inverse = False): + """Degrees angle (`value`) constraint between two 2d lines (`e1` and + `e2`) on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ + if e1.is_line_2d() and e2.is_line_2d(): + return self.add_constraint(SLVS_C_ANGLE, wp, value, _E_NONE, _E_NONE, + e1, e2, _E_NONE, _E_NONE, inverse) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int perpendicular(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D, bint inverse = False): + """Perpendicular constraint between two 2d lines (`e1` and `e2`) + on the work plane (`wp`) with `inverse` option. + """ + if e1.is_line_2d() and e2.is_line_2d(): + return self.add_constraint(SLVS_C_PERPENDICULAR, wp, 0., _E_NONE, _E_NONE, + e1, e2, _E_NONE, _E_NONE, inverse) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int parallel(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): + """Parallel constraint between two lines (`e1` and `e2`) on + the work plane (`wp`). + """ + if e1.is_line() and e2.is_line(): + return self.add_constraint(SLVS_C_PARALLEL, wp, 0., _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int tangent(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): + """Parallel constraint between two entities (`e1` and `e2`) on the + work plane (`wp`). + + | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | + |:---------------:|:---------------:|:-----------------:| + | [is_arc] | [is_line_2d] | not [Entity.FREE_IN_3D] | + | [is_cubic] | [is_line_3d] | [Entity.FREE_IN_3D] | + | [is_arc]/[is_cubic] | [is_arc]/[is_cubic] | not [Entity.FREE_IN_3D] | + """ + if e1.is_arc() and e2.is_line_2d(): + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + return self.add_constraint(SLVS_C_ARC_LINE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2) + elif e1.is_cubic() and e2.is_line_3d() and wp is _E_FREE_IN_3D: + return self.add_constraint(SLVS_C_CUBIC_LINE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2) + elif (e1.is_arc() or e1.is_cubic()) and (e2.is_arc() or e2.is_cubic()): + if wp is _E_FREE_IN_3D: + raise ValueError("this is a 2d constraint") + return self.add_constraint(SLVS_C_CURVE_CURVE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") + + cpdef int distance_proj(self, Entity e1, Entity e2, double value): + """Projected distance (`value`) constraint between + two 2D/3D points (`e1` and `e2`). + """ + if e1.is_point() and e2.is_point(): + return self.add_constraint(SLVS_C_PROJ_PT_DISTANCE, _E_FREE_IN_3D, + value, e1, e2, _E_NONE, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}") + + cpdef int dragged(self, Entity e1, Entity wp = _E_FREE_IN_3D): + """Dragged constraint of a point (`e1`) on the work plane (`wp`).""" + if e1.is_point(): + return self.add_constraint(SLVS_C_WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE) + else: + raise TypeError(f"unsupported entities: {e1}, {wp}") + + cpdef int length_diff(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): + """The length difference between two lines (`e1` and `e2`).""" + if e1.is_line() and e2.is_line(): + return self.add_constraint(SLVS_C_LENGTH_DIFFERENCE, wp, value, _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") diff --git a/cython/test/__init__.py b/cython/test/__init__.py new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/cython/test/__init__.py @@ -0,0 +1 @@ + diff --git a/cython/test/__main__.py b/cython/test/__main__.py new file mode 100644 index 000000000..98c0f1ab6 --- /dev/null +++ b/cython/test/__main__.py @@ -0,0 +1,4 @@ +from unittest import defaultTestLoader, TextTestRunner + +if __name__ == '__main__': + TextTestRunner().run(defaultTestLoader.discover('test')) diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py new file mode 100644 index 000000000..f6a0b8bbf --- /dev/null +++ b/cython/test/test_slvs.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- + +"""This module will test the functions of Solvespace.""" + +__author__ = "Yuan Chang" +__copyright__ = "Copyright (C) 2016-2019" +__license__ = "GPLv3+" +__email__ = "pyslvs@gmail.com" + +from unittest import TestCase +from math import radians +from solvespace import ResultFlag, SolverSystem, make_quaternion + + +class CoreTest(TestCase): + + def test_crank_rocker(self): + """Crank rocker example.""" + sys = SolverSystem() + wp = sys.create_2d_base() + p0 = sys.add_point_2d(0, 0, wp) + sys.dragged(p0, wp) + p1 = sys.add_point_2d(90, 0, wp) + sys.dragged(p1, wp) + line0 = sys.add_line_2d(p0, p1, wp) + p2 = sys.add_point_2d(20, 20, wp) + p3 = sys.add_point_2d(0, 10, wp) + p4 = sys.add_point_2d(30, 20, wp) + sys.distance(p2, p3, 40, wp) + sys.distance(p2, p4, 40, wp) + sys.distance(p3, p4, 70, wp) + sys.distance(p0, p3, 35, wp) + sys.distance(p1, p4, 70, wp) + line1 = sys.add_line_2d(p0, p3, wp) + sys.angle(line0, line1, 45, wp) + + sys_new = sys.copy() # solver copy test + + result_flag = sys.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys.params(p2.params) + self.assertAlmostEqual(39.54852, x, 4) + self.assertAlmostEqual(61.91009, y, 4) + + result_flag = sys_new.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys_new.params(p2.params) + self.assertAlmostEqual(39.54852, x, 4) + self.assertAlmostEqual(61.91009, y, 4) + + def test_involute(self): + """Involute example.""" + r = 10 + angle = 45 + + sys = SolverSystem() + wp = sys.create_2d_base() + p0 = sys.add_point_2d(0, 0, wp) + sys.dragged(p0, wp) + + p1 = sys.add_point_2d(0, 10, wp) + sys.distance(p0, p1, r, wp) + line0 = sys.add_line_2d(p0, p1, wp) + + p2 = sys.add_point_2d(10, 10, wp) + line1 = sys.add_line_2d(p1, p2, wp) + sys.distance(p1, p2, r * radians(angle), wp) + sys.perpendicular(line0, line1, wp, False) + + p3 = sys.add_point_2d(10, 0, wp) + sys.dragged(p3, wp) + line_base = sys.add_line_2d(p0, p3, wp) + sys.angle(line0, line_base, angle, wp) + + result_flag = sys.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys.params(p2.params) + self.assertAlmostEqual(12.62467, x, 4) + self.assertAlmostEqual(1.51746, y, 4) + + def test_jansen_linkage(self): + """Jansen's linkage example.""" + sys = SolverSystem() + wp = sys.create_2d_base() + p0 = sys.add_point_2d(0, 0, wp) + sys.dragged(p0, wp) + + p1 = sys.add_point_2d(0, 20, wp) + sys.distance(p0, p1, 15, wp) + line0 = sys.add_line_2d(p0, p1, wp) + + p2 = sys.add_point_2d(-38, -7.8, wp) + sys.dragged(p2, wp) + p3 = sys.add_point_2d(-50, 30, wp) + p4 = sys.add_point_2d(-70, -15, wp) + sys.distance(p2, p3, 41.5, wp) + sys.distance(p3, p4, 55.8, wp) + sys.distance(p2, p4, 40.1, wp) + + p5 = sys.add_point_2d(-50, -50, wp) + p6 = sys.add_point_2d(-10, -90, wp) + p7 = sys.add_point_2d(-20, -40, wp) + sys.distance(p5, p6, 65.7, wp) + sys.distance(p6, p7, 49.0, wp) + sys.distance(p5, p7, 36.7, wp) + + sys.distance(p1, p3, 50, wp) + sys.distance(p1, p7, 61.9, wp) + + p8 = sys.add_point_2d(20, 0, wp) + line_base = sys.add_line_2d(p0, p8, wp) + sys.angle(line0, line_base, 45, wp) + + result_flag = sys.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys.params(p8.params) + self.assertAlmostEqual(18.93036, x, 4) + self.assertAlmostEqual(13.63778, y, 4) + + def test_nut_cracker(self): + """Nut cracker example.""" + h0 = 0.5 + b0 = 0.75 + r0 = 0.25 + n1 = 1.5 + n2 = 2.3 + l0 = 3.25 + + sys = SolverSystem() + wp = sys.create_2d_base() + p0 = sys.add_point_2d(0, 0, wp) + sys.dragged(p0, wp) + + p1 = sys.add_point_2d(2, 2, wp) + p2 = sys.add_point_2d(2, 0, wp) + line0 = sys.add_line_2d(p0, p2, wp) + sys.horizontal(line0, wp) + + line1 = sys.add_line_2d(p1, p2, wp) + p3 = sys.add_point_2d(b0 / 2, h0, wp) + sys.dragged(p3, wp) + sys.distance(p3, line1, r0, wp) + sys.distance(p0, p1, n1, wp) + sys.distance(p1, p2, n2, wp) + + result_flag = sys.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, _ = sys.params(p2.params) + ans_min = x - b0 / 2 + ans_max = l0 - r0 - b0 / 2 + self.assertAlmostEqual(1.01576, ans_min, 4) + self.assertAlmostEqual(2.625, ans_max, 4) + + def test_pydemo(self): + """Some sample code for slvs.dll. + We draw some geometric entities, provide initial guesses for their positions, + and then constrain them. The solver calculates their new positions, + in order to satisfy the constraints. + + Copyright 2008-2013 Jonathan Westhues. + Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Solvespace bundled. + + An example of a constraint in 2d. In our first group, we create a workplane + along the reference frame's xy plane. In a second group, we create some + entities in that group and dimension them. + """ + sys = SolverSystem() + sys.set_group(1) + + # First, we create our workplane. Its origin corresponds to the origin + # of our base frame (x y z) = (0 0 0) + p101 = sys.add_point_3d(0, 0, 0) + # and it is parallel to the xy plane, so it has basis vectors (1 0 0) + # and (0 1 0). + qw, qx, qy, qz = make_quaternion(1, 0, 0, 0, 1, 0) + n102 = sys.add_normal_3d(qw, qx, qy, qz) + wp200 = sys.add_work_plane(p101, n102) + + # Now create a second group. We'll solve group 2, while leaving group 1 + # constant; so the workplane that we've created will be locked down, + # and the solver can't move it. + sys.set_group(2) + p301 = sys.add_point_2d(10, 20, wp200) + p302 = sys.add_point_2d(20, 10, wp200) + + # And we create a line segment with those endpoints. + l400 = sys.add_line_2d(p301, p302, wp200) + + # Now three more points. + p303 = sys.add_point_2d(100, 120, wp200) + p304 = sys.add_point_2d(120, 110, wp200) + p305 = sys.add_point_2d(115, 115, wp200) + + # And arc, centered at point 303, starting at point 304, ending at + # point 305. + a401 = sys.add_arc(n102, p303, p304, p305, wp200) + + # Now one more point, and a distance + p306 = sys.add_point_2d(200, 200, wp200) + d307 = sys.add_distance(30, wp200) + + # And a complete circle, centered at point 306 with radius equal to + # distance 307. The normal is 102, the same as our workplane. + c402 = sys.add_circle(n102, p306, d307, wp200) + + # The length of our line segment is 30.0 units. + sys.distance(p301, p302, 30, wp200) + + # And the distance from our line segment to the origin is 10.0 units. + sys.distance(p101, l400, 10, wp200) + + # And the line segment is vertical. + sys.vertical(l400, wp200) + + # And the distance from one endpoint to the origin is 15.0 units. + sys.distance(p301, p101, 15, wp200) + + # The arc and the circle have equal radius. + sys.equal(a401, c402, wp200) + + # The arc has radius 17.0 units. + sys.diameter(a401, 17 * 2) + + # If the solver fails, then ask it to report which constraints caused + # the problem. + + # And solve. + result_flag = sys.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys.params(p301.params) + self.assertAlmostEqual(10, x, 4) + self.assertAlmostEqual(11.18030, y, 4) + x, y = sys.params(p302.params) + self.assertAlmostEqual(10, x, 4) + self.assertAlmostEqual(-18.81966, y, 4) + x, y = sys.params(p303.params) + self.assertAlmostEqual(101.11418, x, 4) + self.assertAlmostEqual(119.04153, y, 4) + x, y = sys.params(p304.params) + self.assertAlmostEqual(116.47661, x, 4) + self.assertAlmostEqual(111.76171, y, 4) + x, y = sys.params(p305.params) + self.assertAlmostEqual(117.40922, x, 4) + self.assertAlmostEqual(114.19676, y, 4) + x, y = sys.params(p306.params) + self.assertAlmostEqual(200, x, 4) + self.assertAlmostEqual(200, y, 4) + x, = sys.params(d307.params) + self.assertAlmostEqual(17, x, 4) + self.assertEqual(6, sys.dof()) diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index 82ce2d302..6df720827 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -462,7 +462,7 @@ bool WriteFile(const Platform::Path &filename, const std::string &data) { #if defined(WIN32) const void *LoadResource(const std::string &name, size_t *size) { - HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), (LPWSTR)RT_RCDATA); ssassert(hres != NULL, "Cannot find resource"); HGLOBAL res = ::LoadResource(NULL, hres); ssassert(res != NULL, "Cannot load resource");