init_code
This commit is contained in:
3
pygarment/pattern/__init__.py
Normal file
3
pygarment/pattern/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Package with various 2D garment pattern wrappers when pattern is given in custom .json format
|
||||
"""
|
||||
165
pygarment/pattern/cairo_dlls/cairosvg_LICENSE.txt
Normal file
165
pygarment/pattern/cairo_dlls/cairosvg_LICENSE.txt
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 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.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser 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
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
BIN
pygarment/pattern/cairo_dlls/libGraphicsMagick++-12.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libGraphicsMagick++-12.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libbrotlicommon.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libbrotlicommon.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libbrotlidec.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libbrotlidec.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libbrotlienc.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libbrotlienc.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libbz2-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libbz2-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libcairo-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libcairo-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libcairo-gobject-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libcairo-gobject-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libcairomm-1.0-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libcairomm-1.0-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libexpat-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libexpat-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libffi-8.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libffi-8.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libfontconfig-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libfontconfig-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libfreetype-6.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libfreetype-6.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libfribidi-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libfribidi-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgc-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgc-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgcc_s_seh-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgcc_s_seh-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgdk_pixbuf-2.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgdk_pixbuf-2.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgdkmm-3.0-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgdkmm-3.0-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgirepository-1.0-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgirepository-1.0-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libglib-2.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libglib-2.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libglibmm-2.4-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libglibmm-2.4-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgmodule-2.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgmodule-2.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgobject-2.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgobject-2.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgomp-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgomp-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgraphite2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgraphite2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgslcblas-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgslcblas-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libgspell-1-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libgspell-1-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libharfbuzz-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libharfbuzz-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libheif.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libheif.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libhwy.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libhwy.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libiconv-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libiconv-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libidn2-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libidn2-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libintl-8.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libintl-8.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libjasper.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libjasper.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libjbig-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libjbig-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libjxl_threads.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libjxl_threads.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/liblcms2-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/liblcms2-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/liblqr-1-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/liblqr-1-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libltdl-7.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libltdl-7.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/liblzma-5.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/liblzma-5.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libmpdec-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libmpdec-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libncursesw6.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libncursesw6.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libnghttp2-14.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libnghttp2-14.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libnspr4.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libnspr4.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libopenjp2-7.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libopenjp2-7.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpanelw6.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpanelw6.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpango-1.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpango-1.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpangocairo-1.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpangocairo-1.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpangoft2-1.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpangoft2-1.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpangomm-1.4-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpangomm-1.4-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpangowin32-1.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpangowin32-1.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpcre2-8-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpcre2-8-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpixman-1-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpixman-1-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libplc4.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libplc4.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libplds4.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libplds4.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpng16-16.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpng16-16.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpoppler-glib-8.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpoppler-glib-8.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpotrace-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpotrace-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libpsl-5.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libpsl-5.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libquadmath-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libquadmath-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libraqm-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libraqm-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libreadline8.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libreadline8.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/librevenge-0.0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/librevenge-0.0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/librevenge-stream-0.0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/librevenge-stream-0.0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libsigc-2.0-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libsigc-2.0-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libsoup-2.4-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libsoup-2.4-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libssh2-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libssh2-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libssl-1_1-x64.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libssl-1_1-x64.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libstdc++-6.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libstdc++-6.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libtermcap-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libtermcap-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libthai-0.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libthai-0.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libtiff-5.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libtiff-5.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libvisio-0.1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libvisio-0.1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwebpdemux-2.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwebpdemux-2.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwebpmux-3.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwebpmux-3.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwinpthread-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwinpthread-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwmf-0-2-7.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwmf-0-2-7.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwmflite-0-2-7.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwmflite-0-2-7.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwpd-0.10.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwpd-0.10.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libwpg-0.3.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libwpg-0.3.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libxslt-1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libxslt-1.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/libxxhash.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/libxxhash.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/nss3.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/nss3.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/nssutil3.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/nssutil3.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/smime3.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/smime3.dll
Normal file
Binary file not shown.
BIN
pygarment/pattern/cairo_dlls/zlib1.dll
Normal file
BIN
pygarment/pattern/cairo_dlls/zlib1.dll
Normal file
Binary file not shown.
973
pygarment/pattern/core.py
Normal file
973
pygarment/pattern/core.py
Normal file
@@ -0,0 +1,973 @@
|
||||
"""
|
||||
Module for basic operations on patterns
|
||||
"""
|
||||
# Basic
|
||||
import copy
|
||||
import errno
|
||||
import json
|
||||
import numpy as np
|
||||
import os
|
||||
import random
|
||||
import svgpathtools as svgpath
|
||||
|
||||
# My
|
||||
|
||||
from . import rotation as rotation_tools
|
||||
from . import utils
|
||||
|
||||
standard_filenames = [
|
||||
'specification', # e.g. used by dataset generation
|
||||
'template',
|
||||
'prediction'
|
||||
]
|
||||
|
||||
pattern_spec_template = {
|
||||
'pattern': {
|
||||
'panels': {},
|
||||
'stitches': []
|
||||
},
|
||||
'parameters': {},
|
||||
'parameter_order': [],
|
||||
'properties': { # these are to be ensured when pattern content is updated directly
|
||||
'curvature_coords': 'relative',
|
||||
'normalize_panel_translation': False,
|
||||
'normalized_edge_loops': True, # will trigger edge loop normalization on reload
|
||||
'units_in_meter': 100 # cm
|
||||
}
|
||||
}
|
||||
|
||||
panel_spec_template = {
|
||||
'translation': [ 0, 0, 0 ],
|
||||
'rotation': [ 0, 0, 0 ],
|
||||
'vertices': [],
|
||||
'edges': []
|
||||
}
|
||||
|
||||
class EmptyPatternError(BaseException):
|
||||
def __init__(self, *args: object) -> None:
|
||||
super().__init__(*args)
|
||||
|
||||
# ------------ Patterns --------
|
||||
class BasicPattern(object):
|
||||
"""Loading & serializing of a pattern specification in custom JSON format.
|
||||
Input:
|
||||
* Pattern template in custom JSON format
|
||||
Output representations:
|
||||
* Pattern instance in custom JSON format
|
||||
* In the current state
|
||||
|
||||
Not implemented:
|
||||
* Convertion to NN-friendly format
|
||||
* Support for patterns with darts
|
||||
"""
|
||||
|
||||
# ------------ Interface -------------
|
||||
|
||||
def __init__(self, pattern_file=None):
|
||||
|
||||
self.spec_file = pattern_file
|
||||
|
||||
if pattern_file is not None: # load pattern from file
|
||||
self.path = os.path.dirname(pattern_file)
|
||||
self.name = BasicPattern.name_from_path(pattern_file)
|
||||
self.reloadJSON()
|
||||
else: # create empty pattern
|
||||
self.path = None
|
||||
self.name = self.__class__.__name__
|
||||
self.spec = copy.deepcopy(pattern_spec_template)
|
||||
self.pattern = self.spec['pattern']
|
||||
self.properties = self.spec['properties'] # mandatory part
|
||||
|
||||
def reloadJSON(self):
|
||||
"""(Re)loads pattern info from spec file.
|
||||
Useful when spec is updated from outside"""
|
||||
if self.spec_file is None:
|
||||
print('BasicPattern::WARNING::{}::Pattern is not connected to any file. Reloadig from file request ignored.'.format(
|
||||
self.name
|
||||
))
|
||||
return
|
||||
|
||||
with open(self.spec_file, 'r') as f_json:
|
||||
self.spec = json.load(f_json)
|
||||
self.pattern = self.spec['pattern']
|
||||
self.properties = self.spec['properties'] # mandatory part
|
||||
|
||||
# template normalization - panel translations and curvature to relative coords
|
||||
self._normalize_template()
|
||||
|
||||
def serialize(self, path, to_subfolder=True, tag='', empty_ok=False):
|
||||
|
||||
if not empty_ok and len(self.panel_order()) == 0:
|
||||
raise RuntimeError(f'{self.__class__.__name__}::ERROR::Asked to save an empty pattern')
|
||||
|
||||
# log context
|
||||
if tag:
|
||||
tag = '_' + tag
|
||||
if to_subfolder:
|
||||
log_dir = os.path.join(path, self.name + tag) # NOTE Added change
|
||||
try:
|
||||
os.makedirs(log_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
else:
|
||||
log_dir = path
|
||||
|
||||
spec_file = os.path.join(log_dir, (self.name + tag + '_specification.json'))
|
||||
|
||||
# Save specification
|
||||
with open(spec_file, 'w') as f_json:
|
||||
json.dump(self.spec, f_json, indent=2)
|
||||
|
||||
|
||||
|
||||
# print('{}::{}::Pattern saved to {}'.format(self.__class__.__name__, self.name, spec_file))
|
||||
|
||||
return log_dir
|
||||
|
||||
@staticmethod
|
||||
def name_from_path(pattern_file):
|
||||
name = os.path.splitext(os.path.basename(pattern_file))[0]
|
||||
if name.endswith('_specification'):
|
||||
name = name.split('_specification')[0]
|
||||
if name in standard_filenames: # use name of directory instead
|
||||
path = os.path.dirname(pattern_file)
|
||||
name = os.path.basename(os.path.normpath(path))
|
||||
return name
|
||||
|
||||
# --------- Info ------------------------
|
||||
def panel_order(self, force_update=False):
|
||||
"""
|
||||
Return current agreed-upon order of panels
|
||||
* if not defined in the pattern or if 'force_update' is enabled, re-evaluate it based on curent panel translation and save
|
||||
"""
|
||||
if 'panel_order' not in self.pattern or force_update:
|
||||
self.pattern['panel_order'] = self.define_panel_order()
|
||||
return self.pattern['panel_order']
|
||||
|
||||
def define_panel_order(self, name_list=None, location_dict=None, dim=0, tolerance=10):
|
||||
""" (Recursive) Ordering of the panels based on their 3D translation values.
|
||||
* Using cm as units for tolerance (when the two coordinates are considered equal)
|
||||
* Sorting by all dims as keys X -> Y -> Z (left-right (looking from Z) then down-up then back-front)
|
||||
* based on the fuzzysort suggestion here https://stackoverflow.com/a/24024801/11206726"""
|
||||
|
||||
if name_list is None: # start from beginning
|
||||
name_list = self.pattern['panels'].keys()
|
||||
if not name_list:
|
||||
return []
|
||||
if location_dict is None: # obtain location for all panels to use in sorting further
|
||||
location_dict = {}
|
||||
for name in name_list:
|
||||
location_dict[name], _ = self._panel_universal_transtation(name)
|
||||
|
||||
# consider only translations of the requested panel names
|
||||
reference = [location_dict[panel_n][dim] for panel_n in name_list]
|
||||
sorted_couple = sorted(zip(reference, name_list)) # sorts according to the first list
|
||||
sorted_reference, sorted_names = zip(*sorted_couple)
|
||||
sorted_names = list(sorted_names)
|
||||
|
||||
if (dim + 1) < 3: # 3D is max
|
||||
# re-sort values by next dimention if they have similar values in current dimention
|
||||
fuzzy_start, fuzzy_end = 0, 0 # init both in case we start from 1 panel to sort
|
||||
for fuzzy_end in range(1, len(sorted_reference)):
|
||||
if sorted_reference[fuzzy_end] - sorted_reference[fuzzy_start] >= tolerance:
|
||||
# the range of similar values is completed
|
||||
if fuzzy_end - fuzzy_start > 1:
|
||||
sorted_names[fuzzy_start:fuzzy_end] = self.define_panel_order(
|
||||
sorted_names[fuzzy_start:fuzzy_end], location_dict, dim + 1, tolerance)
|
||||
fuzzy_start = fuzzy_end # start counting similar values anew
|
||||
|
||||
# take care of the tail
|
||||
if fuzzy_start != fuzzy_end:
|
||||
sorted_names[fuzzy_start:] = self.define_panel_order(
|
||||
sorted_names[fuzzy_start:], location_dict, dim + 1, tolerance)
|
||||
|
||||
return sorted_names
|
||||
|
||||
# -- sub-utils --
|
||||
def _edge_as_vector(self, vertices, edge_dict):
|
||||
"""Represent edge as vector of fixed length:
|
||||
* First 2 elements: Vector endpoint.
|
||||
Original edge endvertex positions can be restored if edge vector is added to the start point,
|
||||
which in turn could be obtained from previous edges in the panel loop
|
||||
* Next 2 elements: Curvature values
|
||||
Given in relative coordinates. With zeros if edge is not curved
|
||||
|
||||
"""
|
||||
edge_verts = vertices[edge_dict['endpoints']]
|
||||
edge_vector = edge_verts[1] - edge_verts[0]
|
||||
curvature = np.array(edge_dict['curvature']) if 'curvature' in edge_dict else [0, 0]
|
||||
|
||||
return np.concatenate([edge_vector, curvature])
|
||||
|
||||
def _edge_as_curve(self, vertices, edge):
|
||||
start = vertices[edge['endpoints'][0]]
|
||||
end = vertices[edge['endpoints'][1]]
|
||||
if ('curvature' in edge):
|
||||
# NOTE: supports old curves
|
||||
if isinstance(edge['curvature'], list) or edge['curvature']['type'] == 'quadratic':
|
||||
control_scale = self._flip_y(edge['curvature'] if isinstance(edge['curvature'], list) else edge['curvature']['params'][0])
|
||||
control_point = utils.rel_to_abs_2d(start, end, control_scale)
|
||||
return svgpath.QuadraticBezier(*utils.list_to_c([start, control_point, end]))
|
||||
elif edge['curvature']['type'] == 'circle': # Assuming circle
|
||||
# https://svgwrite.readthedocs.io/en/latest/classes/path.html#svgwrite.path.Path.push_arc
|
||||
|
||||
radius, large_arc, right = edge['curvature']['params']
|
||||
|
||||
return svgpath.Arc(
|
||||
utils.list_to_c(start), radius + 1j*radius,
|
||||
rotation=0,
|
||||
large_arc=large_arc,
|
||||
sweep=not right,
|
||||
end=utils.list_to_c(end)
|
||||
)
|
||||
|
||||
elif edge['curvature']['type'] == 'cubic':
|
||||
cps = []
|
||||
for p in edge['curvature']['params']:
|
||||
control_scale = self._flip_y(p)
|
||||
control_point = utils.rel_to_abs_2d(start, end, control_scale)
|
||||
cps.append(control_point)
|
||||
|
||||
return svgpath.CubicBezier(*utils.list_to_c([start, *cps, end]))
|
||||
|
||||
else:
|
||||
raise NotImplementedError(f'{self.__class__.__name__}::Unknown curvature type {edge["curvature"]["type"]}')
|
||||
|
||||
else:
|
||||
return svgpath.Line(*utils.list_to_c([start, end]))
|
||||
|
||||
@staticmethod
|
||||
def _point_in_3D(local_coord, rotation, translation):
|
||||
"""Apply 3D transformation to the point given in 2D local coordinated, e.g. on the panel
|
||||
* rotation is expected to be given in 'xyz' Euler anges (as in Autodesk Maya) or as 3x3 matrix"""
|
||||
|
||||
# 2D->3D local
|
||||
local_coord = np.append(local_coord, 0)
|
||||
|
||||
# Rotate
|
||||
rotation = np.array(rotation)
|
||||
if rotation.size == 3: # transform Euler angles to matrix
|
||||
rotation = rotation_tools.euler_xyz_to_R(rotation)
|
||||
# otherwise we already have the matrix
|
||||
elif rotation.size != 9:
|
||||
raise ValueError('BasicPattern::ERROR::You need to provide Euler angles or Rotation matrix for _point_in_3D(..)')
|
||||
rotated_point = rotation.dot(local_coord)
|
||||
|
||||
# translate
|
||||
return rotated_point + translation
|
||||
|
||||
def _panel_universal_transtation(self, panel_name):
|
||||
"""Return a universal 3D translation of the panel (e.g. to be used in judging the panel order).
|
||||
Universal translation it defined as world 3D location of mid-point of the top (in 3D) of the panel (2D) bounding box.
|
||||
* Assumptions:
|
||||
* In most cases, top-mid-point of a panel corresponds to body landmarks (e.g. neck, middle of an arm, waist)
|
||||
and thus is mostly stable across garment designs.
|
||||
* 3D location of a panel is placing this panel around the body in T-pose
|
||||
* Function result is independent from the current choice of the local coordinate system of the panel
|
||||
"""
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
vertices = np.array(panel['vertices'])
|
||||
|
||||
# out of 2D bounding box sides' midpoints choose the one that is highest in 3D
|
||||
top_right = vertices.max(axis=0)
|
||||
low_left = vertices.min(axis=0)
|
||||
mid_x = (top_right[0] + low_left[0]) / 2
|
||||
mid_y = (top_right[1] + low_left[1]) / 2
|
||||
mid_points_2D = [
|
||||
[mid_x, top_right[1]],
|
||||
[mid_x, low_left[1]],
|
||||
[top_right[0], mid_y],
|
||||
[low_left[0], mid_y]
|
||||
]
|
||||
rot_matrix = rotation_tools.euler_xyz_to_R(panel['rotation']) # calculate once for all points
|
||||
mid_points_3D = np.vstack(tuple(
|
||||
[self._point_in_3D(coords, rot_matrix, panel['translation']) for coords in mid_points_2D]
|
||||
))
|
||||
top_mid_point = mid_points_3D[:, 1].argmax()
|
||||
|
||||
return mid_points_3D[top_mid_point], np.array(mid_points_2D[top_mid_point])
|
||||
|
||||
# --------- Pattern operations (changes inner dicts) ----------
|
||||
def _normalize_template(self):
|
||||
"""
|
||||
Updated template definition for convenient processing:
|
||||
* Converts curvature coordinates to realitive ones (in edge frame) -- for easy length scaling
|
||||
* snaps each panel center to (0, 0) if requested in props
|
||||
* scales everything to cm
|
||||
"""
|
||||
if self.properties['curvature_coords'] == 'absolute':
|
||||
for panel in self.pattern['panels']:
|
||||
# convert curvature
|
||||
vertices = self.pattern['panels'][panel]['vertices']
|
||||
edges = self.pattern['panels'][panel]['edges']
|
||||
for edge in edges:
|
||||
if 'curvature' in edge:
|
||||
edge['curvature'] = utils.abs_to_rel_2d(
|
||||
vertices[edge['endpoints'][0]],
|
||||
vertices[edge['endpoints'][1]],
|
||||
edge['curvature']
|
||||
)
|
||||
# now we have new property
|
||||
self.properties['curvature_coords'] = 'relative'
|
||||
|
||||
if 'units_in_meter' in self.properties:
|
||||
if self.properties['units_in_meter'] != 100:
|
||||
for panel in self.pattern['panels']:
|
||||
self._normalize_panel_scaling(panel, self.properties['units_in_meter'])
|
||||
# now we have cm
|
||||
self.properties['original_units_in_meter'] = self.properties['units_in_meter']
|
||||
self.properties['units_in_meter'] = 100
|
||||
print('WARNING: pattern units converted to cm')
|
||||
else:
|
||||
print('WARNING: units not specified in the pattern. Scaling normalization was not applied')
|
||||
|
||||
# after curvature is converted!!
|
||||
# Only if requested
|
||||
if ('normalize_panel_translation' in self.properties
|
||||
and self.properties['normalize_panel_translation']):
|
||||
print('Normalizing translation!')
|
||||
self.properties['normalize_panel_translation'] = False # one-time use property. Preverts rotation issues on future reads
|
||||
for panel in self.pattern['panels']:
|
||||
# put origin in the middle of the panel--
|
||||
offset = self._normalize_panel_translation(panel)
|
||||
# udpate translation vector
|
||||
original = self.pattern['panels'][panel]['translation']
|
||||
self.pattern['panels'][panel]['translation'] = [
|
||||
original[0] + offset[0],
|
||||
original[1] + offset[1],
|
||||
original[2],
|
||||
]
|
||||
|
||||
# Recalculate origins and traversal order of panel edge loops if not normalized already
|
||||
if ('normalized_edge_loops' not in self.properties
|
||||
or not self.properties['normalized_edge_loops']):
|
||||
print('{}::WARNING::normalizing the order and origin choice for edge loops in panels'.format(self.__class__.__name__))
|
||||
self.properties['normalized_edge_loops'] = True
|
||||
for panel in self.pattern['panels']:
|
||||
self._normalize_edge_loop(panel)
|
||||
|
||||
# Recalculate panel order if not given already
|
||||
self.panel_order()
|
||||
|
||||
def _normalize_panel_translation(self, panel_name):
|
||||
""" Convert panel vertices to local coordinates:
|
||||
Shifts all panel vertices s.t. origin is at the center of the panel
|
||||
"""
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
vertices = np.asarray(panel['vertices'])
|
||||
offset = np.mean(vertices, axis=0)
|
||||
vertices = vertices - offset
|
||||
|
||||
panel['vertices'] = vertices.tolist()
|
||||
|
||||
return offset
|
||||
|
||||
def _normalize_panel_scaling(self, panel_name, units_in_meter):
|
||||
"""Convert all panel info to cm. I assume that curvature is alredy converted to relative coords -- scaling does not need update"""
|
||||
scaling = 100 / units_in_meter
|
||||
# vertices
|
||||
vertices = np.array(self.pattern['panels'][panel_name]['vertices'])
|
||||
vertices = scaling * vertices
|
||||
self.pattern['panels'][panel_name]['vertices'] = vertices.tolist()
|
||||
|
||||
# translation
|
||||
translation = self.pattern['panels'][panel_name]['translation']
|
||||
self.pattern['panels'][panel_name]['translation'] = [scaling * coord for coord in translation]
|
||||
|
||||
def _normalize_edge_loop(self, panel_name):
|
||||
"""
|
||||
* Re-order edges s.t. the edge loop starts from low-left vertex
|
||||
* Make the edge loop follow counter-clockwise direction (uniform traversal)
|
||||
"""
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
vertices = np.array(panel['vertices'])
|
||||
|
||||
# Loop Origin
|
||||
loop_origin_id = self._vert_at_left_corner(vertices)
|
||||
print('{}:{}: Origin: {} -> {}'.format(
|
||||
self.name, panel_name, panel['edges'][0]['endpoints'][0], loop_origin_id))
|
||||
|
||||
rotated_edges, rotated_edge_ids = self._rotate_edges(
|
||||
panel['edges'], list(range(len(panel['edges']))), loop_origin_id)
|
||||
panel['edges'] = rotated_edges
|
||||
|
||||
# Panel flip for uniform edge loop order (and normal direction)
|
||||
first_edge = self._edge_as_vector(vertices, rotated_edges[0])[:2]
|
||||
last_edge = self._edge_as_vector(vertices, rotated_edges[-1])[:2]
|
||||
flipped = False
|
||||
# due to the choice of origin (at the corner), first & last edge cross-product will reliably show panel normal direction
|
||||
if np.cross(first_edge, last_edge) > 0: # should be negative -- counterclockwise
|
||||
print('{}::{}::panel <{}> flipped'.format(
|
||||
self.__class__.__name__, self.name, panel_name
|
||||
))
|
||||
flipped = True
|
||||
|
||||
# Vertices
|
||||
vertices[:, 0] = - vertices[:, 0] # flip by X coordinate -- we'll rotate around Y
|
||||
panel['vertices'] = vertices.tolist()
|
||||
|
||||
# Edges
|
||||
# new loop origin after update
|
||||
loop_origin_id = self._vert_at_left_corner(vertices)
|
||||
print('{}:{}: Origin: {} -> {}'.format(
|
||||
self.name, panel_name, panel['edges'][0]['endpoints'][0], loop_origin_id))
|
||||
|
||||
rotated_edges, rotated_edge_ids = self._rotate_edges(rotated_edges, rotated_edge_ids, loop_origin_id)
|
||||
panel['edges'] = rotated_edges
|
||||
# update the curvatures in edges as they changed left\right symmetry in 3D
|
||||
for edge_id in range(len(rotated_edges)):
|
||||
if 'curvature' in panel['edges'][edge_id]:
|
||||
curvature = panel['edges'][edge_id]['curvature']
|
||||
# YES!! Only one of the curvature coordinates need update at this point
|
||||
panel['edges'][edge_id]['curvature'][1] = -curvature[1]
|
||||
|
||||
# Panel translation and rotation -- local coord frame changed!
|
||||
panel['translation'][0] -= 2 * panel['translation'][0]
|
||||
|
||||
panel_R = rotation_tools.euler_xyz_to_R(panel['rotation'])
|
||||
flip_R = np.eye(3)
|
||||
flip_R[0, 0] = flip_R[2, 2] = -1 # by 180 around Y
|
||||
|
||||
panel['rotation'] = rotation_tools.R_to_euler(panel_R * flip_R)
|
||||
|
||||
# Stitches -- update the edge references according to the new ids
|
||||
if 'stitches' in self.pattern.keys():
|
||||
for stitch_id in range(len(self.pattern['stitches'])):
|
||||
for side_id in [0, 1]:
|
||||
if self.pattern['stitches'][stitch_id][side_id]['panel'] == panel_name:
|
||||
old_edge_id = self.pattern['stitches'][stitch_id][side_id]['edge']
|
||||
self.pattern['stitches'][stitch_id][side_id]['edge'] = rotated_edge_ids[old_edge_id]
|
||||
|
||||
return rotated_edge_ids, flipped
|
||||
|
||||
# -- sub-utils --
|
||||
def _edge_length(self, panel, edge):
|
||||
panel = self.pattern['panels'][panel]
|
||||
v_id_start, v_id_end = tuple(panel['edges'][edge]['endpoints'])
|
||||
v_start, v_end = np.array(panel['vertices'][v_id_start]), \
|
||||
np.array(panel['vertices'][v_id_end])
|
||||
|
||||
return np.linalg.norm(v_end - v_start)
|
||||
|
||||
@staticmethod
|
||||
def _vert_at_left_corner(vertices):
|
||||
"""
|
||||
Find, which vertex is in the left corner
|
||||
* Determenistic process
|
||||
"""
|
||||
left_corner = np.min(vertices, axis=0)
|
||||
vertices = vertices - left_corner
|
||||
|
||||
# choose the one closest to zero (=low-left corner) as new origin
|
||||
verts_norms = np.linalg.norm(vertices, axis=1) # numpy 1.9+
|
||||
origin_id = np.argmin(verts_norms)
|
||||
|
||||
return origin_id
|
||||
|
||||
@staticmethod
|
||||
def _rotate_edges(edges, edge_ids, new_origin_id):
|
||||
"""
|
||||
Rotate provided list of edges s.t. the first edge starts from vertex with id = new_origin_id
|
||||
Map old edge_ids to new ones accordingly
|
||||
* edges expects list of edges structures
|
||||
"""
|
||||
|
||||
first_edge_orig_id = [idx for idx, edge in enumerate(edges) if edge['endpoints'][0] == new_origin_id]
|
||||
|
||||
first_edge_orig_id = first_edge_orig_id[0]
|
||||
rotated_edges = edges[first_edge_orig_id:] + edges[:first_edge_orig_id]
|
||||
|
||||
# map from old ids to new ids
|
||||
rotated_edge_ids = edge_ids[(len(rotated_edges) - first_edge_orig_id):] + edge_ids[:(len(rotated_edges) - first_edge_orig_id)]
|
||||
|
||||
return rotated_edges, rotated_edge_ids
|
||||
|
||||
def _restore(self, backup_copy):
|
||||
"""Restores spec structure from given backup copy
|
||||
Makes a full copy of backup to avoid accidential corruption of backup
|
||||
"""
|
||||
self.spec = copy.deepcopy(backup_copy)
|
||||
self.pattern = self.spec['pattern']
|
||||
self.properties = self.spec['properties'] # mandatory part
|
||||
|
||||
# -------- Checks ------------
|
||||
def is_self_intersecting(self):
|
||||
"""returns True if any of the pattern panels are self-intersecting"""
|
||||
return any(map(self._is_panel_self_intersecting, self.pattern['panels']))
|
||||
|
||||
def _is_panel_self_intersecting(self, panel_name, n_vert_approximation=10):
|
||||
"""Checks whatever a given panel contains intersecting edges
|
||||
"""
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
vertices = np.array(panel['vertices'])
|
||||
|
||||
edge_curves = []
|
||||
for e in panel['edges']:
|
||||
curve = self._edge_as_curve(vertices, e)
|
||||
|
||||
if isinstance(curve, svgpath.Arc):
|
||||
# NOTE: Intersections for Arcs (Circle edge) fails in svgpathtools:
|
||||
# They are not well implemented in svgpathtools, see
|
||||
# https://github.com/mathandy/svgpathtools/issues/121
|
||||
# https://github.com/mathandy/svgpathtools/blob/fcb648b9bb9591d925876d3b51649fa175b40524/svgpathtools/path.py#L1960
|
||||
# Hence using linear approximation for robustness:
|
||||
n = n_vert_approximation + 1
|
||||
tvals = np.linspace(0, 1, n, endpoint=False)[1:]
|
||||
edge_verts = [curve.point(t) for t in tvals]
|
||||
edge_curves += [svgpath.Line(edge_verts[i], edge_verts[i + 1]) for i in range(n-2)]
|
||||
else:
|
||||
edge_curves.append(curve)
|
||||
|
||||
# NOTE: simple pairwise checks of edges
|
||||
for i1 in range(0, len(edge_curves)):
|
||||
for i2 in range(i1 + 1, len(edge_curves)):
|
||||
intersect_t = edge_curves[i1].intersect(edge_curves[i2])
|
||||
|
||||
# Check exceptions -- intersection at the vertex
|
||||
for i in range(len(intersect_t)):
|
||||
t1, t2 = intersect_t[i]
|
||||
if t2 < t1:
|
||||
t1, t2 = t2, t1
|
||||
if utils.close_enough(t1, 0) and utils.close_enough(t2, 1):
|
||||
intersect_t[i] = None
|
||||
intersect_t = [el for el in intersect_t if el is not None]
|
||||
|
||||
if intersect_t: # Any other case of intersections
|
||||
return True
|
||||
return False
|
||||
|
||||
# NOTE: Deprecated. Preserved for backward compatibility
|
||||
# with the first dataset of 3D garments and sewing patterns
|
||||
class ParametrizedPattern(BasicPattern):
|
||||
"""
|
||||
Extention to BasicPattern that can work with parametrized patterns
|
||||
Update pattern with new parameter values & randomize those parameters
|
||||
"""
|
||||
def __init__(self, pattern_file=None):
|
||||
super().__init__(pattern_file)
|
||||
self.parameters = self.spec['parameters']
|
||||
|
||||
self.parameter_defaults = {
|
||||
'length': 1,
|
||||
'additive_length': 0,
|
||||
'curve': 1
|
||||
}
|
||||
self.constraint_types = [
|
||||
'length_equality'
|
||||
]
|
||||
|
||||
def param_values_list(self):
|
||||
"""Returns current values of all parameters as a list in the pattern defined parameter order"""
|
||||
value_list = []
|
||||
for parameter in self.spec['parameter_order']:
|
||||
value = self.parameters[parameter]['value']
|
||||
if isinstance(value, list):
|
||||
value_list += value
|
||||
else:
|
||||
value_list.append(value)
|
||||
return value_list
|
||||
|
||||
def apply_param_list(self, values):
|
||||
"""Apply given parameters supplied as a list of param_values_list() form"""
|
||||
|
||||
self._restore_template(params_to_default=False)
|
||||
|
||||
# set new values
|
||||
value_count = 0
|
||||
for parameter in self.spec['parameter_order']:
|
||||
last_value = self.parameters[parameter]['value']
|
||||
if isinstance(last_value, list):
|
||||
self.parameters[parameter]['value'] = [values[value_count + i] for i in range(len(last_value))]
|
||||
value_count += len(last_value)
|
||||
else:
|
||||
self.parameters[parameter]['value'] = values[value_count]
|
||||
value_count += 1
|
||||
|
||||
self._update_pattern_by_param_values()
|
||||
|
||||
def reloadJSON(self):
|
||||
"""(Re)loads pattern info from spec file.
|
||||
Useful when spec is updated from outside"""
|
||||
super().reloadJSON()
|
||||
|
||||
self.parameters = self.spec['parameters']
|
||||
self._normalize_param_scaling()
|
||||
|
||||
def _restore(self, backup_copy):
|
||||
"""Restores spec structure from given backup copy
|
||||
Makes a full copy of backup to avoid accidential corruption of backup
|
||||
"""
|
||||
super()._restore(backup_copy)
|
||||
self.parameters = self.spec['parameters']
|
||||
|
||||
# ---------- Parameters operations --------
|
||||
|
||||
def _normalize_param_scaling(self):
|
||||
"""Convert additive parameters to cm units"""
|
||||
|
||||
if 'original_units_in_meter' in self.properties: # pattern was scaled
|
||||
scaling = 100 / self.properties['original_units_in_meter']
|
||||
for parameter in self.parameters:
|
||||
if self.parameters[parameter]['type'] == 'additive_length':
|
||||
self.parameters[parameter]['value'] = scaling * self.parameters[parameter]['value']
|
||||
self.parameters[parameter]['range'] = [
|
||||
scaling * elem for elem in self.parameters[parameter]['range']
|
||||
]
|
||||
|
||||
# now we have cm everywhere -- no need to keep units info
|
||||
self.properties.pop('original_units_in_meter', None)
|
||||
|
||||
print('WARNING: Parameter units were converted to cm')
|
||||
|
||||
def _normalize_edge_loop(self, panel_name):
|
||||
"""Update the edge loops and edge ids references in parameters & constraints after change"""
|
||||
rotated_edge_ids, flipped = super()._normalize_edge_loop(panel_name)
|
||||
|
||||
# Parameters
|
||||
for parameter_name in self.spec['parameters']:
|
||||
self._influence_after_edge_loop_update(
|
||||
self.spec['parameters'][parameter_name]['influence'],
|
||||
panel_name, rotated_edge_ids)
|
||||
|
||||
# Constraints
|
||||
if 'constraints' in self.spec:
|
||||
for constraint_name in self.spec['constraints']:
|
||||
self._influence_after_edge_loop_update(
|
||||
self.spec['constraints'][constraint_name]['influence'],
|
||||
panel_name, rotated_edge_ids)
|
||||
|
||||
def _influence_after_edge_loop_update(self, infl_list, panel_name, new_edge_ids):
|
||||
"""
|
||||
Update the list of parameter\constraint influence with the new edge ids of given panel.
|
||||
|
||||
flipped -- indicates if in the new edges start & end vertices have been swapped
|
||||
"""
|
||||
for infl_id in range(len(infl_list)):
|
||||
if infl_list[infl_id]['panel'] == panel_name:
|
||||
# update
|
||||
edge_list = infl_list[infl_id]['edge_list']
|
||||
for edge_list_id in range(len(edge_list)):
|
||||
if isinstance(edge_list[edge_list_id], int): # Simple edge id lists in curvature params
|
||||
old_id = edge_list[edge_list_id]
|
||||
edge_list[edge_list_id] = new_edge_ids[old_id]
|
||||
elif isinstance(edge_list[edge_list_id]['id'], list): # Meta-edge in length parameters & constraints
|
||||
for i in range(len(edge_list[edge_list_id]['id'])):
|
||||
old_id = edge_list[edge_list_id]['id'][i]
|
||||
edge_list[edge_list_id]['id'][i] = new_edge_ids[old_id]
|
||||
else: # edge description in length parameters & constraints
|
||||
old_id = edge_list[edge_list_id]['id']
|
||||
edge_list[edge_list_id]['id'] = new_edge_ids[old_id]
|
||||
|
||||
def _update_pattern_by_param_values(self):
|
||||
"""
|
||||
Recalculates vertex positions and edge curves according to current
|
||||
parameter values
|
||||
(!) Assumes that the current pattern is a template:
|
||||
with all the parameters equal to defaults!
|
||||
"""
|
||||
for parameter in self.spec['parameter_order']:
|
||||
value = self.parameters[parameter]['value']
|
||||
param_type = self.parameters[parameter]['type']
|
||||
if param_type not in self.parameter_defaults:
|
||||
raise ValueError("Incorrect parameter type. Alowed are "
|
||||
+ self.parameter_defaults.keys())
|
||||
|
||||
for panel_influence in self.parameters[parameter]['influence']:
|
||||
for edge in panel_influence['edge_list']:
|
||||
if param_type == 'length':
|
||||
self._extend_edge(panel_influence['panel'], edge, value)
|
||||
elif param_type == 'additive_length':
|
||||
self._extend_edge(panel_influence['panel'], edge, value, multiplicative=False)
|
||||
elif param_type == 'curve':
|
||||
self._curve_edge(panel_influence['panel'], edge, value)
|
||||
# finally, ensure secified constraints are held
|
||||
self._apply_constraints()
|
||||
|
||||
def _restore_template(self, params_to_default=True):
|
||||
"""Restore pattern to it's state with all parameters having default values
|
||||
Recalculate vertex positions, edge curvatures & snap values to 1
|
||||
"""
|
||||
# Follow process backwards
|
||||
self._invert_constraints()
|
||||
|
||||
for parameter in reversed(self.spec['parameter_order']):
|
||||
value = self.parameters[parameter]['value']
|
||||
param_type = self.parameters[parameter]['type']
|
||||
if param_type not in self.parameter_defaults:
|
||||
raise ValueError("Incorrect parameter type. Alowed are "
|
||||
+ self.parameter_defaults.keys())
|
||||
|
||||
for panel_influence in reversed(self.parameters[parameter]['influence']):
|
||||
for edge in reversed(panel_influence['edge_list']):
|
||||
if param_type == 'length':
|
||||
self._extend_edge(panel_influence['panel'], edge, self._invert_value(value))
|
||||
elif param_type == 'additive_length':
|
||||
self._extend_edge(panel_influence['panel'], edge,
|
||||
self._invert_value(value, multiplicative=False),
|
||||
multiplicative=False)
|
||||
elif param_type == 'curve':
|
||||
self._curve_edge(panel_influence['panel'], edge, self._invert_value(value))
|
||||
|
||||
# restore defaults
|
||||
if params_to_default:
|
||||
if isinstance(value, list):
|
||||
self.parameters[parameter]['value'] = [self.parameter_defaults[param_type] for _ in value]
|
||||
else:
|
||||
self.parameters[parameter]['value'] = self.parameter_defaults[param_type]
|
||||
|
||||
def _extend_edge(self, panel_name, edge_influence, value, multiplicative=True):
|
||||
"""
|
||||
Shrinks/elongates a given edge or edge collection of a given panel. Applies equally
|
||||
to straight and curvy edges tnks to relative coordinates of curve controls
|
||||
Expects
|
||||
* each influenced edge to supply the elongatoin direction
|
||||
* scalar scaling_factor
|
||||
'multiplicative' parameter controls the type of extention:
|
||||
* if True, value is treated as a scaling factor of the edge or edge projection -- default
|
||||
* if False, value is added to the edge or edge projection
|
||||
"""
|
||||
if isinstance(value, list):
|
||||
raise ValueError("Multiple scaling factors are not supported")
|
||||
|
||||
verts_ids, verts_coords, target_line, _ = self._meta_edge(panel_name, edge_influence)
|
||||
|
||||
# calc extention pivot
|
||||
if edge_influence['direction'] == 'end':
|
||||
fixed = verts_coords[0] # start is fixed
|
||||
elif edge_influence['direction'] == 'start':
|
||||
fixed = verts_coords[-1] # end is fixed
|
||||
elif edge_influence['direction'] == 'both':
|
||||
fixed = (verts_coords[0] + verts_coords[-1]) / 2
|
||||
else:
|
||||
raise RuntimeError('Unknown edge extention direction {}'.format(edge_influence['direction']))
|
||||
|
||||
# move verts
|
||||
# * along target line that sits on fixed point (correct sign & distance along the line)
|
||||
verts_projection = np.empty(verts_coords.shape)
|
||||
for i in range(verts_coords.shape[0]):
|
||||
verts_projection[i] = (verts_coords[i] - fixed).dot(target_line) * target_line
|
||||
|
||||
if multiplicative:
|
||||
# * to match the scaled projection (correct point of application -- initial vertex position)
|
||||
new_verts = verts_coords - (1 - value) * verts_projection
|
||||
else:
|
||||
# * to match the added projection:
|
||||
# still need projection to make sure the extention derection is corect relative to fixed point
|
||||
|
||||
# normalize first
|
||||
for i in range(verts_coords.shape[0]):
|
||||
norm = np.linalg.norm(verts_projection[i])
|
||||
if not np.isclose(norm, 0):
|
||||
verts_projection[i] /= norm
|
||||
|
||||
# zero projections were not normalized -- they will zero-out the effect
|
||||
new_verts = verts_coords + value * verts_projection
|
||||
|
||||
# update in the initial structure
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
for ni, idx in enumerate(verts_ids):
|
||||
panel['vertices'][idx] = new_verts[ni].tolist()
|
||||
|
||||
def _curve_edge(self, panel_name, edge, scaling_factor):
|
||||
"""
|
||||
Updated the curvature of an edge accoding to scaling_factor.
|
||||
Can only be applied to edges with curvature information
|
||||
scaling_factor can be
|
||||
* scalar -- only the Y of control point is changed
|
||||
* 2-value list -- both coordinated of control are updated
|
||||
"""
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
if 'curvature' not in panel['edges'][edge]:
|
||||
raise ValueError('Applying curvature scaling to non-curvy edge '
|
||||
+ str(edge) + ' of ' + panel_name)
|
||||
control = panel['edges'][edge]['curvature']
|
||||
|
||||
if isinstance(scaling_factor, list):
|
||||
control = [
|
||||
control[0] * scaling_factor[0],
|
||||
control[1] * scaling_factor[1]
|
||||
]
|
||||
else:
|
||||
control[1] *= scaling_factor
|
||||
|
||||
panel['edges'][edge]['curvature'] = control
|
||||
|
||||
def _invert_value(self, value, multiplicative=True):
|
||||
"""If value is a list, return a list with each value inverted.
|
||||
'multiplicative' parameter controls the type of inversion:
|
||||
* if True, returns multiplicative inverse (1/value) == default
|
||||
* if False, returns additive inverse (-value)
|
||||
"""
|
||||
if multiplicative:
|
||||
if isinstance(value, list):
|
||||
if any(np.isclose(value, 0)):
|
||||
raise ZeroDivisionError('Zero value encountered while restoring multiplicative parameter.')
|
||||
return map(lambda x: 1 / x, value)
|
||||
else:
|
||||
if np.isclose(value, 0):
|
||||
raise ZeroDivisionError('Zero value encountered while restoring multiplicative parameter.')
|
||||
return 1 / value
|
||||
else:
|
||||
if isinstance(value, list):
|
||||
return map(lambda x: -x, value)
|
||||
else:
|
||||
return -value
|
||||
|
||||
def _apply_constraints(self):
|
||||
"""Change the pattern to adhere to constraints if given in pattern spec
|
||||
Assumes no zero-length edges exist"""
|
||||
if 'constraints' not in self.spec:
|
||||
return
|
||||
|
||||
for constraint_n in self.spec['constraints']: # order preserved as it's a list
|
||||
constraint = self.spec['constraints'][constraint_n]
|
||||
constraint_type = constraint['type']
|
||||
if constraint_type not in self.constraint_types:
|
||||
raise ValueError("Incorrect constraint type. Alowed are "
|
||||
+ self.constraint_types)
|
||||
|
||||
if constraint_type == 'length_equality':
|
||||
# get all length of the affected (meta) edges
|
||||
target_len = []
|
||||
for panel_influence in constraint['influence']:
|
||||
for edge in panel_influence['edge_list']:
|
||||
# NOTE: constraints along a custom vector are not well tested
|
||||
_, _, _, length = self._meta_edge(panel_influence['panel'], edge)
|
||||
edge['length'] = length
|
||||
target_len.append(length)
|
||||
if len(target_len) == 0:
|
||||
return
|
||||
# target as mean of provided edges
|
||||
target_len = sum(target_len) / len(target_len)
|
||||
|
||||
# calculate scaling factor for every edge to match max length
|
||||
# & update edges with it
|
||||
for panel_influence in constraint['influence']:
|
||||
for edge in panel_influence['edge_list']:
|
||||
scaling = target_len / edge['length']
|
||||
if not np.isclose(scaling, 1):
|
||||
edge['value'] = scaling
|
||||
self._extend_edge(panel_influence['panel'], edge, edge['value'])
|
||||
|
||||
def _invert_constraints(self):
|
||||
"""Restore pattern to the state before constraint was applied"""
|
||||
if 'constraints' not in self.spec:
|
||||
return
|
||||
|
||||
# follow the process backwards
|
||||
for constraint_n in reversed(self.spec['constraint_order']): # order preserved as it's a list
|
||||
constraint = self.spec['constraints'][constraint_n]
|
||||
constraint_type = constraint['type']
|
||||
if constraint_type not in self.constraint_types:
|
||||
raise ValueError("Incorrect constraint type. Alowed are "
|
||||
+ self.constraint_types)
|
||||
|
||||
if constraint_type == 'length_equality':
|
||||
# update edges with invertes scaling factor
|
||||
for panel_influence in constraint['influence']:
|
||||
for edge in panel_influence['edge_list']:
|
||||
scaling = self._invert_value(edge['value'])
|
||||
self._extend_edge(panel_influence['panel'], edge, scaling)
|
||||
edge['value'] = 1
|
||||
|
||||
def _meta_edge(self, panel_name, edge_influence):
|
||||
"""Returns info for the given edge or meta-edge in inified form"""
|
||||
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
edge_ids = edge_influence['id']
|
||||
if isinstance(edge_ids, list):
|
||||
# meta-edge
|
||||
# get all vertices in order
|
||||
verts_ids = [panel['edges'][edge_ids[0]]['endpoints'][0]] # start
|
||||
for edge_id in edge_ids:
|
||||
verts_ids.append(panel['edges'][edge_id]['endpoints'][1]) # end vertices
|
||||
else:
|
||||
# single edge
|
||||
verts_ids = panel['edges'][edge_ids]['endpoints']
|
||||
|
||||
verts_coords = []
|
||||
for idx in verts_ids:
|
||||
verts_coords.append(panel['vertices'][idx])
|
||||
verts_coords = np.array(verts_coords)
|
||||
|
||||
# extention line
|
||||
if 'along' in edge_influence:
|
||||
target_line = edge_influence['along']
|
||||
else:
|
||||
target_line = verts_coords[-1] - verts_coords[0]
|
||||
target_line = np.array(target_line, dtype=float) # https://stackoverflow.com/questions/50625975/typeerror-ufunc-true-divide-output-typecode-d-could-not-be-coerced-to-pro
|
||||
|
||||
if np.isclose(np.linalg.norm(target_line), 0):
|
||||
raise ZeroDivisionError('target line is zero ' + str(target_line))
|
||||
else:
|
||||
target_line /= np.linalg.norm(target_line)
|
||||
|
||||
return verts_ids, verts_coords, target_line, target_line.dot(verts_coords[-1] - verts_coords[0])
|
||||
|
||||
def _invalidate_all_values(self):
|
||||
"""Sets all values of params & constraints to None if not set already
|
||||
Useful in direct updates of pattern panels"""
|
||||
|
||||
updated_once = False
|
||||
for parameter in self.parameters:
|
||||
if self.parameters[parameter]['value'] is not None:
|
||||
self.parameters[parameter]['value'] = None
|
||||
updated_once = True
|
||||
|
||||
if 'constraints' in self.spec:
|
||||
for constraint in self.spec['constraints']:
|
||||
for edge_collection in self.spec['constraints'][constraint]['influence']:
|
||||
for edge in edge_collection['edge_list']:
|
||||
if edge['value'] is not None:
|
||||
edge['value'] = None
|
||||
updated_once = True
|
||||
if updated_once:
|
||||
# only display worning if some new invalidation happened
|
||||
print('ParametrizedPattern::WARNING::Parameter (& constraints) values are invalidated')
|
||||
|
||||
# ---------- Randomization -------------
|
||||
def _randomize_pattern(self):
|
||||
"""Robustly randomize current pattern"""
|
||||
# restore template state before making any changes to parameters
|
||||
self._restore_template(params_to_default=False)
|
||||
|
||||
spec_backup = copy.deepcopy(self.spec)
|
||||
self._randomize_parameters()
|
||||
self._update_pattern_by_param_values()
|
||||
for _ in range(100): # upper bound on trials to avoid infinite loop
|
||||
if not self.is_self_intersecting():
|
||||
break
|
||||
|
||||
print('WARNING::Randomized pattern is self-intersecting. Re-try..')
|
||||
self._restore(spec_backup)
|
||||
# Try again
|
||||
self._randomize_parameters()
|
||||
self._update_pattern_by_param_values()
|
||||
|
||||
def _new_value(self, param_range):
|
||||
"""Random value within range given as an iteratable"""
|
||||
value = random.uniform(param_range[0], param_range[1])
|
||||
# prevent non-reversible zero values
|
||||
if abs(value) < 1e-2:
|
||||
value = 1e-2 * (-1 if value < 0 else 1)
|
||||
return value
|
||||
|
||||
def _randomize_parameters(self):
|
||||
"""
|
||||
Sets new random values for the pattern parameters
|
||||
Parameter type agnostic
|
||||
"""
|
||||
for parameter in self.parameters:
|
||||
param_ranges = self.parameters[parameter]['range']
|
||||
|
||||
# check if parameter has multiple values (=> multiple ranges) like for curves
|
||||
if isinstance(self.parameters[parameter]['value'], list):
|
||||
values = []
|
||||
for param_range in param_ranges:
|
||||
values.append(self._new_value(param_range))
|
||||
self.parameters[parameter]['value'] = values
|
||||
else: # simple 1-value parameter
|
||||
self.parameters[parameter]['value'] = self._new_value(param_ranges)
|
||||
|
||||
|
||||
62
pygarment/pattern/rotation.py
Normal file
62
pygarment/pattern/rotation.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Simple Rotation Conversion routines (Maya-Python2.7-Compatible!!)
|
||||
"""
|
||||
import numpy as np
|
||||
import math as m
|
||||
import sys
|
||||
|
||||
# TODO: Maya python 2.7 is long gone.
|
||||
# Can be substituted with scipy rotation transformation routines for Maya2022+
|
||||
|
||||
|
||||
# Thanks to https://www.meccanismocomplesso.org/en/3d-rotations-and-euler-angles-in-python/ for the code
|
||||
def _Rx(theta):
|
||||
return np.matrix([
|
||||
[1, 0 , 0 ],
|
||||
[0, m.cos(theta), -m.sin(theta)],
|
||||
[0, m.sin(theta), m.cos(theta)]])
|
||||
|
||||
|
||||
def _Ry(theta):
|
||||
return np.matrix([
|
||||
[m.cos(theta), 0, m.sin(theta)],
|
||||
[0 , 1, 0 ],
|
||||
[-m.sin(theta), 0, m.cos(theta)]])
|
||||
|
||||
|
||||
def _Rz(theta):
|
||||
return np.matrix([
|
||||
[m.cos(theta), -m.sin(theta), 0],
|
||||
[m.sin(theta), m.cos(theta) , 0],
|
||||
[0 , 0 , 1]])
|
||||
|
||||
|
||||
def euler_xyz_to_R(euler):
|
||||
"""Convert to Rotation matrix.
|
||||
Expects input in degrees.
|
||||
Only support Maya convension of intrinsic xyz Euler Angles
|
||||
"""
|
||||
return _Rz(np.deg2rad(euler[2])) * _Ry(np.deg2rad(euler[1])) * _Rx(np.deg2rad(euler[0]))
|
||||
|
||||
|
||||
def R_to_euler(R):
|
||||
"""
|
||||
Convert Rotation matrix to Euler-angles in degrees (in Maya convension of intrinsic xyz Euler Angles)
|
||||
NOTE:
|
||||
Routine produces one of the possible Euler angles, corresponding to input rotations (the Euler angles are not uniquely defined)
|
||||
"""
|
||||
tol = sys.float_info.epsilon * 10
|
||||
|
||||
if abs(R[0, 0]) < tol and abs(R[1, 0]) < tol:
|
||||
eul1 = 0
|
||||
eul2 = m.atan2(-R[2, 0], R[0, 0])
|
||||
eul3 = m.atan2(-R[1, 2], R[1, 1])
|
||||
else:
|
||||
eul1 = m.atan2(R[1, 0], R[0, 0])
|
||||
sp = m.sin(eul1)
|
||||
cp = m.cos(eul1)
|
||||
eul2 = m.atan2(-R[2, 0], cp * R[0, 0] + sp * R[1, 0])
|
||||
eul3 = m.atan2(sp * R[0, 2] - cp * R[1, 2], cp * R[1, 1] - sp * R[0, 1])
|
||||
|
||||
return [np.rad2deg(eul3), np.rad2deg(eul2), np.rad2deg(eul1)]
|
||||
|
||||
85
pygarment/pattern/utils.py
Normal file
85
pygarment/pattern/utils.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Generic utility functions"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
def list_to_c(num):
|
||||
"""Convert 2D list or list of 2D lists into complex number/list of complex numbers"""
|
||||
if isinstance(num[0], list) or isinstance(num[0], np.ndarray):
|
||||
return [complex(n[0], n[1]) for n in num]
|
||||
else:
|
||||
return complex(num[0], num[1])
|
||||
|
||||
def c_to_np(num):
|
||||
"""Convert complex number to a numpy array of 2 elements"""
|
||||
return np.asarray([num.real, num.imag])
|
||||
|
||||
def vector_angle(v1, v2):
|
||||
"""Find an angle between two 2D vectors"""
|
||||
v1, v2 = np.asarray(v1), np.asarray(v2)
|
||||
cos = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
|
||||
cos = max(min(cos, 1), -1) # NOTE: getting rid of numbers like 1.000002 that appear due to numerical instability
|
||||
angle = np.arccos(cos)
|
||||
# Cross to indicate correct relative orienataion of v2 w.r.t. v1
|
||||
cross = np.cross(v1, v2)
|
||||
|
||||
if abs(cross) > 1e-5:
|
||||
angle *= np.sign(cross)
|
||||
return angle
|
||||
|
||||
def c_to_list(num):
|
||||
"""Convert complex number to a list of 2 elements
|
||||
Allows processing of lists of complex numbers
|
||||
"""
|
||||
|
||||
if isinstance(num, (list, tuple, set, np.ndarray)):
|
||||
return [c_to_list(n) for n in num]
|
||||
else:
|
||||
return [num.real, num.imag]
|
||||
|
||||
def close_enough(f1, f2=0, tol=1e-4):
|
||||
"""Compare two floats correctly """
|
||||
return abs(f1 - f2) < tol
|
||||
|
||||
# Vector local coodinates conversion
|
||||
def rel_to_abs_2d(start, end, rel_point):
|
||||
"""
|
||||
Converts coordinates expressed in a coordinate frame local
|
||||
to the edge [start, end] into edge vertices (global) coordinate frame
|
||||
"""
|
||||
|
||||
start, end = np.array(start), np.array(end) # in case inputs are lists/tuples
|
||||
edge = end - start
|
||||
edge_perp = np.array([-edge[1], edge[0]])
|
||||
|
||||
abs_start = start + rel_point[0] * edge
|
||||
abs_point = abs_start + rel_point[1] * edge_perp
|
||||
|
||||
return abs_point
|
||||
|
||||
def abs_to_rel_2d(start, end, abs_point, as_vector=False):
|
||||
"""
|
||||
Converts coordinates expressed in a global coordinate frame into
|
||||
a frame local to the edge [start, end]
|
||||
"""
|
||||
|
||||
start, end, abs_point = np.array(start), np.array(end), \
|
||||
np.array(abs_point)
|
||||
|
||||
rel_point = [None, None]
|
||||
edge_vec = end - start
|
||||
edge_len = np.linalg.norm(edge_vec)
|
||||
point_vec = abs_point if as_vector else abs_point - start # vector or point
|
||||
|
||||
# X
|
||||
# project control_vec on edge_vec by dot product properties
|
||||
projected_len = edge_vec.dot(point_vec) / edge_len
|
||||
rel_point[0] = projected_len / edge_len
|
||||
# Y
|
||||
projected = edge_vec * rel_point[0]
|
||||
vert_comp = point_vec - projected
|
||||
rel_point[1] = np.linalg.norm(vert_comp) / edge_len
|
||||
|
||||
# Distinguish left&right curvature
|
||||
rel_point[1] *= np.sign(np.cross(edge_vec, point_vec))
|
||||
|
||||
return np.asarray(rel_point)
|
||||
404
pygarment/pattern/wrappers.py
Normal file
404
pygarment/pattern/wrappers.py
Normal file
@@ -0,0 +1,404 @@
|
||||
"""
|
||||
To be used in Python 3.6+ due to dependencies
|
||||
"""
|
||||
from copy import copy
|
||||
import random
|
||||
import string
|
||||
import os
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
# Correct dependencies on Win
|
||||
# https://stackoverflow.com/questions/46265677/get-cairosvg-working-in-windows
|
||||
if 'Windows' in os.environ.get('OS',''):
|
||||
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||
os.environ['path'] += f';{os.path.abspath(dir_path + "/cairo_dlls/")}'
|
||||
|
||||
import cairosvg
|
||||
import svgpathtools as svgpath
|
||||
import svgwrite as sw
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# my
|
||||
from pygarment import data_config
|
||||
from . import core
|
||||
from .utils import *
|
||||
|
||||
|
||||
class VisPattern(core.ParametrizedPattern):
|
||||
"""
|
||||
"Visualizible" pattern wrapper of pattern specification in custom JSON format.
|
||||
Input:
|
||||
* Pattern template in custom JSON format
|
||||
Output representations:
|
||||
* Pattern instance in custom JSON format
|
||||
* In the current state
|
||||
* SVG (stitching info is lost)
|
||||
* PNG for visualization
|
||||
|
||||
Not implemented:
|
||||
* Support for patterns with darts
|
||||
|
||||
NOTE: Visualization assumes the pattern uses cm as units
|
||||
"""
|
||||
|
||||
# ------------ Interface -------------
|
||||
|
||||
def __init__(self, pattern_file=None):
|
||||
super().__init__(pattern_file)
|
||||
|
||||
self.px_per_unit = 3
|
||||
|
||||
def serialize(
|
||||
self, path, to_subfolder=True, tag='',
|
||||
with_3d=True, with_text=True, view_ids=True,
|
||||
with_printable=False,
|
||||
empty_ok=False):
|
||||
|
||||
log_dir = super().serialize(path, to_subfolder, tag=tag, empty_ok=empty_ok)
|
||||
if len(self.panel_order()) == 0: # If we are still here, but pattern is empty, don't generate an image
|
||||
return log_dir
|
||||
|
||||
if tag:
|
||||
tag = '_' + tag
|
||||
svg_file = os.path.join(log_dir, (self.name + tag + '_pattern.svg'))
|
||||
svg_printable_file = os.path.join(log_dir, (self.name + tag + '_print_pattern.svg'))
|
||||
png_file = os.path.join(log_dir, (self.name + tag + '_pattern.png'))
|
||||
pdf_file = os.path.join(log_dir, (self.name + tag + '_print_pattern.pdf'))
|
||||
png_3d_file = os.path.join(log_dir, (self.name + tag + '_3d_pattern.png'))
|
||||
|
||||
# save visualtisation
|
||||
self._save_as_image(svg_file, png_file, with_text, view_ids)
|
||||
if with_3d:
|
||||
self._save_as_image_3D(png_3d_file)
|
||||
if with_printable:
|
||||
self._save_as_pdf(svg_printable_file, pdf_file, with_text, view_ids)
|
||||
|
||||
return log_dir
|
||||
|
||||
# -------- Drawing ---------
|
||||
|
||||
def _verts_to_px_coords(self, vertices, translation_2d):
|
||||
"""Convert given vertices and panel (2D) translation to px coordinate frame & units"""
|
||||
# Flip Y coordinate (in SVG Y looks down)
|
||||
vertices[:, 1] *= -1
|
||||
translation_2d[1] *= -1
|
||||
# Put upper left corner of the bounding box at zero
|
||||
offset = np.min(vertices, axis=0)
|
||||
vertices = vertices - offset
|
||||
translation_2d = translation_2d + offset
|
||||
return vertices, translation_2d
|
||||
|
||||
def _flip_y(self, point):
|
||||
"""
|
||||
To get to image coordinates one might need to flip Y axis
|
||||
"""
|
||||
flipped_point = list(point) # top-level copy
|
||||
flipped_point[1] *= -1
|
||||
return flipped_point
|
||||
|
||||
def _draw_a_panel(self, panel_name, apply_transform=True, fill=True):
|
||||
"""
|
||||
Adds a requested panel to the svg drawing with given offset and scaling
|
||||
Assumes (!!)
|
||||
that edges are correctly oriented to form a closed loop
|
||||
Returns
|
||||
the lower-right vertex coordinate for the convenice of future offsetting.
|
||||
"""
|
||||
attributes = {
|
||||
'fill': 'rgb(115, 113, 125)' if fill else 'rgb(255,255,255)', # fill with white
|
||||
'stroke': 'rgb(51,51,51)',
|
||||
'stroke-width': '0.2'
|
||||
}
|
||||
|
||||
panel = self.pattern['panels'][panel_name]
|
||||
vertices = np.asarray(panel['vertices'])
|
||||
vertices, translation = self._verts_to_px_coords(
|
||||
vertices,
|
||||
np.array(panel['translation'][:2])) # Only XY
|
||||
|
||||
# draw edges
|
||||
segs = [self._edge_as_curve(vertices, edge) for edge in panel['edges']]
|
||||
path = svgpath.Path(*segs)
|
||||
if apply_transform:
|
||||
# Placement and rotation according to the 3D location
|
||||
# But flatterened on 2D
|
||||
# Z-fist rotation to only reflect rotation visible in XY plane
|
||||
# NOTE: Heuristic, might be bug-prone
|
||||
rotation = R.from_euler('XYZ', panel['rotation'], degrees=True) # XYZ
|
||||
|
||||
# Estimate degree of rotation of Y axis
|
||||
# NOTE: Ox sometimes gets flipped because of
|
||||
# Gimbal locks of this Euler angle representation
|
||||
res = rotation.apply([0, 1, 0])
|
||||
flat_rot_angle = np.rad2deg(vector_angle([0, 1], res[:2]))
|
||||
path = path.rotated(
|
||||
degs=-flat_rot_angle,
|
||||
origin=list_to_c(vertices[0])
|
||||
)
|
||||
path = path.translated(list_to_c(translation)) # NOTE: rot/transl order is important!
|
||||
|
||||
return path, attributes, panel['translation'][-1] >= 0
|
||||
|
||||
def _add_panel_annotations(
|
||||
self, drawing, panel_name, path:svgpath.Path, with_text=True, view_ids=True):
|
||||
""" Adds a annotations for requested panel to the svg drawing with given offset and scaling
|
||||
Assumes (!!)
|
||||
that edges are correctly oriented to form a closed loop
|
||||
Returns
|
||||
the lower-right vertex coordinate for the convenice of future offsetting.
|
||||
"""
|
||||
bbox = path.bbox()
|
||||
panel_center = np.array([(bbox[0] + bbox[1]) / 2, (bbox[2] + bbox[3]) / 2])
|
||||
|
||||
if with_text:
|
||||
text_insert = panel_center # + np.array([-len(panel_name) * 12 / 2, 3])
|
||||
drawing.add(drawing.text(panel_name, insert=text_insert,
|
||||
fill='rgb(31,31,31)',
|
||||
font_size='7',
|
||||
text_anchor='middle',
|
||||
dominant_baseline='middle'))
|
||||
|
||||
if view_ids:
|
||||
# name vertices
|
||||
for idx in range(len(path)):
|
||||
seg = path[idx]
|
||||
ver = c_to_np(seg.start)
|
||||
drawing.add(
|
||||
drawing.text(str(idx), insert=ver,
|
||||
fill='rgb(245,96,66)',
|
||||
font_size='7'))
|
||||
# name edges
|
||||
for idx in range(len(path)):
|
||||
seg = path[idx]
|
||||
middle = c_to_np(seg.point(seg.ilength(seg.length() / 2, s_tol=1e-3)))
|
||||
middle[1] -= 3 # slightly above the line
|
||||
# name
|
||||
drawing.add(
|
||||
drawing.text(idx, insert=middle,
|
||||
fill='rgb(44,131,68)',
|
||||
font_size='7',
|
||||
text_anchor='middle'))
|
||||
|
||||
def get_svg(self, svg_filename,
|
||||
with_text=True, view_ids=True,
|
||||
flat=False, fill_panels=True,
|
||||
margin=2) -> sw.Drawing:
|
||||
"""Convert pattern to writable svg representation"""
|
||||
|
||||
if len(self.panel_order()) == 0: # If we are still here, but pattern is empty, don't generate an image
|
||||
raise core.EmptyPatternError()
|
||||
|
||||
# Get svg representation per panel
|
||||
# Order by depth (=> most front panels render in front)
|
||||
# TODOLOW Even smarter way is needed for prettier allignment
|
||||
panel_order = self.panel_order()
|
||||
panel_z = [self.pattern['panels'][pn]['translation'][-1] for pn in panel_order]
|
||||
z_sorted_panels = [p for _, p in sorted(zip(panel_z, panel_order))]
|
||||
|
||||
# Get panel paths
|
||||
paths_front, paths_back = [], []
|
||||
attributes_f, attributes_b = [], []
|
||||
names_f, names_b = [], []
|
||||
shift_x_front, shift_x_back = margin, margin
|
||||
for panel in z_sorted_panels:
|
||||
if panel is not None:
|
||||
path, attr, front = self._draw_a_panel(
|
||||
panel,
|
||||
apply_transform=not flat,
|
||||
fill=fill_panels
|
||||
)
|
||||
if flat:
|
||||
path = path.translated(list_to_c([
|
||||
shift_x_front if front else shift_x_back,
|
||||
0]))
|
||||
bbox = path.bbox()
|
||||
diff = (bbox[1] - bbox[0]) + margin
|
||||
if front:
|
||||
shift_x_front += diff
|
||||
else:
|
||||
shift_x_back += diff
|
||||
if front:
|
||||
paths_front.append(path)
|
||||
attributes_f.append(attr)
|
||||
names_f.append(panel)
|
||||
else:
|
||||
paths_back.append(path)
|
||||
attributes_b.append(attr)
|
||||
names_b.append(panel)
|
||||
|
||||
# Shift back panels if both front and back exist
|
||||
if len(paths_front) > 0 and len(paths_back) > 0:
|
||||
front_max_x = max([path.bbox()[1] for path in paths_front])
|
||||
back_min_x = min([path.bbox()[0] for path in paths_back])
|
||||
shift_x = front_max_x - back_min_x + 10 # A little spacing
|
||||
if flat:
|
||||
front_max_y = max([path.bbox()[3] for path in paths_front])
|
||||
back_min_y = min([path.bbox()[2] for path in paths_back])
|
||||
shift_y = front_max_y - back_min_y + 10 # A little spacing
|
||||
shift_x = 0
|
||||
else:
|
||||
shift_y = 0
|
||||
paths_back = [path.translated(list_to_c([shift_x, shift_y])) for path in paths_back]
|
||||
|
||||
# SVG convert
|
||||
paths = paths_front + paths_back
|
||||
arrdims = np.array([path.bbox() for path in paths])
|
||||
dims = np.max(arrdims[:, 1]) - np.min(arrdims[:, 0]), np.max(arrdims[:, 3]) - np.min(arrdims[:, 2])
|
||||
|
||||
viewbox = (
|
||||
np.min(arrdims[:, 0]) - margin,
|
||||
np.min(arrdims[:, 2]) - margin,
|
||||
dims[0] + 2 * margin,
|
||||
dims[1] + 2 * margin
|
||||
)
|
||||
|
||||
# Pattern info for correct placement
|
||||
self.svg_bbox = [np.min(arrdims[:, 0]), np.max(arrdims[:, 1]), np.min(arrdims[:, 2]), np.max(arrdims[:, 3])]
|
||||
self.svg_bbox_size = [viewbox[2], viewbox[3]]
|
||||
|
||||
# Save
|
||||
attributes = attributes_f + attributes_b
|
||||
|
||||
dwg = svgpath.wsvg(
|
||||
paths,
|
||||
attributes=attributes,
|
||||
margin_size=0,
|
||||
filename=svg_filename,
|
||||
viewbox=viewbox,
|
||||
dimensions=[str(viewbox[2]) + 'cm', str(viewbox[3]) + 'cm'],
|
||||
paths2Drawing=True)
|
||||
|
||||
# text annotations
|
||||
panel_names = names_f + names_b
|
||||
if with_text or view_ids:
|
||||
for i, panel in enumerate(panel_names):
|
||||
if panel is not None:
|
||||
self._add_panel_annotations(
|
||||
dwg, panel, paths[i], with_text, view_ids)
|
||||
|
||||
return dwg
|
||||
|
||||
def _save_as_image(
|
||||
self, svg_filename, png_filename,
|
||||
with_text=True, view_ids=True,
|
||||
margin=2):
|
||||
"""
|
||||
Saves current pattern in svg and png format for visualization
|
||||
|
||||
* with_text: include panel names
|
||||
* view_ids: include ids of vertices and edges in the output image
|
||||
* margin: small amount of free space around the svg drawing (to correctly display the line width)
|
||||
|
||||
"""
|
||||
|
||||
dwg = self.get_svg(
|
||||
svg_filename,
|
||||
with_text=with_text,
|
||||
view_ids=view_ids,
|
||||
flat=False,
|
||||
margin=margin
|
||||
)
|
||||
|
||||
dwg.save(pretty=True)
|
||||
|
||||
# to png
|
||||
# NOTE: Assuming the pattern uses cm
|
||||
# 3 px == 1 cm
|
||||
# DPI = 96 (default) px/inch == 96/2.54 px/cm
|
||||
cairosvg.svg2png(
|
||||
url=svg_filename, write_to=png_filename, dpi=2.54*self.px_per_unit)
|
||||
|
||||
def _save_as_image_3D(self, png_filename):
|
||||
"""Save the patterns with 3D positioning using matplotlib visualization"""
|
||||
|
||||
# NOTE: this routine is mostly needed for debugging
|
||||
|
||||
fig = plt.figure(figsize=(30 / 2.54, 30 / 2.54))
|
||||
ax = fig.add_subplot(projection='3d')
|
||||
|
||||
|
||||
# TODOLOW Support arcs / curves (use linearization)
|
||||
for panel in self.pattern['panels']:
|
||||
p = self.pattern['panels'][panel]
|
||||
rot = p['rotation']
|
||||
tr = p['translation']
|
||||
verts_2d = p['vertices']
|
||||
|
||||
verts_to_plot = copy(verts_2d)
|
||||
verts_to_plot.append(verts_to_plot[0])
|
||||
|
||||
verts3d = np.vstack(tuple([self._point_in_3D(v, rot, tr) for v in verts_to_plot]))
|
||||
x = np.squeeze(np.asarray(verts3d[:, 0]))
|
||||
y = np.squeeze(np.asarray(verts3d[:, 1]))
|
||||
z = np.squeeze(np.asarray(verts3d[:, 2]))
|
||||
|
||||
ax.plot(x, y, z)
|
||||
|
||||
ax.view_init(elev=115, azim=-59, roll=30)
|
||||
ax.set_aspect('equal')
|
||||
fig.savefig(png_filename, dpi=300, transparent=False)
|
||||
|
||||
plt.close(fig) # Cleanup
|
||||
|
||||
def _save_as_pdf(self, svg_filename, pdf_filename,
|
||||
with_text=True, view_ids=True,
|
||||
margin=2):
|
||||
"""Save a pattern as a pdf with non-overlapping panels and no filling
|
||||
Suitable for printing
|
||||
"""
|
||||
dwg = self.get_svg(
|
||||
svg_filename,
|
||||
with_text=with_text,
|
||||
view_ids=view_ids,
|
||||
flat=True,
|
||||
fill_panels=False,
|
||||
margin=margin
|
||||
)
|
||||
dwg.save(pretty=True)
|
||||
|
||||
# to pdf
|
||||
# NOTE: Assuming the pattern uses cm
|
||||
# 3 px == 1 cm
|
||||
# DPI = 96 (default) px/inch == 96/2.54 px/cm
|
||||
cairosvg.svg2pdf(
|
||||
url=svg_filename, write_to=pdf_filename, dpi=2.54*self.px_per_unit)
|
||||
|
||||
class RandomPattern(VisPattern):
|
||||
"""
|
||||
Parameter randomization of a pattern template in custom JSON format.
|
||||
Input:
|
||||
* Pattern template in custom JSON format
|
||||
Output representations:
|
||||
* Pattern instance in custom JSON format
|
||||
(with updated parameter values and vertex positions)
|
||||
* SVG (stitching info is lost)
|
||||
* PNG for visualization
|
||||
|
||||
Implementation limitations:
|
||||
* Parameter randomization is only performed once on loading
|
||||
* Only accepts unchanged template files (all parameter values = 1)
|
||||
otherwise, parameter values will go out of control and outside of the original range
|
||||
(with no way to recognise it)
|
||||
"""
|
||||
|
||||
# ------------ Interface -------------
|
||||
def __init__(self, template_file):
|
||||
"""Note that this class requires some input file:
|
||||
there is not point of creating this object with empty pattern"""
|
||||
super().__init__(template_file, view_ids=False) # don't show ids for datasets
|
||||
|
||||
# update name for a random pattern
|
||||
self.name = self.name + '_' + self._id_generator()
|
||||
|
||||
# randomization setup
|
||||
self._randomize_pattern()
|
||||
|
||||
# -------- Other Utils ---------
|
||||
def _id_generator(self, size=10,
|
||||
chars=string.ascii_uppercase + string.digits):
|
||||
"""Generated a random string of a given size, see
|
||||
https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits
|
||||
"""
|
||||
return ''.join(random.choices(chars, k=size))
|
||||
Reference in New Issue
Block a user