initial version 0.1.0
|
|
@ -0,0 +1,179 @@
|
|||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
.tmp/
|
||||
.doc/
|
||||
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# 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/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Run svg2ild test",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "tests/test_frame.py",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"python.envFile": "${workspaceFolder}/.env"
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are 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.
|
||||
|
||||
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.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
|
||||
|
||||
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 Affero 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. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
|
||||
|
||||
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 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 work with which it is combined will remain governed by version 3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU Affero 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 Affero 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 Affero 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 Affero 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.
|
||||
|
||||
svg2ild
|
||||
Copyright (C) 2025 miklo
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
|
||||
|
||||
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 AGPL, see <http://www.gnu.org/licenses/>.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# svg2ild
|
||||
|
||||
Converts a series of .svg files to .ild animation for laser shows
|
||||
ILDA - image data transfer format for laser shows
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# ilda/__init__.py
|
||||
"""
|
||||
ILDA - Image Laser Data Archive format handling for laser shows
|
||||
|
||||
This package provides functionality to convert SVG files to ILDA animation format
|
||||
for laser show applications.
|
||||
|
||||
Main classes:
|
||||
- Frame: Represents a single ILDA frame with points and colors
|
||||
- Animation: Represents a sequence of frames for laser animation
|
||||
|
||||
Utilities:
|
||||
- order_paths_greedy_with_2opt: Optimizes path ordering to minimize blank travel
|
||||
- PathOrderResult: Container for ordered path results
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__author__ = "miklo"
|
||||
__email__ = "test@miklobit.com"
|
||||
__description__ = "Converts SVG files to ILDA animation format for laser shows"
|
||||
|
||||
# Import main classes
|
||||
from .frame import Frame
|
||||
from .animation import Animation
|
||||
|
||||
# Import utility functions and classes
|
||||
from .utils import order_paths_greedy_with_2opt, PathOrderResult
|
||||
|
||||
# Import commonly used constants and types
|
||||
from .frame import (
|
||||
DEFAULT_ilda_palette,
|
||||
ILDA_CANVAS,
|
||||
ILDA_HALF,
|
||||
INT16_MIN,
|
||||
INT16_MAX,
|
||||
PointF,
|
||||
PointI,
|
||||
RGB
|
||||
)
|
||||
|
||||
# Define what gets exported with "from ilda import *"
|
||||
__all__ = [
|
||||
# Main classes
|
||||
"Frame",
|
||||
"Animation",
|
||||
|
||||
# Utility functions
|
||||
"order_paths_greedy_with_2opt",
|
||||
"PathOrderResult",
|
||||
|
||||
# Constants
|
||||
"DEFAULT_ilda_palette",
|
||||
"ILDA_CANVAS",
|
||||
"ILDA_HALF",
|
||||
"INT16_MIN",
|
||||
"INT16_MAX",
|
||||
|
||||
# Type aliases
|
||||
"PointF",
|
||||
"PointI",
|
||||
"RGB",
|
||||
|
||||
# Package metadata
|
||||
"__version__",
|
||||
"__author__",
|
||||
"__email__",
|
||||
"__description__",
|
||||
]
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
# ilda/animation.py
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
from .frame import Frame
|
||||
|
||||
|
||||
class Animation:
|
||||
"""
|
||||
A class representing an ILDA animation consisting of multiple frames (SVG files).
|
||||
It stores the default ILDA format (e.g. 0), company_name, and projector,
|
||||
which are used when creating sections for each frame.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
ilda_format: int = 0,
|
||||
company_name: str = "MIKLOBIT",
|
||||
projector: int = 0) -> None:
|
||||
self.ilda_format: int = ilda_format
|
||||
self.company_name: str = company_name
|
||||
self.projector: int = projector
|
||||
|
||||
self.frames: List[Frame] = []
|
||||
self.frame_names: List[str] = []
|
||||
|
||||
def set_ilda_format(self, fmt: int) -> None:
|
||||
self.ilda_format = fmt
|
||||
|
||||
def set_company_name(self, name: str) -> None:
|
||||
self.company_name = name
|
||||
|
||||
def set_projector(self, projector: int) -> None:
|
||||
self.projector = projector
|
||||
|
||||
def create_from_file(self,
|
||||
svg_path: str,
|
||||
simplify: bool = False,
|
||||
tol: float = 1.0,
|
||||
clear_existing: bool = True) -> None:
|
||||
"""
|
||||
Load a single SVG file and add a Frame object to the animation.
|
||||
The frame name is the file name (stem) truncated to 8 characters.
|
||||
The simplify and tol parameters are passed to Frame.read_svg.
|
||||
"""
|
||||
p = Path(svg_path)
|
||||
if clear_existing:
|
||||
self.frames = []
|
||||
self.frame_names = []
|
||||
|
||||
if not p.exists():
|
||||
raise FileNotFoundError(f"SVG file not found: {svg_path}")
|
||||
|
||||
frame = Frame()
|
||||
frame.read_svg(str(p), simplify=simplify, tol=tol)
|
||||
name8 = p.stem[:8]
|
||||
self.frames.append(frame)
|
||||
self.frame_names.append(name8)
|
||||
|
||||
def create_from_folder(self,
|
||||
folder_path: str,
|
||||
simplify: bool = False,
|
||||
tol: float = 1.0,
|
||||
recursive: bool = False,
|
||||
clear_existing: bool = True) -> None:
|
||||
"""
|
||||
Load all .svg files from the folder (alphabetically) and create Frame objects.
|
||||
If recursive=True, it searches subdirectories.
|
||||
"""
|
||||
p = Path(folder_path)
|
||||
if not p.exists() or not p.is_dir():
|
||||
raise NotADirectoryError(f"Folder not found: {folder_path}")
|
||||
|
||||
if clear_existing:
|
||||
self.frames = []
|
||||
self.frame_names = []
|
||||
|
||||
if recursive:
|
||||
glob_iter = p.rglob("*.svg")
|
||||
else:
|
||||
glob_iter = p.glob("*.svg")
|
||||
|
||||
files = sorted(list(glob_iter), key=lambda x: x.name.lower())
|
||||
for fp in files:
|
||||
try:
|
||||
self.create_from_file(str(fp), simplify=simplify, tol=tol, clear_existing=False)
|
||||
except Exception as e:
|
||||
print(f"Warning: failed to read SVG {fp}: {e}")
|
||||
|
||||
def _make_eof_header(self) -> bytes:
|
||||
"""
|
||||
Returns a 32-byte ILDA header indicating EOF (NumberOfRecords = 0).
|
||||
Uses the ilda_format/company_name/projector settings from the instance.
|
||||
"""
|
||||
b = bytearray(32)
|
||||
b[0:4] = b'ILDA'
|
||||
b[7] = int(self.ilda_format) & 0xFF
|
||||
name_enc = self.company_name.encode('ascii', errors='ignore')[:8]
|
||||
b[16:16+len(name_enc)] = name_enc # company name field (optional)
|
||||
b[24:26] = (0).to_bytes(2, byteorder='big', signed=False) # NumberOfRecords = 0 -> EOF
|
||||
b[30] = int(self.projector) & 0xFF
|
||||
return bytes(b)
|
||||
|
||||
def write_ild(self, out_path: str, overwrite: bool = True) -> None:
|
||||
"""
|
||||
Save the compiled .ilda file: concatenate all sections obtained from Frame.get_ilda
|
||||
and add a single EOF header.
|
||||
- Skip frames for which get_ilda() will throw a ValueError (e.g., no points).
|
||||
- FrameNumber and TotalFrames are set automatically.
|
||||
"""
|
||||
if not self.frames:
|
||||
raise RuntimeError("No frames to write. Use create_from_* first.")
|
||||
|
||||
outp = Path(out_path)
|
||||
if outp.exists() and not overwrite:
|
||||
raise FileExistsError(f"File exists: {out_path}")
|
||||
|
||||
total_frames = len(self.frames)
|
||||
bin_stream = bytearray()
|
||||
|
||||
for idx, frame in enumerate(self.frames):
|
||||
frame_name = self.frame_names[idx] if idx < len(self.frame_names) else f"{idx:08d}"
|
||||
try:
|
||||
section = frame.get_ilda(format_code=self.ilda_format,
|
||||
frame_name=frame_name,
|
||||
company_name=self.company_name,
|
||||
frame_number=idx,
|
||||
total_frames=total_frames,
|
||||
projector=self.projector)
|
||||
except ValueError as ve:
|
||||
# return ValueError if frame empty (--> header of 0 records = EOF)
|
||||
print(f"Info: skipping empty frame '{frame_name}': {ve}")
|
||||
continue
|
||||
bin_stream += section
|
||||
|
||||
bin_stream += self._make_eof_header()
|
||||
|
||||
with open(outp, 'wb') as f:
|
||||
f.write(bin_stream)
|
||||
|
|
@ -0,0 +1,637 @@
|
|||
# ilda/frame.py
|
||||
"""
|
||||
ILDA frame
|
||||
(ILDA - image data transfer format for laser shows)
|
||||
"""
|
||||
|
||||
import math
|
||||
import re
|
||||
from typing import List, Tuple, Dict, Any, Optional
|
||||
from xml.etree.ElementTree import Element, SubElement, ElementTree
|
||||
|
||||
from svgpathtools import svg2paths2, Path
|
||||
from shapely.geometry import LineString, box
|
||||
|
||||
import numpy as np
|
||||
from .utils import order_paths_greedy_with_2opt
|
||||
|
||||
|
||||
PointF = Tuple[float, float]
|
||||
PointI = Tuple[int, int, int] # x,y,z as signed 16-bit integers
|
||||
RGB = Tuple[int, int, int]
|
||||
|
||||
ILDA_CANVAS = 65536
|
||||
ILDA_HALF = ILDA_CANVAS // 2 # 32768
|
||||
INT16_MIN = -32768
|
||||
INT16_MAX = 32767
|
||||
|
||||
# Global/default palette (64 entries) as (index, (r,g,b))
|
||||
DEFAULT_ilda_palette: List[Tuple[int, RGB]] = [
|
||||
(0, (255, 0, 0)), (1, (255, 16, 0)), (2, (255, 32, 0)), (3, (255, 48, 0)),
|
||||
(4, (255, 64, 0)), (5, (255, 80, 0)), (6, (255, 96, 0)), (7, (255, 112, 0)),
|
||||
(8, (255, 128, 0)), (9, (255, 144, 0)), (10, (255, 160, 0)), (11, (255, 176, 0)),
|
||||
(12, (255, 192, 0)), (13, (255, 208, 0)), (14, (255, 224, 0)), (15, (255, 240, 0)),
|
||||
(16, (255, 255, 0)), (17, (224, 255, 0)), (18, (192, 255, 0)), (19, (160, 255, 0)),
|
||||
(20, (128, 255, 0)), (21, ( 96, 255, 0)), (22, ( 64, 255, 0)), (23, ( 32, 255, 0)),
|
||||
(24, ( 0, 255, 0)), (25, ( 0, 255, 36)), (26, ( 0, 255, 73)), (27, ( 0, 255, 109)),
|
||||
(28, ( 0, 255, 146)), (29, ( 0, 255, 182)), (30, ( 0, 255, 219)), (31, ( 0, 255, 255)),
|
||||
(32, ( 0, 227, 255)), (33, ( 0, 198, 255)), (34, ( 0, 170, 255)), (35, ( 0, 142, 255)),
|
||||
(36, ( 0, 113, 255)), (37, ( 0, 85, 255)), (38, ( 0, 56, 255)), (39, ( 0, 28, 255)),
|
||||
(40, ( 0, 0, 255)), (41, ( 32, 0, 255)), (42, ( 64, 0, 255)), (43, ( 96, 0, 255)),
|
||||
(44, (128, 0, 255)), (45, (160, 0, 255)), (46, (192, 0, 255)), (47, (224, 0, 255)),
|
||||
(48, (255, 0, 255)), (49, (255, 32, 255)), (50, (255, 64, 255)), (51, (255, 96, 255)),
|
||||
(52, (255, 128, 255)), (53, (255, 160, 255)), (54, (255, 192, 255)), (55, (255, 224, 255)),
|
||||
(56, (255, 255, 255)), (57, (255, 224, 224)), (58, (255, 192, 192)), (59, (255, 160, 160)),
|
||||
(60, (255, 128, 128)), (61, (255, 96, 96)), (62, (255, 64, 64)), (63, (255, 32, 32))
|
||||
]
|
||||
|
||||
class Frame:
|
||||
# Public instance state: only normalized ILDA-format points and colors plus original svg size
|
||||
points_list: List[List[PointI]] # each path -> list of (x,y,z) int16
|
||||
colors_rgb: List[RGB] # per-path colors as 8-bit rgb
|
||||
colors_indexed: List[int] # per-path palette index (0-255)
|
||||
svg_size: Tuple[float, float] # (width, height) of original SVG in user units (floats)
|
||||
point_cnt: int # total points of all paths
|
||||
point_cnt_simpl: int # total points of all paths simplified
|
||||
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.points_list = []
|
||||
self.colors_rgb = []
|
||||
self.colors_indexed = []
|
||||
self.svg_size = (0.0, 0.0)
|
||||
self.point_cnt = 0
|
||||
self.point_cnt_simpl = 0
|
||||
|
||||
def read_svg(self, infile: str, simplify: bool = False, tol: float = 1.0, mintravel: bool = False) -> None:
|
||||
"""
|
||||
Read SVG paths and convert to internal normalized ILDA integer coordinates.
|
||||
|
||||
Args:
|
||||
infile: SVG file path
|
||||
simplify: if True apply Ramer-Douglas-Peucker simplification with tolerance tol
|
||||
tol: tolerance for simplification (same units as SVG coordinates)
|
||||
mintravel: optional order polylines to minimize blank travel
|
||||
Result:
|
||||
self.points_list: list of paths; each path is list of (x,y,z) int16 with z=0
|
||||
self.colors_rgb: per-path (r,g,b) tuples
|
||||
self.colors_indexed: per-path 8bit color index from DEFAULT_ilda_palette
|
||||
self.svg_size: (width, height) from svg attributes (floats)
|
||||
Notes:
|
||||
the viewbox passed to _path_to_points is (xmin, ymin, xmax, ymax) derived from svg size.
|
||||
"""
|
||||
|
||||
# Helper: compare two polylines with tolerance
|
||||
def _poly_equal(a: List[Tuple[float, float]], b: List[Tuple[float, float]], eps: float = 1e-6) -> bool:
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
for (x1, y1), (x2, y2) in zip(a, b):
|
||||
if abs(x1 - x2) > eps or abs(y1 - y2) > eps:
|
||||
return False
|
||||
return True
|
||||
|
||||
# Helper: append one polyline (list of (x,y) floats) and its attributes to internal lists
|
||||
def _append_poly_and_attr(poly: List[Tuple[float, float]], attr: Dict[str, str]) -> None:
|
||||
# If poly too short, append empty placeholder and still record color info
|
||||
if not poly or len(poly) < 2:
|
||||
self.points_list.append([])
|
||||
color = self._extract_stroke(attr)
|
||||
rgb = self._parse_color_to_rgb(color)
|
||||
self.colors_rgb.append(rgb)
|
||||
idx = self._find_best_palette_index(rgb)
|
||||
self.colors_indexed.append(idx)
|
||||
return
|
||||
|
||||
# Simplify polyline using class RDP if requested
|
||||
if simplify:
|
||||
processed = self._rdp(poly, tol)
|
||||
else:
|
||||
processed = poly
|
||||
|
||||
if not processed or len(processed) < 2:
|
||||
self.points_list.append([])
|
||||
color = self._extract_stroke(attr)
|
||||
rgb = self._parse_color_to_rgb(color)
|
||||
self.colors_rgb.append(rgb)
|
||||
idx = self._find_best_palette_index(rgb)
|
||||
self.colors_indexed.append(idx)
|
||||
return
|
||||
|
||||
# Normalize to ILDA coordinates (use svg_size width/height)
|
||||
width, height = self.svg_size
|
||||
ptsi = [self._normalize_point_to_ilda((x, y), width, height) for (x, y) in processed]
|
||||
ptsi3 = [(int(x), int(y), 0) for (x, y) in ptsi]
|
||||
self.points_list.append(ptsi3)
|
||||
|
||||
# Color handling (duplicate original attr for this fragment)
|
||||
color = self._extract_stroke(attr)
|
||||
rgb = self._parse_color_to_rgb(color)
|
||||
self.colors_rgb.append(rgb)
|
||||
idx = self._find_best_palette_index(rgb)
|
||||
self.colors_indexed.append(idx)
|
||||
|
||||
# Update simplified point counter
|
||||
self.point_cnt_simpl += len(processed)
|
||||
|
||||
# ---- main body ----
|
||||
paths, attribs, svg_attrib = svg2paths2(infile)
|
||||
width, height = self._extract_svg_size(svg_attrib)
|
||||
self.svg_size = (width, height)
|
||||
|
||||
# Reset internal lists and counters
|
||||
self.points_list = []
|
||||
self.colors_rgb = []
|
||||
self.colors_indexed = []
|
||||
sample_step = max(0.5, tol / 3.0)
|
||||
self.point_cnt = 0
|
||||
self.point_cnt_simpl = 0
|
||||
|
||||
# Collect all polylines (after sampling+clipping) and their attributes
|
||||
all_polylines: List[List[Tuple[float, float]]] = []
|
||||
all_attrs: List[Dict[str, str]] = []
|
||||
|
||||
for i, path_obj in enumerate(paths):
|
||||
# Pass viewbox as (xmin, ymin, xmax, ymax) to _path_to_points
|
||||
xmin, ymin = 0.0, 0.0
|
||||
xmax, ymax = width, height
|
||||
ptsf_parts = self._path_to_points(path_obj, sample_step, viewbox=(xmin, ymin, xmax, ymax))
|
||||
# ptsf_parts is list of polylines (each polyline is list of (x,y))
|
||||
self.point_cnt += sum(len(row) for row in ptsf_parts)
|
||||
attr = attribs[i] if i < len(attribs) else {}
|
||||
for part in ptsf_parts:
|
||||
all_polylines.append(part)
|
||||
all_attrs.append(attr)
|
||||
|
||||
# If nothing found, keep behavior: print and return
|
||||
if not all_polylines:
|
||||
print(f"Processing file: {infile} Points: {self.point_cnt} Points (simplified): {self.point_cnt_simpl}")
|
||||
return
|
||||
|
||||
# optional order polylines to minimize blank travel
|
||||
if mintravel:
|
||||
ordered_result = order_paths_greedy_with_2opt(all_polylines, start_point=None, do_2opt=True)
|
||||
ordered_polylines = ordered_result.paths
|
||||
else:
|
||||
ordered_polylines = list(all_polylines)
|
||||
|
||||
# Map ordered polylines back to original attributes by matching geometry (or reversed)
|
||||
used_indices = set()
|
||||
for poly in ordered_polylines:
|
||||
matched_idx = None
|
||||
matched_attr: Dict[str, str] = {}
|
||||
for j, orig in enumerate(all_polylines):
|
||||
if j in used_indices:
|
||||
continue
|
||||
if _poly_equal(orig, poly) or _poly_equal(list(reversed(orig)), poly):
|
||||
matched_idx = j
|
||||
matched_attr = all_attrs[j]
|
||||
used_indices.add(j)
|
||||
break
|
||||
if matched_idx is None:
|
||||
# If no exact match found, use empty attr (rare)
|
||||
matched_attr = {}
|
||||
_append_poly_and_attr(poly, matched_attr)
|
||||
|
||||
print(f"Processing file: {infile} Points: {self.point_cnt} Points (simplified): {self.point_cnt_simpl}")
|
||||
|
||||
|
||||
|
||||
def get_ilda(self,
|
||||
format_code: int = 0,
|
||||
frame_name: str = '00000001',
|
||||
company_name: str = 'MIKLOBIT',
|
||||
frame_number: int = 0,
|
||||
total_frames: int = 1,
|
||||
projector: int = 0) -> bytes:
|
||||
"""
|
||||
Prepare ILDA binary data for this frame: header (32B) + records.
|
||||
- Does not append EOF header (NumberOfRecords = 0) — this is left to the Animation class.
|
||||
- If the frame does not contain points, it raises a ValueError (to avoid accidentally
|
||||
inserting EOF in the middle of an animation).
|
||||
Params:
|
||||
format_code: ILDA format (only 0 implemented)
|
||||
frame_name, company_name: ASCII, up to 8 characters (will be truncated/padded)
|
||||
frame_number, total_frames, projector: header fields
|
||||
Returns:
|
||||
bytes with header + records (without EOF)
|
||||
"""
|
||||
if format_code != 0:
|
||||
raise NotImplementedError("Only Format 0 (3D Indexed) is implemented")
|
||||
|
||||
# helper creating a 32-byte header
|
||||
def _make_header(fmt: int, name: str, company: str, num_records: int,
|
||||
frame_number: int = 0, total_frames: int = 1, projector: int = 0) -> bytes:
|
||||
b = bytearray(32)
|
||||
b[0:4] = b'ILDA'
|
||||
b[7] = fmt & 0xFF
|
||||
name_enc = name.encode('ascii', errors='ignore')[:8]
|
||||
b[8:8+len(name_enc)] = name_enc
|
||||
comp_enc = company.encode('ascii', errors='ignore')[:8]
|
||||
b[16:16+len(comp_enc)] = comp_enc
|
||||
b[24:26] = num_records.to_bytes(2, byteorder='big', signed=False)
|
||||
b[26:28] = frame_number.to_bytes(2, byteorder='big', signed=False)
|
||||
b[28:30] = total_frames.to_bytes(2, byteorder='big', signed=False)
|
||||
b[30] = projector & 0xFF
|
||||
return bytes(b)
|
||||
|
||||
# total number of records (each record = 8 bytes)
|
||||
total_points = 0
|
||||
for path in self.points_list:
|
||||
total_points += max(0, len(path))
|
||||
|
||||
if total_points == 0:
|
||||
# Note: a header with num_records=0 is treated as EOF — we do not want to include this
|
||||
# in the middle of the animation; we return an error so that Animation can skip this frame.
|
||||
raise ValueError("Frame contains no points; get_ilda() would produce EOF header")
|
||||
|
||||
records = bytearray()
|
||||
# determine the index of the last point in this frame -> set the LastPoint bit at the last point of this section
|
||||
last_path_idx = -1
|
||||
last_point_idx = -1
|
||||
for pi, path in enumerate(self.points_list):
|
||||
if path:
|
||||
last_path_idx = pi
|
||||
last_point_idx = len(path) - 1
|
||||
|
||||
for pi, path in enumerate(self.points_list):
|
||||
if not path:
|
||||
continue
|
||||
# first point as blanking (duplicate of first point) with Blanking bit set
|
||||
fx, fy, fz = path[0]
|
||||
status = 0
|
||||
status |= (1 << 6) # Blanking bit
|
||||
ci = 0
|
||||
if pi < len(self.colors_indexed):
|
||||
ci = int(self.colors_indexed[pi]) & 0xFF
|
||||
records += int(fx).to_bytes(2, byteorder='big', signed=True)
|
||||
records += int(fy).to_bytes(2, byteorder='big', signed=True)
|
||||
records += int(fz).to_bytes(2, byteorder='big', signed=True)
|
||||
records += bytes([status, ci])
|
||||
|
||||
# subsequent points (draw), set LastPoint to the last point of this frame/section
|
||||
for pti, (x, y, z) in enumerate(path):
|
||||
status = 0
|
||||
if (pi == last_path_idx) and (pti == last_point_idx):
|
||||
status |= (1 << 7) # LastPoint bit
|
||||
ci = 0
|
||||
if pi < len(self.colors_indexed):
|
||||
ci = int(self.colors_indexed[pi]) & 0xFF
|
||||
records += int(x).to_bytes(2, byteorder='big', signed=True)
|
||||
records += int(y).to_bytes(2, byteorder='big', signed=True)
|
||||
records += int(z).to_bytes(2, byteorder='big', signed=True)
|
||||
records += bytes([status, ci])
|
||||
|
||||
header = _make_header(format_code, frame_name, company_name, len(records) // 8,
|
||||
frame_number=frame_number, total_frames=total_frames, projector=projector)
|
||||
return header + bytes(records)
|
||||
|
||||
def write_ild(self, filename: str, format_code: int = 0,
|
||||
frame_name: str = '00000001', company_name: str = 'MIKLO',
|
||||
frame_number: int = 0, total_frames: int = 1, projector: int = 0) -> None:
|
||||
"""
|
||||
Save a single ILDA file for this frame (including the EOF header).
|
||||
The implementation uses get_ilda() to generate the frame section and then
|
||||
adds the EOF header (NumberOfRecords = 0).
|
||||
"""
|
||||
# Get binary for (header + records)
|
||||
section = self.get_ilda(format_code=format_code,
|
||||
frame_name=frame_name,
|
||||
company_name=company_name,
|
||||
frame_number=frame_number,
|
||||
total_frames=total_frames,
|
||||
projector=projector)
|
||||
# Create EOF header (format_code, 0 records)
|
||||
def _make_eof_header(fmt: int, name: str, company: str, frame_number: int = 0, total_frames: int = 0, projector: int = 0) -> bytes:
|
||||
b = bytearray(32)
|
||||
b[0:4] = b'ILDA'
|
||||
b[7] = fmt & 0xFF
|
||||
name_enc = name.encode('ascii', errors='ignore')[:8]
|
||||
b[8:8+len(name_enc)] = name_enc
|
||||
comp_enc = company.encode('ascii', errors='ignore')[:8]
|
||||
b[16:16+len(comp_enc)] = comp_enc
|
||||
b[24:26] = (0).to_bytes(2, byteorder='big', signed=False) # NumberOfRecords = 0 -> EOF
|
||||
b[26:28] = frame_number.to_bytes(2, byteorder='big', signed=False)
|
||||
b[28:30] = total_frames.to_bytes(2, byteorder='big', signed=False)
|
||||
b[30] = projector & 0xFF
|
||||
return bytes(b)
|
||||
|
||||
eof_header = _make_eof_header(format_code, frame_name, company_name, frame_number=0, total_frames=0, projector=projector)
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(section)
|
||||
f.write(eof_header)
|
||||
|
||||
|
||||
def write_svg(self, outfile: str, canvas_size: Optional[Tuple[float, float]] = None) -> None:
|
||||
"""
|
||||
Build an SVG with paths from internal ILDA-normalized integer coordinates.
|
||||
Args:
|
||||
outfile: output SVG filename
|
||||
canvas_size: optional (width, height) in target SVG units. If None, uses ILDA canvas size (65536,65536).
|
||||
If provided, ILDA coordinates are scaled to fit this canvas preserving the ILDA aspect.
|
||||
"""
|
||||
if canvas_size is None:
|
||||
target_w, target_h = (ILDA_CANVAS, ILDA_CANVAS)
|
||||
else:
|
||||
target_w, target_h = canvas_size
|
||||
|
||||
svg_attrs = {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'width': str(target_w),
|
||||
'height': str(target_h),
|
||||
'viewBox': f"0 0 {target_w} {target_h}"
|
||||
}
|
||||
root = Element('svg', svg_attrs)
|
||||
|
||||
scale_x = target_w / float(ILDA_CANVAS)
|
||||
scale_y = target_h / float(ILDA_CANVAS)
|
||||
|
||||
for idx, pts in enumerate(self.points_list):
|
||||
if not pts:
|
||||
continue
|
||||
ptsf = []
|
||||
for (xi, yi, zi) in pts:
|
||||
nx = (xi / ILDA_CANVAS) + 0.5
|
||||
ny = 0.5 - (yi / ILDA_CANVAS)
|
||||
fx = nx * ILDA_CANVAS * scale_x
|
||||
fy = ny * ILDA_CANVAS * scale_y
|
||||
ptsf.append((float(fx), float(fy)))
|
||||
closed = False
|
||||
d = self._points_to_path_d(ptsf, closed)
|
||||
el = SubElement(root, 'path')
|
||||
el.set('d', d)
|
||||
rgb = self.colors_rgb[idx] if idx < len(self.colors_rgb) else (0, 0, 0)
|
||||
el.set('stroke', f"rgb({rgb[0]},{rgb[1]},{rgb[2]})")
|
||||
el.set('fill', 'none')
|
||||
el.set('stroke-width', '1.0')
|
||||
|
||||
tree = ElementTree(root)
|
||||
tree.write(outfile, encoding='utf-8', xml_declaration=True)
|
||||
|
||||
|
||||
|
||||
def _extract_svg_size(self, svg_attrib: Dict[str, str]) -> Tuple[float, float]:
|
||||
vb = svg_attrib.get('viewBox') or svg_attrib.get('viewbox')
|
||||
if vb:
|
||||
parts = vb.strip().replace(',', ' ').split()
|
||||
if len(parts) >= 4:
|
||||
try:
|
||||
w = float(parts[2])
|
||||
h = float(parts[3])
|
||||
if w > 0 and h > 0:
|
||||
return (w, h)
|
||||
except Exception:
|
||||
pass
|
||||
def parse_dim(v: Optional[str]) -> Optional[float]:
|
||||
if not v:
|
||||
return None
|
||||
m = re.match(r'([0-9.+-eE]+)', v.strip())
|
||||
if m:
|
||||
try:
|
||||
return float(m.group(1))
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
w = parse_dim(svg_attrib.get('width'))
|
||||
h = parse_dim(svg_attrib.get('height'))
|
||||
if w and h:
|
||||
return (w, h)
|
||||
return (ILDA_CANVAS, ILDA_CANVAS)
|
||||
|
||||
def _extract_stroke(self, attr: Dict[str, str]) -> str:
|
||||
if 'stroke' in attr and attr['stroke'].strip():
|
||||
return attr['stroke'].strip()
|
||||
style = attr.get('style', '')
|
||||
for part in style.split(';'):
|
||||
if not part:
|
||||
continue
|
||||
if ':' not in part:
|
||||
continue
|
||||
k, v = part.split(':', 1)
|
||||
if k.strip() == 'stroke' and v.strip():
|
||||
return v.strip()
|
||||
return ''
|
||||
|
||||
|
||||
def _parse_color_to_rgb(self, color_str: str) -> RGB:
|
||||
if not color_str:
|
||||
return (0, 0, 0)
|
||||
s = color_str.strip()
|
||||
if s.startswith('#'):
|
||||
s2 = s[1:]
|
||||
if len(s2) == 6:
|
||||
try:
|
||||
r = int(s2[0:2], 16)
|
||||
g = int(s2[2:4], 16)
|
||||
b = int(s2[4:6], 16)
|
||||
return (r, g, b)
|
||||
except Exception:
|
||||
return (0, 0, 0)
|
||||
if len(s2) == 3:
|
||||
try:
|
||||
r = int(s2[0]*2, 16)
|
||||
g = int(s2[1]*2, 16)
|
||||
b = int(s2[2]*2, 16)
|
||||
return (r, g, b)
|
||||
except Exception:
|
||||
return (0, 0, 0)
|
||||
m = re.match(r'rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)', s, re.IGNORECASE)
|
||||
if m:
|
||||
try:
|
||||
r = max(0, min(255, int(m.group(1))))
|
||||
g = max(0, min(255, int(m.group(2))))
|
||||
b = max(0, min(255, int(m.group(3))))
|
||||
return (r, g, b)
|
||||
except Exception:
|
||||
return (0, 0, 0)
|
||||
named = {
|
||||
'black': (0,0,0), 'white': (255,255,255), 'red': (255,0,0),
|
||||
'green': (0,128,0), 'blue': (0,0,255)
|
||||
}
|
||||
lc = s.lower()
|
||||
if lc in named:
|
||||
return named[lc]
|
||||
return (0, 0, 0)
|
||||
|
||||
def _path_to_points(self, path: Path, max_segment_length: float, viewbox: Optional[Tuple[float, float, float, float]] = None) -> List[List[PointF]]:
|
||||
"""
|
||||
Sample an svgpathtools Path into one or more polylines (list of point lists).
|
||||
- If viewbox is provided as (xmin, ymin, width, height) or (xmin,ymin,xmax,ymax),
|
||||
the resulting single polyline is clipped to the rectangle and may produce multiple pieces.
|
||||
- Returns list of polylines (each polyline is a list of (x,y) floats).
|
||||
"""
|
||||
# existing sampling logic (produce single polyline)
|
||||
pts: List[PointF] = []
|
||||
for seg in path:
|
||||
seg_pts = self._sample_segment(seg, max_segment_length)
|
||||
if not seg_pts:
|
||||
continue
|
||||
if not pts:
|
||||
pts.extend(seg_pts)
|
||||
else:
|
||||
if abs(pts[-1][0] - seg_pts[0][0]) < 1e-9 and abs(pts[-1][1] - seg_pts[0][1]) < 1e-9:
|
||||
pts.extend(seg_pts[1:])
|
||||
else:
|
||||
pts.extend(seg_pts)
|
||||
# ensure final endpoint included
|
||||
if len(path) > 0:
|
||||
try:
|
||||
endc = path[-1].point(1.0)
|
||||
endpt = (endc.real, endc.imag)
|
||||
if not pts or (abs(pts[-1][0] - endpt[0]) > 1e-9 or abs(pts[-1][1] - endpt[1]) > 1e-9):
|
||||
pts.append(endpt)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# If no clipping requested, return single polyline as single-item list (or empty list if too short)
|
||||
if not viewbox:
|
||||
return [pts] if len(pts) >= 2 else []
|
||||
|
||||
# Convert to (xmin,ymin,xmax,ymax) exactly (read_svg should provide this form)
|
||||
xmin, ymin, xmax, ymax = viewbox
|
||||
|
||||
if len(pts) < 2:
|
||||
return []
|
||||
|
||||
pieces = self._clip_polyline_to_rect(pts, (xmin, ymin, xmax, ymax))
|
||||
return pieces
|
||||
|
||||
|
||||
|
||||
def _sample_segment(self, seg: Any, max_segment_length: float) -> List[PointF]:
|
||||
try:
|
||||
seg_len = seg.length(error=1e-5)
|
||||
except Exception:
|
||||
bbox = seg.bbox()
|
||||
dx = bbox[2] - bbox[0]
|
||||
dy = bbox[3] - bbox[1]
|
||||
seg_len = math.hypot(dx, dy)
|
||||
if seg_len == 0:
|
||||
return []
|
||||
n = max(1, int(math.ceil(seg_len / max_segment_length)))
|
||||
pts: List[PointF] = []
|
||||
for i in range(n):
|
||||
t = i / n
|
||||
c = seg.point(t)
|
||||
pts.append((c.real, c.imag))
|
||||
return pts
|
||||
|
||||
|
||||
def _rdp(self, points: List[PointF], eps: float) -> List[PointF]:
|
||||
if len(points) < 3:
|
||||
return points[:]
|
||||
start = np.array(points[0])
|
||||
end = np.array(points[-1])
|
||||
line_vec = end - start
|
||||
line_len_sq = np.dot(line_vec, line_vec)
|
||||
if line_len_sq == 0.0:
|
||||
dists = [np.linalg.norm(np.array(p) - start) for p in points]
|
||||
else:
|
||||
def point_line_distance(pt: PointF) -> float:
|
||||
v = np.array(pt) - start
|
||||
t = np.dot(v, line_vec) / line_len_sq
|
||||
t = max(0.0, min(1.0, t))
|
||||
proj = start + t * line_vec
|
||||
return float(np.linalg.norm(np.array(pt) - proj))
|
||||
dists = [point_line_distance(p) for p in points]
|
||||
idx = int(np.argmax(dists))
|
||||
dmax = dists[idx]
|
||||
if dmax > eps:
|
||||
left = self._rdp(points[:idx+1], eps)
|
||||
right = self._rdp(points[idx:], eps)
|
||||
return left[:-1] + right
|
||||
else:
|
||||
return [points[0], points[-1]]
|
||||
|
||||
|
||||
def _normalize_point_to_ilda(self, p: PointF, svg_w: float, svg_h: float) -> Tuple[int, int]:
|
||||
x, y = p
|
||||
if svg_w <= 0 or svg_h <= 0:
|
||||
svg_w, svg_h = (ILDA_CANVAS, ILDA_CANVAS)
|
||||
nx = x / svg_w
|
||||
ny = y / svg_h
|
||||
ix = int(round((nx - 0.5) * ILDA_CANVAS))
|
||||
iy = int(round((0.5 - ny) * ILDA_CANVAS))
|
||||
ix = max(INT16_MIN, min(INT16_MAX, ix))
|
||||
iy = max(INT16_MIN, min(INT16_MAX, iy))
|
||||
return (ix, iy)
|
||||
|
||||
|
||||
def _ilda_to_canvas_float(self, p3: PointI) -> Tuple[float, float]:
|
||||
x_int, y_int, _ = p3
|
||||
nx = (x_int / ILDA_CANVAS) + 0.5
|
||||
ny = 0.5 - (y_int / ILDA_CANVAS)
|
||||
fx = nx * ILDA_CANVAS
|
||||
fy = ny * ILDA_CANVAS
|
||||
return (float(fx), float(fy))
|
||||
|
||||
|
||||
def _points_to_path_d(self, points: List[Tuple[float, float]], closed: bool) -> str:
|
||||
if not points:
|
||||
return ""
|
||||
parts: List[str] = []
|
||||
x0, y0 = points[0]
|
||||
parts.append(f"M {x0:.6f} {y0:.6f}")
|
||||
for (x, y) in points[1:]:
|
||||
parts.append(f"L {x:.6f} {y:.6f}")
|
||||
if closed:
|
||||
parts.append("Z")
|
||||
return " ".join(parts)
|
||||
|
||||
def _find_best_palette_index(self, rgb: RGB) -> int:
|
||||
# If no default palette provided, return 0
|
||||
if not DEFAULT_ilda_palette:
|
||||
return 0
|
||||
r0, g0, b0 = rgb
|
||||
best_idx = DEFAULT_ilda_palette[0][0]
|
||||
best_dist = None
|
||||
for idx, (pr, pg, pb) in DEFAULT_ilda_palette:
|
||||
dr = abs(pr - r0)
|
||||
dg = abs(pg - g0)
|
||||
db = abs(pb - b0)
|
||||
dist = dr*dr + dg*dg + db*db
|
||||
if best_dist is None or dist < best_dist:
|
||||
best_dist = dist
|
||||
best_idx = idx
|
||||
# Ensure 0-255 range
|
||||
return max(0, min(255, int(best_idx)))
|
||||
|
||||
|
||||
def _clip_polyline_to_rect(self,
|
||||
poly: List[PointF],
|
||||
rect: Tuple[float, float, float, float],
|
||||
eps: float = 1e-9) -> List[List[PointF]]:
|
||||
"""
|
||||
Clip a polyline to axis-aligned rectangle.
|
||||
- poly: list of (x,y) floats representing the sampled polyline
|
||||
- rect: (xmin, ymin, xmax, ymax)
|
||||
- Returns list of polylines (each a list of (x,y)) that lie inside the rect.
|
||||
- Uses Shapely
|
||||
"""
|
||||
|
||||
xmin, ymin, xmax, ymax = rect
|
||||
if not poly or xmin >= xmax or ymin >= ymax:
|
||||
return []
|
||||
|
||||
if LineString is not None:
|
||||
# robust, uses geometric intersection
|
||||
ls = LineString(poly)
|
||||
clip = box(xmin, ymin, xmax, ymax)
|
||||
inter = clip.intersection(ls)
|
||||
parts: List[List[PointF]] = []
|
||||
# inter can be LineString or MultiLineString or GeometryCollection
|
||||
def _extract_lines(geom):
|
||||
if geom.is_empty:
|
||||
return
|
||||
geom_type = geom.geom_type
|
||||
if geom_type == 'LineString':
|
||||
coords = list(geom.coords)
|
||||
if len(coords) >= 2:
|
||||
parts.append([(float(x), float(y)) for x, y in coords])
|
||||
elif geom_type == 'MultiLineString':
|
||||
for g in geom.geoms:
|
||||
_extract_lines(g)
|
||||
# ignore points etc.
|
||||
_extract_lines(inter)
|
||||
return parts
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
# ilda/utils.py
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Tuple, Optional
|
||||
import math
|
||||
|
||||
Point = Tuple[float, float]
|
||||
PathPoints = List[Point]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PathOrderResult:
|
||||
"""
|
||||
Container for ordered paths result.
|
||||
|
||||
Attributes:
|
||||
paths: List of ordered polylines (each polyline is a list of (x,y) tuples).
|
||||
reversed_flags: Parallel list of bools, True if corresponding original path was reversed.
|
||||
"""
|
||||
paths: List[PathPoints]
|
||||
reversed_flags: List[bool]
|
||||
|
||||
|
||||
def _dist(a: Point, b: Point) -> float:
|
||||
"""Euclidean distance between two points."""
|
||||
return math.hypot(a[0] - b[0], a[1] - b[1])
|
||||
|
||||
|
||||
def order_paths_greedy_with_2opt(paths: List[PathPoints],
|
||||
start_point: Optional[Point] = None,
|
||||
do_2opt: bool = True) -> PathOrderResult:
|
||||
"""
|
||||
Order polylines to reduce dead (blanked) travel between them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
paths :
|
||||
List of polylines (each polyline must have >= 2 points).
|
||||
start_point :
|
||||
Optional coordinate (x,y) to start the sequence from. If None, starts from first path.
|
||||
do_2opt :
|
||||
Whether to run a local 2-opt improvement after greedy construction.
|
||||
|
||||
Returns
|
||||
-------
|
||||
PathOrderResult
|
||||
Ordered paths and flags indicating if each path was reversed relative to original.
|
||||
"""
|
||||
n = len(paths)
|
||||
if n == 0:
|
||||
return PathOrderResult([], [])
|
||||
|
||||
# Precompute endpoints (start, end) for each path
|
||||
endpoints: List[Tuple[Point, Point]] = [(p[0], p[-1]) for p in paths]
|
||||
|
||||
used = [False] * n
|
||||
order: List[int] = []
|
||||
rev_flags: List[bool] = []
|
||||
|
||||
# choose initial index
|
||||
if start_point is None:
|
||||
cur_idx = 0
|
||||
cur_rev = False
|
||||
used[cur_idx] = True
|
||||
order.append(cur_idx)
|
||||
rev_flags.append(cur_rev)
|
||||
cur_pt = endpoints[cur_idx][1] if not cur_rev else endpoints[cur_idx][0]
|
||||
else:
|
||||
# pick closest endpoint among all paths
|
||||
best_idx = -1
|
||||
best_rev = False
|
||||
best_d = float('inf')
|
||||
for i, (s, e) in enumerate(endpoints):
|
||||
d_s = _dist(start_point, s)
|
||||
if d_s < best_d:
|
||||
best_d = d_s; best_idx = i; best_rev = False
|
||||
d_e = _dist(start_point, e)
|
||||
if d_e < best_d:
|
||||
best_d = d_e; best_idx = i; best_rev = True
|
||||
cur_idx = best_idx
|
||||
cur_rev = best_rev
|
||||
used[cur_idx] = True
|
||||
order.append(cur_idx)
|
||||
rev_flags.append(cur_rev)
|
||||
cur_pt = endpoints[cur_idx][1] if not cur_rev else endpoints[cur_idx][0]
|
||||
|
||||
# greedy nearest neighbor choosing orientation
|
||||
for _ in range(1, n):
|
||||
best_j = -1
|
||||
best_j_rev = False
|
||||
best_d = float('inf')
|
||||
for j in range(n):
|
||||
if used[j]:
|
||||
continue
|
||||
s, e = endpoints[j]
|
||||
d1 = _dist(cur_pt, s) # not reversed
|
||||
if d1 < best_d:
|
||||
best_d = d1; best_j = j; best_j_rev = False
|
||||
d2 = _dist(cur_pt, e) # reversed
|
||||
if d2 < best_d:
|
||||
best_d = d2; best_j = j; best_j_rev = True
|
||||
if best_j == -1:
|
||||
break
|
||||
used[best_j] = True
|
||||
order.append(best_j)
|
||||
rev_flags.append(best_j_rev)
|
||||
s, e = endpoints[best_j]
|
||||
cur_pt = e if not best_j_rev else s
|
||||
|
||||
# Build ordered path list (apply reversal where needed)
|
||||
ordered_paths: List[PathPoints] = []
|
||||
for idx, rev in zip(order, rev_flags):
|
||||
p = paths[idx]
|
||||
ordered_paths.append(list(reversed(p)) if rev else list(p))
|
||||
|
||||
# Optional 2-opt improvement (operates on ordered_paths)
|
||||
if do_2opt and len(ordered_paths) > 2:
|
||||
def connection_cost(a: PathPoints, b: PathPoints) -> float:
|
||||
return _dist(a[-1], b[0])
|
||||
|
||||
improved = True
|
||||
seq = ordered_paths
|
||||
while improved:
|
||||
improved = False
|
||||
m = len(seq)
|
||||
for i in range(0, m - 2):
|
||||
for j in range(i + 2, m):
|
||||
# cost affected at edges (i -> i+1) and (j -> j+1)
|
||||
old_cost = connection_cost(seq[i], seq[i+1])
|
||||
if j + 1 < m:
|
||||
old_cost += connection_cost(seq[j], seq[j+1])
|
||||
# form new sequence by reversing subsequence (i+1 .. j) and reversing each subpath
|
||||
new_seq = seq[:i+1] + [list(reversed(seg)) for seg in seq[i+1:j+1]][::-1] + seq[j+1:]
|
||||
new_cost = connection_cost(new_seq[i], new_seq[i+1])
|
||||
if j + 1 < m:
|
||||
new_cost += connection_cost(new_seq[j], new_seq[j+1])
|
||||
if new_cost + 1e-12 < old_cost:
|
||||
seq = new_seq
|
||||
improved = True
|
||||
break
|
||||
if improved:
|
||||
break
|
||||
ordered_paths = seq
|
||||
|
||||
# Reconstruct reversed_flags relative to originals
|
||||
final_flags: List[bool] = []
|
||||
# Create mapping of original endpoints for approximate identification
|
||||
for p in ordered_paths:
|
||||
if not p:
|
||||
final_flags.append(False)
|
||||
continue
|
||||
first = p[0]
|
||||
found = False
|
||||
for orig in paths:
|
||||
if abs(orig[0][0] - first[0]) < 1e-9 and abs(orig[0][1] - first[1]) < 1e-9:
|
||||
final_flags.append(False)
|
||||
found = True
|
||||
break
|
||||
if abs(orig[-1][0] - first[0]) < 1e-9 and abs(orig[-1][1] - first[1]) < 1e-9:
|
||||
final_flags.append(True)
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
final_flags.append(False)
|
||||
|
||||
return PathOrderResult(paths=ordered_paths, reversed_flags=final_flags)
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "svg2ild"
|
||||
version = "0.1.0"
|
||||
description = "Converts SVG files to ILDA animation format for laser shows"
|
||||
readme = "README.md"
|
||||
license = "AGPL-3.0"
|
||||
authors = [
|
||||
{name = "miklo",email = "test@miklobit.com"}
|
||||
]
|
||||
maintainers = [
|
||||
{name = "miklo", email = "test@miklobit.com"}
|
||||
]
|
||||
keywords = ["svg", "ilda", "laser", "animation", "laser-show", "graphics"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: End Users/Desktop",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Multimedia :: Graphics",
|
||||
"Topic :: Scientific/Engineering",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
dependencies = [
|
||||
"numpy>=1.21.0",
|
||||
"scipy>=1.7.0",
|
||||
"shapely>=1.8.0",
|
||||
"svgpathtools>=1.5.0",
|
||||
"svgwrite>=1.4.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
"black>=22.0.0",
|
||||
"isort>=5.10.0",
|
||||
"flake8>=5.0.0",
|
||||
"mypy>=1.0.0",
|
||||
"pre-commit>=2.20.0",
|
||||
]
|
||||
test = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
]
|
||||
docs = [
|
||||
"sphinx>=5.0.0",
|
||||
"sphinx-rtd-theme>=1.2.0",
|
||||
"myst-parser>=0.18.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://forge.citizen4.eu/miklo/svg2ild"
|
||||
Documentation = "https://forge.citizen4.eu/miklo/svg2ild/wiki"
|
||||
Repository = "https://forge.citizen4.eu/miklo/svg2ild.git"
|
||||
"Bug Tracker" = "https://forge.citizen4.eu/miklo/svg2ild/issues"
|
||||
Changelog = "https://forge.citizen4.eu/miklo/svg2ild/blob/main/CHANGELOG.md"
|
||||
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "."}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["ilda*"]
|
||||
exclude = ["tests*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["*.txt", "*.md", "*.yml", "*.yaml", "*.json"]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ['py38']
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
/(
|
||||
# directories
|
||||
\.eggs
|
||||
| \.git
|
||||
| \.hg
|
||||
| \.mypy_cache
|
||||
| \.tox
|
||||
| \.venv
|
||||
| build
|
||||
| dist
|
||||
)/
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
multi_line_output = 3
|
||||
line_length = 88
|
||||
known_first_party = ["ilda"]
|
||||
|
||||
[tool.mypy]
|
||||
python_version = "3.8"
|
||||
warn_return_any = true
|
||||
warn_unused_configs = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
disallow_untyped_decorators = true
|
||||
no_implicit_optional = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
warn_no_return = true
|
||||
warn_unreachable = true
|
||||
strict_equality = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"svgpathtools.*",
|
||||
"shapely.*",
|
||||
"scipy.*",
|
||||
"numpy.*",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py", "*_test.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = [
|
||||
"--strict-markers",
|
||||
"--strict-config",
|
||||
"--verbose",
|
||||
"--cov=ilda",
|
||||
"--cov-report=term-missing",
|
||||
"--cov-report=html",
|
||||
"--cov-report=xml",
|
||||
]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["ilda"]
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/test_*",
|
||||
"setup.py",
|
||||
]
|
||||
|
||||
[tool.coverage.report]
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"def __repr__",
|
||||
"if self.debug:",
|
||||
"if settings.DEBUG",
|
||||
"raise AssertionError",
|
||||
"raise NotImplementedError",
|
||||
"if 0:",
|
||||
"if __name__ == .__main__.:",
|
||||
"class .*\\bProtocol\\):",
|
||||
"@(abc\\.)?abstractmethod",
|
||||
]
|
||||
|
||||
[tool.flake8]
|
||||
max-line-length = 88
|
||||
extend-ignore = ["E203", "W503", "E501"]
|
||||
exclude = [
|
||||
".git",
|
||||
"__pycache__",
|
||||
"build",
|
||||
"dist",
|
||||
".eggs",
|
||||
"*.egg-info",
|
||||
".venv",
|
||||
".env",
|
||||
]
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
numpy==2.0.2
|
||||
scipy==1.13.1
|
||||
shapely==2.0.7
|
||||
svgpathtools==1.7.2
|
||||
svgwrite==1.4.3
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H125 V125 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H225 V225 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H325 V325 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H150 V150 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H250 V250 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H350 V350 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H175 V175 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H275 V275 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H375 V375 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H200 V200 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H300 V300 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H400 V400 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H225 V225 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H325 V325 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H425 V425 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H250 V250 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H350 V350 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H450 V450 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H275 V275 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H375 V375 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H475 V475 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H300 V300 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H400 V400 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H500 V500 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H325 V325 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H425 V425 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H525 V525 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H350 V350 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H450 V450 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H550 V550 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H375 V375 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H475 V475 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H575 V575 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H400 V400 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H500 V500 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H600 V600 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H425 V425 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H525 V525 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H625 V625 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H450 V450 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H550 V550 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H650 V650 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H475 V475 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H575 V575 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H675 V675 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H525 V525 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H625 V625 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H725 V725 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H550 V550 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H650 V650 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H750 V750 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H575 V575 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H675 V675 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H775 V775 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H600 V600 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H700 V700 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H800 V800 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H625 V625 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H725 V725 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H825 V825 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version='1.0' encoding='ascii'?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" width="1000" height="1000">
|
||||
<g id="ViewLayer_LineSetHS" inkscape:groupmode="lineset" inkscape:label="ViewLayer_LineSetHS">
|
||||
<g inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 481.287, 824.783 481.224, 814.783 481.162, 804.783 481.100, 794.784 481.037, 784.784 480.975, 774.784 480.912, 764.784 480.850, 754.784 480.802, 747.129 480.740, 737.129 480.678, 727.129 480.615, 717.129 480.553, 707.129 480.491, 697.129 480.428, 687.130 480.366, 677.130 480.304, 667.130 480.292, 665.348 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 546.571, 717.900 546.721, 707.901 546.871, 697.903 547.021, 687.904 547.171, 677.905 547.321, 667.906 547.363, 665.060 547.513, 655.061 547.634, 646.977 547.743, 639.724 538.694, 635.467 529.645, 631.211 520.596, 626.955 516.576, 625.064 507.527, 620.807 498.478, 616.551 491.211, 613.133 481.749, 616.367 472.286, 619.601 462.824, 622.835 453.361, 626.069 443.898, 629.303 434.436, 632.537 424.915, 635.791 425.151, 645.788 425.386, 655.785 425.608, 665.230 425.844, 675.227 426.079, 685.224 426.314, 695.222 426.550, 705.219 426.742, 713.407 426.978, 723.404 427.213, 733.402 427.448, 743.399 427.684, 753.396 427.919, 763.393 428.154, 773.390 428.389, 783.388 428.482, 787.336 436.639, 793.121 444.797, 798.905 452.954, 804.690 461.111, 810.475 469.268, 816.259 477.425, 822.044 481.287, 824.783 490.210, 820.270 499.134, 815.757 508.058, 811.244 516.982, 806.732 525.906, 802.219 534.830, 797.706 543.753, 793.193 545.456, 792.332 554.380, 787.819 563.304, 783.306 572.227, 778.794 581.151, 774.281 590.075, 769.768 598.999, 765.255 603.454, 763.002 612.378, 758.489 621.302, 753.976 630.226, 749.464 639.150, 744.951 648.073, 740.438 656.131, 736.363 665.055, 731.850 673.978, 727.338 682.902, 722.825 691.826, 718.312 694.294, 717.064 703.218, 712.551 704.186, 712.062 704.840, 702.083 705.494, 692.105 706.110, 682.718 706.764, 672.740 707.419, 662.761 708.073, 652.783 708.523, 645.928 709.177, 635.950 709.831, 625.971 710.486, 615.992 711.140, 606.014 711.794, 596.035 712.449, 586.057 713.048, 576.924 713.702, 566.946 714.356, 556.967 715.011, 546.989 715.665, 537.010 716.320, 527.031 716.974, 517.053 717.628, 507.074 717.774, 504.859 718.428, 494.880 719.082, 484.902 719.737, 474.923 720.391, 464.945 721.045, 454.966 721.700, 444.988 722.354, 435.009 722.714, 429.524 712.938, 427.418 703.162, 425.312 693.387, 423.206 683.611, 421.101 673.835, 418.995 664.059, 416.889 662.213, 416.491 652.436, 418.592 642.659, 420.693 632.882, 422.794 623.105, 424.895 613.329, 426.996 610.493, 427.606 600.716, 429.707 590.939, 431.808 581.162, 433.909 571.385, 436.010 561.609, 438.111 553.617, 439.829 550.729, 440.449 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 424.915, 635.791 433.737, 640.500 442.559, 645.208 451.381, 649.917 460.203, 654.626 469.025, 659.335 477.847, 664.043 480.292, 665.348 489.641, 661.797 498.989, 658.246 508.337, 654.694 517.685, 651.143 527.033, 647.591 536.381, 644.040 545.729, 640.489 547.743, 639.724 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 546.571, 717.900 555.710, 713.839 564.848, 709.778 573.986, 705.717 583.124, 701.656 592.263, 697.595 601.401, 693.534 605.868, 691.549 615.006, 687.487 624.144, 683.426 633.283, 679.365 642.421, 675.304 651.559, 671.243 659.602, 667.669 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 603.503, 642.558 612.631, 646.643 621.758, 650.729 630.885, 654.814 640.013, 658.900 649.140, 662.985 658.267, 667.071 659.602, 667.669 659.876, 662.259 660.381, 652.272 660.885, 642.285 661.390, 632.297 661.895, 622.310 662.400, 612.323 662.904, 602.336 663.232, 595.849 663.737, 585.862 664.242, 575.875 664.746, 565.887 665.251, 555.900 665.756, 545.913 666.261, 535.926 666.765, 525.938 667.031, 520.687 657.484, 523.665 657.108, 523.783 647.561, 526.760 638.015, 529.738 628.469, 532.716 618.922, 535.694 611.050, 538.150 606.856, 539.458 597.309, 542.436 587.763, 545.413 578.217, 548.391 568.670, 551.369 559.124, 554.347 549.578, 557.325 548.975, 557.513 539.429, 560.490 529.882, 563.468 520.336, 566.446 510.790, 569.424 501.243, 572.402 491.697, 575.379 482.151, 578.357 479.755, 579.105 470.597, 575.087 461.440, 571.070 452.282, 567.052 443.124, 563.035 433.967, 559.018 424.809, 555.000 422.995, 554.204 413.837, 550.187 404.680, 546.169 395.522, 542.152 394.972, 541.911 385.815, 537.893 376.657, 533.876 372.939, 532.245 363.782, 528.227 354.624, 524.210 354.088, 523.975 344.930, 519.957 335.773, 515.940 328.466, 512.734 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 679.567, 272.637 669.602, 273.476 669.015, 273.525 659.050, 274.365 649.086, 275.203 639.121, 276.043 629.156, 276.882 619.848, 277.665 615.356, 278.043 605.391, 278.883 595.426, 279.721 585.462, 280.561 575.497, 281.400 565.532, 282.239 555.567, 283.078 553.084, 283.287 552.934, 293.286 552.785, 303.284 552.635, 313.283 552.485, 323.282 552.335, 333.281 552.185, 343.280 552.035, 353.279 551.886, 363.278 551.736, 373.277 551.640, 379.669 551.490, 389.668 551.340, 399.667 551.190, 409.666 551.041, 419.665 550.891, 429.664 550.741, 439.663 550.729, 440.449 550.579, 450.448 550.430, 460.447 550.280, 470.446 550.272, 470.946 559.996, 468.611 569.719, 466.275 579.442, 463.939 589.166, 461.604 598.889, 459.268 608.613, 456.932 613.835, 455.678 623.559, 453.342 633.282, 451.007 643.006, 448.671 652.729, 446.335 662.452, 444.000 671.010, 441.944 680.734, 439.608 690.457, 437.273 700.181, 434.937 709.904, 432.601 719.627, 430.266 722.714, 429.524 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 479.755, 579.105 479.692, 569.105 479.630, 559.105 479.568, 549.105 479.505, 539.105 479.443, 529.106 479.380, 519.106 479.318, 509.106 479.256, 499.106 479.193, 489.106 479.187, 488.022 479.124, 478.022 479.062, 468.022 479.000, 458.022 478.986, 455.867 478.948, 449.833 478.886, 439.833 478.824, 429.834 478.761, 419.834 478.699, 409.834 478.637, 399.834 478.586, 391.681 478.523, 381.681 478.461, 371.681 478.399, 361.681 478.336, 351.682 478.274, 341.682 478.212, 331.682 478.149, 321.682 478.087, 311.682 478.025, 301.683 477.962, 291.683 477.949, 289.613 477.887, 279.613 477.825, 269.613 477.762, 259.613 477.700, 249.614 477.638, 239.614 477.575, 229.614 477.513, 219.614 477.450, 209.614 477.388, 199.615 477.326, 189.615 477.274, 181.292 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 615.356, 278.043 615.031, 288.038 614.706, 298.033 614.381, 308.028 614.056, 318.022 613.731, 328.017 613.406, 338.012 613.081, 348.007 613.037, 349.355 622.910, 350.944 632.783, 352.533 642.656, 354.122 652.529, 355.711 662.402, 357.300 672.275, 358.888 675.184, 359.357 685.052, 357.734 694.919, 356.112 704.787, 354.489 714.654, 352.867 724.522, 351.245 727.884, 350.692 728.538, 340.713 729.192, 330.735 729.847, 320.756 730.501, 310.778 731.155, 300.799 731.810, 290.820 732.464, 280.842 733.119, 270.863 733.299, 268.113 733.953, 258.134 734.608, 248.156 735.262, 238.177 735.917, 228.199 736.571, 218.220 737.225, 208.241 737.880, 198.263 738.534, 188.284 738.978, 181.514 728.978, 181.505 718.978, 181.497 708.978, 181.488 698.978, 181.480 688.978, 181.471 684.174, 181.467 674.174, 181.459 664.174, 181.450 654.174, 181.442 644.174, 181.433 634.174, 181.425 624.174, 181.416 623.100, 181.415 613.100, 181.407 603.100, 181.398 593.100, 181.390 583.100, 181.381 573.100, 181.373 563.100, 181.364 554.612, 181.357 544.612, 181.349 534.612, 181.340 524.612, 181.332 514.612, 181.323 504.612, 181.315 494.612, 181.306 484.612, 181.298 477.274, 181.292 467.274, 181.303 457.274, 181.315 447.274, 181.327 437.274, 181.339 427.274, 181.351 417.274, 181.363 414.219, 181.367 414.454, 191.364 414.690, 201.362 414.925, 211.359 415.160, 221.356 415.396, 231.353 415.631, 241.350 415.866, 251.348 416.101, 261.345 416.337, 271.342 416.595, 282.323 416.831, 292.320 417.066, 302.317 417.301, 312.314 417.537, 322.312 417.772, 332.309 418.007, 342.306 418.242, 352.303 418.478, 362.300 418.713, 372.298 418.843, 377.836 419.079, 387.833 419.314, 397.830 419.549, 407.827 419.785, 417.825 420.020, 427.822 420.128, 432.422 420.364, 442.419 420.599, 452.416 420.834, 462.413 420.974, 468.335 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 675.184, 359.357 675.689, 349.369 676.194, 339.382 676.698, 329.395 677.203, 319.408 677.708, 309.420 678.213, 299.433 678.717, 289.446 679.222, 279.459 679.567, 272.637 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 606.856, 539.458 606.531, 549.453 606.206, 559.447 605.881, 569.442 605.733, 573.994 605.408, 583.988 605.083, 593.983 604.758, 603.978 604.433, 613.973 604.108, 623.967 603.783, 633.962 603.671, 637.388 603.503, 642.558 594.221, 646.278 584.939, 649.999 575.657, 653.719 568.089, 656.752 558.807, 660.473 550.067, 663.976 547.363, 665.060 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 420.974, 468.335 411.501, 465.131 402.028, 461.927 392.555, 458.723 383.082, 455.520 373.609, 452.316 369.806, 451.030 360.333, 447.826 350.860, 444.623 341.387, 441.419 331.914, 438.215 324.478, 435.700 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 311.316, 181.490 311.833, 191.477 312.350, 201.464 312.867, 211.450 313.384, 221.437 313.901, 231.424 314.418, 241.410 314.935, 251.397 315.452, 261.383 315.917, 270.360 316.434, 280.346 316.951, 290.333 317.468, 300.320 317.985, 310.306 318.502, 320.293 319.020, 330.280 319.537, 340.266 320.054, 350.253 320.299, 354.998 320.816, 364.985 321.333, 374.971 321.851, 384.958 322.368, 394.945 322.885, 404.931 323.402, 414.918 323.919, 424.904 324.436, 434.891 324.478, 435.700 334.324, 433.952 344.170, 432.205 354.016, 430.457 363.862, 428.709 373.708, 426.961 383.554, 425.214 391.196, 423.857 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 420.128, 432.422 410.540, 429.583 400.951, 426.745 391.196, 423.857 390.877, 413.862 390.557, 403.867 390.237, 393.872 389.918, 383.878 389.598, 373.883 389.278, 363.888 388.959, 353.893 388.730, 346.745 388.410, 336.750 388.091, 326.755 387.771, 316.760 387.451, 306.765 387.132, 296.770 386.812, 286.776 386.492, 276.781 386.149, 266.056 385.830, 256.061 385.510, 246.066 385.190, 236.071 384.871, 226.076 384.551, 216.081 384.231, 206.086 383.911, 196.091 383.592, 186.097 383.446, 181.535 373.446, 181.529 363.446, 181.522 353.446, 181.516 343.446, 181.510 333.446, 181.504 323.446, 181.498 313.446, 181.492 311.316, 181.490 301.316, 181.502 291.316, 181.514 281.316, 181.526 271.316, 181.538 268.785, 181.541 269.418, 191.521 270.051, 201.501 270.685, 211.481 271.318, 221.461 271.951, 231.441 272.584, 241.421 273.217, 251.401 273.851, 261.381 274.105, 265.392 274.738, 275.372 275.372, 285.352 276.005, 295.331 276.638, 305.311 277.271, 315.291 277.904, 325.271 278.538, 335.251 279.186, 345.470 279.819, 355.450 280.452, 365.430 281.086, 375.410 281.719, 385.390 282.352, 395.370 282.985, 405.350 283.618, 415.330 284.043, 422.025 284.677, 432.005 285.310, 441.985 285.943, 451.965 286.576, 461.945 287.209, 471.925 287.843, 481.905 288.476, 491.885 288.691, 495.285 289.325, 505.265 289.958, 515.245 290.591, 525.225 291.224, 535.205 291.858, 545.185 292.491, 555.165 293.144, 565.458 293.777, 575.438 294.410, 585.418 295.043, 595.398 295.677, 605.378 296.310, 615.357 296.943, 625.337 297.412, 632.734 298.046, 642.714 298.679, 652.694 299.312, 662.674 299.906, 672.036 300.539, 682.016 301.173, 691.996 301.508, 697.290 309.665, 703.075 313.742, 705.966 321.899, 711.750 330.056, 717.535 338.213, 723.320 339.414, 724.172 348.750, 720.590 358.087, 717.008 367.423, 713.426 376.760, 709.844 386.096, 706.262 395.433, 702.680 400.057, 700.905 399.738, 690.910 399.418, 680.916 399.224, 674.838 398.904, 664.843 398.584, 654.848 398.265, 644.853 398.075, 638.923 397.980, 635.962 397.661, 625.967 397.341, 615.972 397.021, 605.977 396.702, 595.982 396.382, 585.987 396.062, 575.992 395.815, 568.261 395.495, 558.267 395.176, 548.272 394.972, 541.911 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(254, 255, 0)" stroke-linejoin="round" d=" M 328.466, 512.734 328.984, 522.721 329.501, 532.708 330.018, 542.694 330.535, 552.681 331.052, 562.668 331.569, 572.654 332.086, 582.641 332.278, 586.345 332.795, 596.332 333.312, 606.319 333.829, 616.305 334.346, 626.292 334.863, 636.279 335.380, 646.265 335.923, 656.756 336.083, 659.833 336.600, 669.820 337.117, 679.807 337.634, 689.793 338.151, 699.780 338.668, 709.766 339.185, 719.753 339.414, 724.172 " />
|
||||
</g>
|
||||
</g>
|
||||
<g id="ViewLayer_LineSetCUbe" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="lineset" inkscape:label="ViewLayer_LineSetCUbe">
|
||||
<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:groupmode="layer" id="strokes" inkscape:label="strokes">
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 255)" stroke-linejoin="round" d=" M 472.113, 940.694 472.016, 930.695 471.919, 920.695 471.823, 910.695 471.726, 900.696 471.629, 890.696 471.620, 889.744 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 255)" stroke-linejoin="round" d=" M 783.925, 717.152 775.172, 721.989 766.420, 726.826 757.668, 731.663 748.915, 736.500 740.163, 741.337 731.411, 746.174 722.658, 751.011 713.906, 755.848 705.153, 760.685 696.401, 765.522 687.649, 770.358 678.896, 775.195 670.144, 780.032 661.391, 784.869 652.639, 789.706 643.887, 794.543 635.134, 799.380 626.382, 804.217 617.629, 809.054 608.877, 813.891 600.125, 818.727 591.372, 823.564 582.620, 828.401 573.867, 833.238 565.115, 838.075 556.363, 842.912 547.610, 847.749 538.858, 852.586 530.106, 857.423 521.353, 862.260 512.601, 867.097 503.848, 871.933 495.096, 876.770 486.344, 881.607 477.591, 886.444 471.620, 889.744 463.771, 883.548 455.921, 877.353 448.072, 871.157 440.222, 864.962 432.373, 858.766 424.523, 852.570 416.674, 846.375 408.824, 840.179 400.975, 833.984 393.125, 827.788 385.276, 821.592 377.426, 815.397 369.577, 809.201 361.727, 803.006 353.878, 796.810 346.028, 790.614 338.179, 784.419 330.329, 778.223 322.480, 772.027 314.630, 765.832 306.781, 759.636 298.931, 753.441 291.082, 747.245 283.232, 741.049 275.383, 734.854 267.533, 728.658 259.684, 722.463 251.834, 716.267 243.985, 710.071 236.135, 703.876 226.966, 696.639 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 255)" stroke-linejoin="round" d=" M 299.906, 672.036 290.431, 675.232 280.955, 678.428 271.480, 681.624 262.004, 684.820 252.529, 688.016 243.053, 691.212 233.578, 694.409 226.966, 696.639 227.836, 706.601 228.705, 716.563 229.574, 726.525 230.427, 736.304 238.063, 742.761 245.699, 749.219 253.334, 755.676 260.970, 762.133 268.606, 768.591 276.241, 775.048 283.877, 781.505 291.512, 787.963 299.148, 794.420 306.784, 800.877 314.419, 807.335 322.055, 813.792 329.690, 820.250 337.326, 826.707 344.962, 833.164 352.597, 839.622 360.233, 846.079 367.869, 852.536 375.504, 858.994 383.140, 865.451 390.775, 871.908 398.411, 878.366 406.047, 884.823 413.682, 891.280 421.318, 897.738 428.954, 904.195 436.589, 910.652 444.225, 917.110 451.860, 923.567 459.496, 930.024 467.132, 936.482 472.113, 940.694 480.715, 935.596 489.318, 930.497 497.920, 925.398 506.523, 920.300 515.126, 915.201 523.728, 910.103 532.331, 905.004 540.934, 899.906 549.536, 894.807 558.139, 889.709 566.741, 884.610 575.344, 879.512 583.947, 874.413 592.549, 869.315 601.152, 864.216 609.755, 859.118 618.357, 854.019 626.960, 848.921 635.562, 843.822 644.165, 838.724 652.768, 833.625 661.370, 828.527 669.973, 823.428 678.575, 818.329 687.178, 813.231 695.781, 808.132 704.383, 803.034 712.986, 797.935 721.589, 792.837 730.191, 787.738 738.794, 782.640 747.396, 777.541 755.999, 772.443 764.602, 767.344 773.204, 762.246 780.184, 758.109 781.094, 748.150 782.003, 738.192 782.913, 728.233 783.822, 718.275 783.925, 717.152 774.780, 713.106 765.636, 709.059 756.491, 705.013 747.346, 700.966 738.202, 696.919 729.057, 692.873 719.912, 688.826 710.768, 684.779 706.110, 682.718 " />
|
||||
<path fill="none" stroke-width="3.0" stroke-linecap="butt" stroke-opacity="1.0" stroke="rgb(0, 0, 255)" stroke-linejoin="round" d=" M 603.671, 637.388 594.527, 633.341 585.382, 629.295 576.237, 625.248 567.093, 621.201 557.948, 617.155 548.803, 613.108 539.659, 609.061 530.514, 605.015 521.369, 600.968 516.711, 598.907 507.236, 602.103 497.760, 605.299 488.285, 608.495 478.809, 611.691 469.334, 614.887 459.858, 618.084 450.383, 621.280 440.907, 624.476 431.432, 627.672 421.957, 630.868 412.481, 634.064 403.006, 637.260 398.075, 638.923 " />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 210 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<path id="square_red" d="M100 100 H600 V600 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green" d="M200 200 H700 V700 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue" d="M300 300 H800 V800 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000.0" height="1000.0" viewBox="0 0 1000.0 1000.0"><path d="M 549.880981 337.554932 L 602.676392 334.548950" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 166.580200 374.496460 L 159.530640 358.886719 L 166.091919 342.437744 L 183.883667 332.107544 L 200.012207 336.105347 L 208.679199 353.897095 L 213.745117 403.671265 L 203.598022 417.160034 L 166.122437 450.973511 L 157.546997 469.573975 L 231.430054 472.534180" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 463.256836 381.195068 L 464.691162 464.141846 L 466.278076 458.114624" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 804.565430 334.259033 L 829.193115 340.972900 L 876.922607 341.156006 L 890.823364 345.550537 L 881.057739 368.179321 L 844.039917 432.327271 L 830.520630 479.827881 L 830.154419 492.568970 L 826.553345 496.047974 L 824.600220 482.360840" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 543.838501 333.038330 L 547.637939 394.317627 L 551.116943 406.646729 L 575.317383 392.944336 L 598.968506 393.692017 L 617.950439 409.057617 L 625.701904 434.417725 L 624.298096 448.593140 L 618.972778 460.876465 L 610.549927 469.604492 L 598.754883 475.250244 L 584.869385 476.776123 L 570.220947 473.770142 L 557.540894 466.644287 L 548.370361 456.726074" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 270.217896 348.831177 L 278.228760 335.693359 L 292.755127 329.483032 L 311.737061 331.100464 L 329.025269 340.637207 L 338.104248 356.384277 L 333.663940 370.681763 L 319.107056 381.362915 L 296.478271 387.191772 L 285.202026 393.890381 L 322.052002 393.463135 L 340.270996 398.117065 L 355.285645 413.314819 L 361.770630 432.418823 L 354.217529 449.539185 L 332.626343 461.395264 L 298.690796 466.537476 L 270.217896 461.959839" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 746.017456 333.450317 L 733.093262 332.153320 L 720.062256 335.235596 L 691.009521 355.209351 L 678.466797 379.531860 L 671.356201 414.123535 L 673.202515 448.501587 L 678.054810 460.647583 L 685.455322 468.719482 L 695.861816 472.366333 L 709.213257 471.008301 L 723.983765 465.560913 L 735.046387 457.122803 L 741.546631 446.365356 L 742.721558 434.509277 L 738.632202 422.317505 L 729.431152 410.385132 L 676.635742 408.874512" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 52.764893 394.653320 L 91.720581 355.346680 L 105.422974 330.307007 L 101.409912 387.023926 L 107.055664 477.615356" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 823.089600 419.006348 L 881.912231 419.006348" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /><path d="M 443.649292 325.378418 L 405.807495 383.636475 L 399.490356 399.063110 L 399.658203 408.828735 L 403.976440 412.704468 L 412.872314 413.955688 L 444.610596 408.401489 L 479.858398 406.829834" stroke="rgb(255,0,0)" fill="none" stroke-width="1.0" /></svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
id="svg5"
|
||||
version="1.1"
|
||||
viewBox="0 0 1000 1000"
|
||||
height="1000"
|
||||
width="1000"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 549.87659,337.55892 c 18.11594,2.26745 35.30238,-3.01674 52.79289,-3.01674"
|
||||
id="52" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 166.575,374.48936 c -25.57439,-26.54613 25.59236,-61.95954 39.21758,-30.16736 5.33558,12.44969 3.89611,26.3934 6.03347,39.21757 0.84861,5.09165 4.06851,15.99687 1.50837,21.11716 -10.49818,20.99635 -55.80963,41.81118 -55.80963,64.85984 0,1.87432 64.61494,5.34051 73.91005,3.01674"
|
||||
id="2" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 463.26288,381.19057 c -0.85479,0.54477 -1.9274,82.96026 1.50837,82.96026 2.07306,0 0.85281,-4.0668 1.50837,-6.03348"
|
||||
id="41" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 804.56224,334.25725 c 10.50938,4.84382 21.69737,6.77606 33.18411,7.54184 7.29064,0.48604 45.84698,-3.92917 52.79289,3.01674 3.93168,3.93168 -32.527,63.32069 -38.43246,72.34699 -13.24678,20.24727 -15.20552,40.23338 -21.4775,62.18531 -1.02599,3.59098 1.34165,12.23367 -1.50836,15.08368 -7.06405,7.06404 -4.8032,-11.23267 -4.52511,-12.06695"
|
||||
id="71" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 543.84311,333.03381 c 1.77707,10.38984 2.06546,21.21169 3.01674,31.67574 0.80139,8.81527 -1.42467,36.28453 4.52511,42.23431 1.77639,1.7764 15.35848,-10.97017 18.10042,-12.06694 33.43995,-13.37599 60.00581,14.69833 55.80962,48.26778 -5.18797,41.50377 -57.32302,42.98097 -76.92678,13.57532"
|
||||
id="51" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 270.22376,348.8356 c 8.57701,-33.04848 63.19802,-20.52952 67.87658,7.54185 2.92455,17.54731 -24.49895,30.16736 -39.21758,30.16736 -2.21689,0 -15.00601,6.11115 -13.57531,7.54184 4.67326,4.67327 46.58894,-10.77167 64.85984,12.06695 45.22792,56.5349 -54.64457,68.45911 -79.94353,55.80963"
|
||||
id="3" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 746.01953,333.45251 c -21.14473,-5.61481 -37.52166,6.88276 -52.79289,19.60879 -22.87762,19.06468 -39.7863,125.26567 9.05021,119.1611 35.76107,-4.47013 55.48868,-33.50504 27.15063,-61.8431 -1.142,-1.142 -45.24402,-1.50837 -52.7929,-1.50837"
|
||||
id="6" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 52.757536,394.64943 c 16.44616,-16.06438 34.36663,-31.18697 46.75942,-49.77616 2.746824,-4.12023 6.033484,-18.52722 6.033484,-13.57531 0,22.43425 -6.746484,47.00652 -3.01674,69.38494 4.14823,24.88933 4.52511,50.70989 4.52511,76.92679"
|
||||
id="1" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 823.08744,419.01339 c 19.27336,-2.0514 39.1271,0 58.82637,0"
|
||||
id="72" />
|
||||
<path
|
||||
style="fill:none;fill-rule:evenodd;stroke:#ff0000;stroke-width:3;stroke-miterlimit:4.2;stroke-dashoffset:0.569953"
|
||||
d="m 443.65409,325.38094 c -1.80786,8.29996 -63.48471,79.39632 -39.21758,87.48536 11.48865,3.82955 29.15608,-3.4733 40.72594,-4.5251 10.72437,-0.97494 26.51518,2.58028 34.69249,-1.50837"
|
||||
id="42" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" viewBox="0 0 1000 1000">
|
||||
<g id="frame001" opacity="1">
|
||||
<path id="square_red_001" d="M100 100 H125 V125 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_001" d="M200 200 H225 V225 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_001" d="M300 300 H325 V325 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0;0;0.05;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame002" opacity="0">
|
||||
<path id="square_red_002" d="M100 100 H150 V150 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_002" d="M200 200 H250 V250 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_002" d="M300 300 H350 V350 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.05;0.05;0.10;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame003" opacity="0">
|
||||
<path id="square_red_003" d="M100 100 H175 V175 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_003" d="M200 200 H275 V275 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_003" d="M300 300 H375 V375 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.10;0.10;0.15;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame004" opacity="0">
|
||||
<path id="square_red_004" d="M100 100 H200 V200 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_004" d="M200 200 H300 V300 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_004" d="M300 300 H400 V400 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.15;0.15;0.20;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame005" opacity="0">
|
||||
<path id="square_red_005" d="M100 100 H225 V225 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_005" d="M200 200 H325 V325 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_005" d="M300 300 H425 V425 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.20;0.20;0.25;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame006" opacity="0">
|
||||
<path id="square_red_006" d="M100 100 H250 V250 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_006" d="M200 200 H350 V350 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_006" d="M300 300 H450 V450 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.25;0.25;0.30;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame007" opacity="0">
|
||||
<path id="square_red_007" d="M100 100 H275 V275 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_007" d="M200 200 H375 V375 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_007" d="M300 300 H475 V475 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.30;0.30;0.35;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame008" opacity="0">
|
||||
<path id="square_red_008" d="M100 100 H300 V300 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_008" d="M200 200 H400 V400 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_008" d="M300 300 H500 V500 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.35;0.35;0.40;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame009" opacity="0">
|
||||
<path id="square_red_009" d="M100 100 H325 V325 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_009" d="M200 200 H425 V425 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_009" d="M300 300 H525 V525 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.40;0.40;0.45;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame010" opacity="0">
|
||||
<path id="square_red_010" d="M100 100 H350 V350 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_010" d="M200 200 H450 V450 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_010" d="M300 300 H550 V550 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.45;0.45;0.50;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame011" opacity="0">
|
||||
<path id="square_red_011" d="M100 100 H375 V375 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_011" d="M200 200 H475 V475 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_011" d="M300 300 H575 V575 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.50;0.50;0.55;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame012" opacity="0">
|
||||
<path id="square_red_012" d="M100 100 H400 V400 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_012" d="M200 200 H500 V500 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_012" d="M300 300 H600 V600 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.55;0.55;0.60;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame013" opacity="0">
|
||||
<path id="square_red_013" d="M100 100 H425 V425 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_013" d="M200 200 H525 V525 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_013" d="M300 300 H625 V625 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.60;0.60;0.65;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame014" opacity="0">
|
||||
<path id="square_red_014" d="M100 100 H450 V450 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_014" d="M200 200 H550 V550 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_014" d="M300 300 H650 V650 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.65;0.65;0.70;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame015" opacity="0">
|
||||
<path id="square_red_015" d="M100 100 H475 V475 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_015" d="M200 200 H575 V575 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_015" d="M300 300 H675 V675 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.70;0.70;0.75;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame016" opacity="0">
|
||||
<path id="square_red_016" d="M100 100 H500 V500 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_016" d="M200 200 H600 V600 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_016" d="M300 300 H700 V700 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.75;0.75;0.80;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame017" opacity="0">
|
||||
<path id="square_red_017" d="M100 100 H525 V525 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_017" d="M200 200 H625 V625 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_017" d="M300 300 H725 V725 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.80;0.80;0.85;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame018" opacity="0">
|
||||
<path id="square_red_018" d="M100 100 H550 V550 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_018" d="M200 200 H650 V650 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_018" d="M300 300 H750 V750 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.85;0.85;0.90;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame019" opacity="0">
|
||||
<path id="square_red_019" d="M100 100 H575 V575 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_019" d="M200 200 H675 V675 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_019" d="M300 300 H775 V775 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.90;0.90;0.95;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
<g id="frame020" opacity="0">
|
||||
<path id="square_red_020" d="M100 100 H600 V600 H100 Z" stroke="#ff0000" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_green_020" d="M200 200 H700 V700 H200 Z" stroke="#00ff00" stroke-width="3.0" fill="none"/>
|
||||
<path id="square_blue_020" d="M300 300 H800 V800 H300 Z" stroke="#0000ff" stroke-width="3.0" fill="none"/>
|
||||
<animate attributeName="opacity" dur="2s" repeatCount="indefinite" calcMode="discrete" values="0;0;1;0;0" keyTimes="0;0.95;0.95;1.00;1" begin="0s" fill="remove"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
# test_animation.py
|
||||
from ilda.animation import Animation
|
||||
import sys
|
||||
|
||||
|
||||
SVG_FOLDER = "animation"
|
||||
OUT_ILDA = "rgb-anim.ild"
|
||||
SIMPLIFY = True
|
||||
TOL = 3.0 # simplify ratio
|
||||
# -----------------------------
|
||||
|
||||
def main() -> None:
|
||||
anim = Animation(ilda_format=0, company_name="MIKLO", projector=0)
|
||||
print(f"Reading SVGs from: {SVG_FOLDER}")
|
||||
anim.create_from_folder(SVG_FOLDER, simplify=SIMPLIFY, tol=TOL)
|
||||
print(f"Loaded {len(anim.frames)} frames; writing ILDA to: {OUT_ILDA}")
|
||||
anim.write_ild(OUT_ILDA)
|
||||
print("Done.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python3
|
||||
from typing import Tuple
|
||||
from ilda.frame import Frame
|
||||
import sys
|
||||
|
||||
CANVAS_SIZE: [Tuple[float, float]] = (1000.0,1000.0) # svg canvas size
|
||||
TOL: float = 2.0 # svg path tolerance
|
||||
|
||||
def convert_to_ild(infile: str, outfile: str, simplify: bool = False) -> None:
|
||||
frame = Frame()
|
||||
try:
|
||||
frame.read_svg(infile, simplify=True, tol=TOL)
|
||||
except FileNotFoundError:
|
||||
print(f"Input file '{infile}' not found.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
frame.write_ild(outfile)
|
||||
print(f"Wrote ILDA to {outfile}")
|
||||
|
||||
def convert_to_svg(infile: str, outfile: str, simplify: bool = False, mintravel: bool = False) -> None:
|
||||
frame = Frame()
|
||||
try:
|
||||
frame.read_svg(infile, simplify=True, tol=TOL)
|
||||
except FileNotFoundError:
|
||||
print(f"Input file '{infile}' not found.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
frame.write_svg(outfile,canvas_size=CANVAS_SIZE)
|
||||
print(f"Wrote (simplified) SVG to {outfile}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
convert_to_ild('frame/hs.svg',
|
||||
'frame/hs.ild',
|
||||
simplify=True)
|
||||
convert_to_ild('frame/text-paths.svg',
|
||||
'frame/text-paths.ild',
|
||||
simplify=True)
|
||||
convert_to_ild('frame/opos.svg',
|
||||
'frame/opos.ild',
|
||||
simplify=True)
|
||||
convert_to_ild('frame/rgb.svg',
|
||||
'frame/rgb.ild',
|
||||
simplify=True)
|
||||
|
||||
convert_to_svg('frame/opos.svg',
|
||||
'frame/opos-simpl.svg',
|
||||
simplify=True)
|
||||
convert_to_svg('frame/text-paths.svg',
|
||||
'frame/text-paths-simpl.svg',
|
||||
simplify=True,
|
||||
mintravel=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||