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 @@
+[](https://pypi.org/project/python-solvespace/)
+[](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");