From a27647fd86939ecb4b089a6157bbb3d30cdcdcaf Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 30 Aug 2017 10:29:50 +0200 Subject: [PATCH 01/67] Move matlab sources into subdir. --- basetestvec.m => matlab/basetestvec.m | 0 boundary_point.m => matlab/boundary_point.m | 0 .../bounding_boxes_intersection.m | 0 build_LS_matrix.m => matlab/build_LS_matrix.m | 0 build_reg_matrix.m => matlab/build_reg_matrix.m | 0 compute_control_points.m => matlab/compute_control_points.m | 0 compute_grid_lines.m => matlab/compute_grid_lines.m | 0 compute_patch_edges.m => matlab/compute_patch_edges.m | 0 find_int.m => matlab/find_int.m | 0 get_errors.m => matlab/get_errors.m | 0 get_knot_vector.m => matlab/get_knot_vector.m | 0 getpoints.m => matlab/getpoints.m | 0 getpoints2.m => matlab/getpoints2.m | 0 interpolate_intersections.m => matlab/interpolate_intersections.m | 0 intersections.m => matlab/intersections.m | 0 patch_patch_intersection.m => matlab/patch_patch_intersection.m | 0 plotresultsvec.m => matlab/plotresultsvec.m | 0 rangetest.m => matlab/rangetest.m | 0 solve.m => matlab/solve.m | 0 spline_surf_vec.m => matlab/spline_surf_vec.m | 0 splinebasevec.m => matlab/splinebasevec.m | 0 swaplines.m => matlab/swaplines.m | 0 transform.m => matlab/transform.m | 0 vec2mat.m => matlab/vec2mat.m | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename basetestvec.m => matlab/basetestvec.m (100%) rename boundary_point.m => matlab/boundary_point.m (100%) rename bounding_boxes_intersection.m => matlab/bounding_boxes_intersection.m (100%) rename build_LS_matrix.m => matlab/build_LS_matrix.m (100%) rename build_reg_matrix.m => matlab/build_reg_matrix.m (100%) rename compute_control_points.m => matlab/compute_control_points.m (100%) rename compute_grid_lines.m => matlab/compute_grid_lines.m (100%) rename compute_patch_edges.m => matlab/compute_patch_edges.m (100%) rename find_int.m => matlab/find_int.m (100%) rename get_errors.m => matlab/get_errors.m (100%) rename get_knot_vector.m => matlab/get_knot_vector.m (100%) rename getpoints.m => matlab/getpoints.m (100%) rename getpoints2.m => matlab/getpoints2.m (100%) rename interpolate_intersections.m => matlab/interpolate_intersections.m (100%) rename intersections.m => matlab/intersections.m (100%) rename patch_patch_intersection.m => matlab/patch_patch_intersection.m (100%) rename plotresultsvec.m => matlab/plotresultsvec.m (100%) rename rangetest.m => matlab/rangetest.m (100%) rename solve.m => matlab/solve.m (100%) rename spline_surf_vec.m => matlab/spline_surf_vec.m (100%) rename splinebasevec.m => matlab/splinebasevec.m (100%) rename swaplines.m => matlab/swaplines.m (100%) rename transform.m => matlab/transform.m (100%) rename vec2mat.m => matlab/vec2mat.m (100%) diff --git a/basetestvec.m b/matlab/basetestvec.m similarity index 100% rename from basetestvec.m rename to matlab/basetestvec.m diff --git a/boundary_point.m b/matlab/boundary_point.m similarity index 100% rename from boundary_point.m rename to matlab/boundary_point.m diff --git a/bounding_boxes_intersection.m b/matlab/bounding_boxes_intersection.m similarity index 100% rename from bounding_boxes_intersection.m rename to matlab/bounding_boxes_intersection.m diff --git a/build_LS_matrix.m b/matlab/build_LS_matrix.m similarity index 100% rename from build_LS_matrix.m rename to matlab/build_LS_matrix.m diff --git a/build_reg_matrix.m b/matlab/build_reg_matrix.m similarity index 100% rename from build_reg_matrix.m rename to matlab/build_reg_matrix.m diff --git a/compute_control_points.m b/matlab/compute_control_points.m similarity index 100% rename from compute_control_points.m rename to matlab/compute_control_points.m diff --git a/compute_grid_lines.m b/matlab/compute_grid_lines.m similarity index 100% rename from compute_grid_lines.m rename to matlab/compute_grid_lines.m diff --git a/compute_patch_edges.m b/matlab/compute_patch_edges.m similarity index 100% rename from compute_patch_edges.m rename to matlab/compute_patch_edges.m diff --git a/find_int.m b/matlab/find_int.m similarity index 100% rename from find_int.m rename to matlab/find_int.m diff --git a/get_errors.m b/matlab/get_errors.m similarity index 100% rename from get_errors.m rename to matlab/get_errors.m diff --git a/get_knot_vector.m b/matlab/get_knot_vector.m similarity index 100% rename from get_knot_vector.m rename to matlab/get_knot_vector.m diff --git a/getpoints.m b/matlab/getpoints.m similarity index 100% rename from getpoints.m rename to matlab/getpoints.m diff --git a/getpoints2.m b/matlab/getpoints2.m similarity index 100% rename from getpoints2.m rename to matlab/getpoints2.m diff --git a/interpolate_intersections.m b/matlab/interpolate_intersections.m similarity index 100% rename from interpolate_intersections.m rename to matlab/interpolate_intersections.m diff --git a/intersections.m b/matlab/intersections.m similarity index 100% rename from intersections.m rename to matlab/intersections.m diff --git a/patch_patch_intersection.m b/matlab/patch_patch_intersection.m similarity index 100% rename from patch_patch_intersection.m rename to matlab/patch_patch_intersection.m diff --git a/plotresultsvec.m b/matlab/plotresultsvec.m similarity index 100% rename from plotresultsvec.m rename to matlab/plotresultsvec.m diff --git a/rangetest.m b/matlab/rangetest.m similarity index 100% rename from rangetest.m rename to matlab/rangetest.m diff --git a/solve.m b/matlab/solve.m similarity index 100% rename from solve.m rename to matlab/solve.m diff --git a/spline_surf_vec.m b/matlab/spline_surf_vec.m similarity index 100% rename from spline_surf_vec.m rename to matlab/spline_surf_vec.m diff --git a/splinebasevec.m b/matlab/splinebasevec.m similarity index 100% rename from splinebasevec.m rename to matlab/splinebasevec.m diff --git a/swaplines.m b/matlab/swaplines.m similarity index 100% rename from swaplines.m rename to matlab/swaplines.m diff --git a/transform.m b/matlab/transform.m similarity index 100% rename from transform.m rename to matlab/transform.m diff --git a/vec2mat.m b/matlab/vec2mat.m similarity index 100% rename from vec2mat.m rename to matlab/vec2mat.m From 88e9a79fa6f0bc82e3e9d36c548aae425fb6465a Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 30 Aug 2017 14:13:26 +0200 Subject: [PATCH 02/67] Populate with standrad files. --- .gitignore | 104 ++++++++ .travis.yml | 8 + LICENSE | 674 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 16 ++ requirements.txt | 1 + 5 files changed, 803 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d5fabb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# PyCharm IDE +.idea/* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0c94ad8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +nguage: python +python: + - "3.5" + - "3.6" +# command to install dependencies +install: "pip3 install -r requirements.txt" +# command to run tests +script: pytest diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a51ec23 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +Intersections +============== + +[![Build Status](https://travis-ci.org/GeoMop/Intersections.svg?branch=master)](https://travis-ci.org/GeoMop/Intersections) +[![Code Health](https://landscape.io/github/GeoMop/Intersections/master/landscape.svg?style=flat)](https://landscape.io/github/GeoMop/Intersections/master) +[![Code Climate](https://codeclimate.com/github/GeoMop/Intersections/badges/gpa.svg)](https://codeclimate.com/github/GeoMop/Intersections) +[![Test Coverage](https://codeclimate.com/github/GeoMop/Intersections/badges/coverage.svg)](https://codeclimate.com/github/GeoMop/Intersections/coverage) + + +Computing intersections of B-spline curves and surfaces. + +Library focus on fast intersection algorithms for non-degenerate intersections of B-spline curves and surfaces +of small degree (especially quadratic). + +Requirements +------------ \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..55b033e --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pytest \ No newline at end of file From 1f92a21c5e9a9e0f6a69dfffad62b46b8dba638f Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 30 Aug 2017 14:14:42 +0200 Subject: [PATCH 03/67] Version of BIH-tree independent of Flow123d. --- src/BIH_tree/bih_node.hh | 141 +++++++++++++++ src/BIH_tree/bih_tree.cc | 276 ++++++++++++++++++++++++++++ src/BIH_tree/bih_tree.hh | 148 +++++++++++++++ src/BIH_tree/bounding_box.hh | 336 +++++++++++++++++++++++++++++++++++ src/BIH_tree/common.hh | 30 ++++ src/BIH_tree/makefile | 5 + 6 files changed, 936 insertions(+) create mode 100644 src/BIH_tree/bih_node.hh create mode 100644 src/BIH_tree/bih_tree.cc create mode 100644 src/BIH_tree/bih_tree.hh create mode 100644 src/BIH_tree/bounding_box.hh create mode 100644 src/BIH_tree/common.hh create mode 100644 src/BIH_tree/makefile diff --git a/src/BIH_tree/bih_node.hh b/src/BIH_tree/bih_node.hh new file mode 100644 index 0000000..f7ac1f7 --- /dev/null +++ b/src/BIH_tree/bih_node.hh @@ -0,0 +1,141 @@ +/*! + * + * Copyright (C) 2015 Technical University of Liberec. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 3 as published by the + * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * + * @file bih_node.hh + * @brief + */ + +#ifndef BIH_NODE_HH_ +#define BIH_NODE_HH_ + +//#include "system/system.hh" +#include "bounding_box.hh" +//#include +#include + +class BIHNode { +public: + + /// count of subareas - don't change + static const unsigned int child_count = 2; + /// count of dimensions + static const unsigned char dimension = 3; + + /** + * Set leaf node. + */ + void set_leaf(unsigned int begin, unsigned int end, double bound,unsigned int depth) { + child_[0]=begin; + child_[1]=end; + bound_ = bound; + axis_=dimension+depth; + } + + /** + * Set non-leaf node. + */ + void set_non_leaf(unsigned int left, unsigned int right, unsigned int axis) { + ASSERT(axis < dimension," "); + ASSERT(is_leaf(), " "); // first must be leaf node (must set bound_) + axis_=axis; + child_[0]=left; + child_[1]=right; + } + + /// return true if node is leaf + bool is_leaf() const + { return axis_ >= dimension; } + + /// return depth of leaf node + + inline unsigned char depth() const + { + ASSERT( is_leaf(), "Not leaf node."); + return axis_ - dimension; + } + + unsigned int leaf_begin() const + { + ASSERT( is_leaf(), "Not leaf node."); + return child_[0]; + } + + unsigned int leaf_end() const + { + ASSERT( is_leaf(), "Not leaf node."); + return child_[1]; + } + + /** + * Get count of elements stored in + * + * @return Count of elements contained in node + */ + unsigned int leaf_size() const + { + ASSERT( is_leaf(), "Not leaf node."); + return child_[1] - child_[0]; + } + + /// return axes (coordination of splitting) of inner node + unsigned int axis() const + { + ASSERT(!is_leaf(), "Not in branch node.\n"); + return axis_; + } + + double bound() const + { + return bound_; + } + + /// Return index of child node. + unsigned int child(unsigned int i_child) const + { + ASSERT(!is_leaf(), "Not in branch node.\n"); + ASSERT( i_child < child_count, "" ); + return child_[i_child]; + + } +private: + + + /** + * Set depth of node to axes_ class members + * + * @param depth Depth of node in tree. + */ + void set_depth(unsigned int depth) { + axis_ = depth + dimension; + } + + /// child nodes indexes + unsigned int child_[child_count]; + + /** + * A non-leaf node has two childs (left and right). Their bounding boxes are created from the bounding box of parent + * so that in one direction the left child set max to its bound_ and the right child set its min to bound_. + * This way we can always repcreate bounding box of every node when traversing the tree from the root. + */ + double bound_; + + /** + * Value stores coordination of splitting area for inner nodes or depth for leaf nodes + * - values 0,1,2 indicate inner node of tree and coordination of splitting area + * - values 3 and greater indicate leaf node of tree and store depth of node (depth = axes_ - 3) + */ + unsigned char axis_; + +}; + +#endif /* BIH_NODE_HH_ */ diff --git a/src/BIH_tree/bih_tree.cc b/src/BIH_tree/bih_tree.cc new file mode 100644 index 0000000..925b79c --- /dev/null +++ b/src/BIH_tree/bih_tree.cc @@ -0,0 +1,276 @@ +/*! + * + * Copyright (C) 2015 Technical University of Liberec. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 3 as published by the + * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * + * @file bih_tree.cc + * @brief + */ + +#include "bih_tree.hh" +#include "bih_node.hh" + +//#include +#include + +/** + * Minimum reduction of box size to allow + * splitting of a node during tree creation. + */ +const double BIHTree::size_reduce_factor = 0.8; + + +BIHTree::BIHTree(unsigned int soft_leaf_size_limit) +: leaf_size_limit(soft_leaf_size_limit), r_gen(123) +{ +} + + +BIHTree::~BIHTree() { +} + +void BIHTree::add_boxes(const std::vector &boxes) { + for(const BoundingBox & box : boxes) { + this->elements_.push_back(box); + main_box_.expand(box); + } +} + +void BIHTree::construct() { + ASSERT( elements_.size() > 0, " "); + + max_n_levels = 2*log(elements_.size())/log(2); + nodes_.reserve(2*elements_.size() / leaf_size_limit); + in_leaves_.resize(elements_.size()); + for(unsigned int i=0; i= median ) { + right_bound = std::min( right_bound, elements_[ *right ].min(axis) ); + --right; + } + std::swap( *left, *right); + } + } + // in any case left==right is now the first element of the right group + + if ( elements_[ *left ].projection_center(axis) < median) { + left_bound = std::max( left_bound, elements_[ *left ].max(axis) ); + ++left; + ++right; + } else { + right_bound = std::min( right_bound, elements_[ *right ].min(axis) ); + } + + unsigned int left_begin = node.leaf_begin(); + unsigned int left_end = left - in_leaves_.begin(); + unsigned int right_end = node.leaf_end(); + unsigned int depth = node.depth()+1; + // create new leaf nodes and possibly call split_node on them + // can not use node reference anymore + nodes_.push_back(BIHNode()); + nodes_.back().set_leaf(left_begin, left_end, left_bound, depth); + nodes_.push_back(BIHNode()); + nodes_.back().set_leaf(left_end, right_end, right_bound, depth); + + nodes_[node_idx].set_non_leaf(nodes_.size()-2, nodes_.size()-1, axis); + +// xprintf(Msg, "%d %f %f %f %f %d %d\n", node_idx, node_box.min(axis), left_bound, right_bound, node_box.max(axis), +// left_end - left_begin, right_end - left_end ); +} + + +void BIHTree::make_node(const BoundingBox &box, unsigned int node_idx) { + // we must refer to the node by index to prevent seg. fault due to nodes_ reallocation + + split_node(box,node_idx); + + { + BIHNode &node = nodes_[node_idx]; + BIHNode &child = nodes_[ node.child(0) ]; + BoundingBox node_box(box); + node_box.set_max(node.axis(), child.bound() ); + if ( child.leaf_size() > leaf_size_limit + && child.depth() < max_n_levels) +// && ( node.axis() != node_box.longest_axis() +// || node_box.size(node_box.longest_axis()) < box.size(node.axis()) * size_reduce_factor ) +// ) + { + make_node(node_box, node.child(0) ); + } +// else{ +// xprintf(Msg,"%d %d %f %f\n",node_idx, child.leaf_size(), +// node_box.size(node_box.longest_axis()), +// box.size(node.axis())); +// } + } + + { + BIHNode &node = nodes_[node_idx]; + BIHNode &child = nodes_[ node.child(1) ]; + BoundingBox node_box(box); + node_box.set_min(node.axis(), child.bound() ); + if ( child.leaf_size() > leaf_size_limit + && child.depth() < max_n_levels) +// && ( node.axis() != node_box.longest_axis() +// || node_box.size(node_box.longest_axis()) < box.size(node.axis()) * size_reduce_factor ) +// ) + { + make_node(node_box, node.child(1) ); + } +// else{ +// xprintf(Msg,"%d %d %f %f\n",node_idx, child.leaf_size(), +// node_box.size(node_box.longest_axis()), +// box.size(node.axis())); +// } + } +} + + +double BIHTree::estimate_median(unsigned char axis, const BIHNode &node) +{ + unsigned int median_idx; + unsigned int n_elements = node.leaf_size(); + + // TODO: possible optimizations: + // - try to apply nth_element directly to in_leaves_ array + // - if current approach is better (due to cache memory), check randomization of median for large meshes + // - good balancing of tree is crutial both for creation and find method + +// unsigned int sample_size = 50+n_elements/5; +// if (n_elements > sample_size) { +// // random sample +// std::uniform_int_distribution distribution(node.leaf_begin(), node.leaf_end()-1); +// coors_.resize(sample_size); +// for (unsigned int i=0; ir_gen); +// +// coors_[i] = elements_[ in_leaves_[ median_idx ] ].projection_center(axis); +// } +// +// } else + { + // all elements + coors_.resize(n_elements); + for (unsigned int i=0; i &result_list, bool full_list) const +{ + std::stack > node_stack; + ASSERT(result_list.size() == 0, ""); + + unsigned int counter = 0; + node_stack.push(0); + while (! node_stack.empty()) { + const BIHNode &node = nodes_[node_stack.top()]; + //DebugOut().fmt("node: {}\n", node_stack.top() ); + node_stack.pop(); + + + if (node.is_leaf()) { + + counter ++; + //START_TIMER("leaf"); + for (unsigned int i=node.leaf_begin(); i cpy(result_list); + sort(cpy.begin(), cpy.end()); + std::vector::iterator it = unique(cpy.begin(), cpy.end()); + OLD_ASSERT_EQUAL(cpy.size() , it - cpy.begin()); +#endif +} + + +void BIHTree::find_point(const Point &point, std::vector &result_list, bool full_list) const +{ + find_bounding_box(BoundingBox(point), result_list, full_list); +} + + + diff --git a/src/BIH_tree/bih_tree.hh b/src/BIH_tree/bih_tree.hh new file mode 100644 index 0000000..3d55fc5 --- /dev/null +++ b/src/BIH_tree/bih_tree.hh @@ -0,0 +1,148 @@ +/*! + * + * Copyright (C) 2015 Technical University of Liberec. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 3 as published by the + * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * + * @file bih_tree.hh + * @brief + */ + +#ifndef BIH_TREE_HH_ +#define BIH_TREE_HH_ + +#include "bih_node.hh" +//#include "mesh/mesh.h" +//#include "mesh/point.hh" +//#include + + + +/** + * @brief Class for O(log N) lookup for intersections with a set of bounding boxes. + * + * Notes: + * Assumes spacedim=3. Implementation was designed for arbitrary number of childs per node, but + * currently it supports max 2 childs per node (binary tree). + * + */ +class BIHTree { +public: + /// count of dimensions + static const unsigned int dimension = 3; + /// max count of elements to estimate median - value must be even + static const unsigned int max_median_sample_size = 5; + + /** + * Constructor + * + * Set class members and call functions which create tree + * @param mesh - Mesh used for creation the tree + * @param soft_leaf_size_limit - Maximal number of elements stored in a leaf node of BIH tree. + */ + BIHTree(unsigned int soft_leaf_size_limit = 20); + + void add_boxes(const std::vector &boxes); + + void construct(); + + /** + * Destructor + */ + ~BIHTree(); + + /** + * Get count of elements stored in tree + * + * @return Count of bounding boxes stored in elements_ member + */ + unsigned int get_element_count() const; + + /** + * Main bounding box of the whole tree. + */ + const BoundingBox &tree_box() const; + + /** + * Gets elements which can have intersection with bounding box + * + * @param boundingBox Bounding box which is tested if has intersection + * @param result_list vector of ids of suspect elements + * @param full_list put to result_list all suspect elements found in leaf node or add only those that has intersection with boundingBox + */ + void find_bounding_box(const BoundingBox &boundingBox, std::vector &result_list, bool full_list = false) const; + + /** + * Gets elements which can have intersection with point + * + * @param point Point which is tested if has intersection + * @param result_list vector of ids of suspect elements + * @param full_list put to result_list all suspect elements found in leaf node or add only those that has intersection with point + */ + void find_point(const Point &point, std::vector &result_list, bool full_list = false) const; + + /** + * Get vector of mesh elements bounding boxes + * + * @return elements_ vector + */ + std::vector &get_elements() { return elements_; } + + /// Gets bounding box of element of given index @p ele_index. + const BoundingBox & ele_bounding_box(unsigned int ele_idx) const; + +protected: + /// required reduction in size of box to allow further splitting + static const double size_reduce_factor; + + /// create bounding boxes of element + //void element_boxes(); + + /// split tree node given by node_idx, distribute elements to child nodes + void split_node(const BoundingBox &node_box, unsigned int node_idx); + + /// create child nodes of node given by node_idx + void make_node(const BoundingBox &box, unsigned int node_idx); + + /** + * For given node takes projection of centers of bounding boxes of its elements to axis given by + * @p node::axis() + * and estimate median of these values. That is optimal split point. + * Precise median is computed for sets smaller then @p max_median_sample_size + * estimate from random sample is used for larger sets. + */ + double estimate_median(unsigned char axis, const BIHNode &node); + + /// mesh + //Mesh* mesh_; + /// vector of mesh elements bounding boxes (from mesh) + std::vector elements_; + /// Main bounding box. (from mesh) + BoundingBox main_box_; + + /// vector of tree nodes + std::vector nodes_; + /// Maximal number of elements stored in a leaf node of BIH tree. + unsigned int leaf_size_limit; + /// Maximal count of BIH tree levels + unsigned int max_n_levels; + + /// vector stored element indexes in leaf nodes + std::vector in_leaves_; + /// temporary vector stored values of coordinations for calculating median + std::vector coors_; + + // random generator + std::mt19937 r_gen; + + +}; + +#endif /* BIH_TREE_HH_ */ diff --git a/src/BIH_tree/bounding_box.hh b/src/BIH_tree/bounding_box.hh new file mode 100644 index 0000000..afb3947 --- /dev/null +++ b/src/BIH_tree/bounding_box.hh @@ -0,0 +1,336 @@ +/*! + * + * Copyright (C) 2015 Technical University of Liberec. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 3 as published by the + * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * + * @file bounding_box.hh + * @brief + */ + +#ifndef BOX_ELEMENT_HH_ +#define BOX_ELEMENT_HH_ + + +#include +//#include +#include + +#include "common.hh" +#include +#include + +using namespace std; + +struct Point +{ + std::array p_; + + Point() : + p_({0,0,0}) + {} + + Point(double x, double y, double z) : + p_({x,y,z}) + {} + + double &operator [](uint i) { + return p_[i]; + } + + const double &operator [](uint i) const { + return p_[i]; + } + + +}; + +Point operator+(Point a, Point b) { + return Point( a[0]+b[0], a[1]+b[1], a[2]+b[2]); +} + +Point operator-(Point a, Point b) { + return Point( a[0]-b[0], a[1]-b[1], a[2]-b[2]); +} + +Point operator*(double a, Point b) { + return Point( a*b[0], a*b[1], a*b[2]); +} + + +bool operator <=(Point a, Point b) { + return + a[0] <= b[0] && + a[1] <= b[1] && + a[2] <= b[2]; +} + +/** + * @brief Bounding box in 3d ambient space. + * + * Primary intention is usage in BIHTree and various speedups of non-compatible + * intersections. + * + * Copy constructor and assignment are default provided by compiler. + * These can be used to set bounds latter on without particular method + * to this end: + * + * @code + * BoundingBox box; // non-initialized box + * box=BoundingBox( arma::vec3("0 1 2"), arma::vec3("4 5 6") ); + * @endcode + * + * Don;t worry about performance, all is inlined. + */ +class BoundingBox { +public: + + /// Currently we set dimension to 3. + static const unsigned int dimension = 3; + /// stabilization parameter + static constexpr double epsilon = 64 * std::numeric_limits::epsilon(); + + + + /** + * Default constructor. + * No initialization of vertices. Be very careful using this. + * One necessary usage is vector of BoundigBox. + */ + BoundingBox() {} + + /** + * Constructor for point box. + */ + BoundingBox(const Point &min) + : min_vertex_(min), max_vertex_(min) + {}; + + /** + * Constructor. + * + * From given minimal and maximal vertex. + */ + BoundingBox(const Point &min, const Point &max) + : min_vertex_(min), max_vertex_(max) + { + ASSERT( min <= max , "Wrong coordinates in constructor."); + }; + + /** + * Constructor. + * + * Make bounding box for set of points. + */ + BoundingBox(const vector &points) { + ASSERT( 0 < points.size(), "" ); + + auto it = points.begin(); + max_vertex_ = min_vertex_ = *it; + ++it; + for(; it != points.end(); ++it) expand( *it ); + } + + + /** + * Set maximum in given axis. + */ + void set_max(unsigned int axis, double max) { + ASSERT(axis < dimension, ""); + ASSERT( min(axis) <= max, ""); + max_vertex_[axis] = max; + } + + /** + * Set minimum on given axis. + */ + void set_min(unsigned int axis, double min) { + ASSERT(axis < dimension, ""); + ASSERT(min <= max(axis), ""); + min_vertex_[axis] = min; + } + + /** + * Return minimal vertex of the bounding box. + */ + const Point &min() const { + return min_vertex_; + } + + /** + * Return maximal vertex of the bounding box. + */ + const Point &max() const { + return max_vertex_; + } + + /** + * Return minimal value on given axis. + */ + double min(unsigned int axis) const { + return min()[axis]; + } + + /** + * Return maximal value on given axis. + */ + double max(unsigned int axis) const { + return max()[axis]; + } + + + /** + * Return size of the box in given axis. + */ + double size(unsigned int axis) const { + return max()[axis] - min()[axis]; + } + + + /** + * Return center of the bounding box. + */ + Point center() const { + return 0.5 * (max_vertex_ + min_vertex_); + } + + /** + * Return center of projection of the bounding box to given @p axis. + * Axis coding is: 0 - axis x, 1 - axis y, 2 - axis z. + */ + double projection_center(unsigned int axis) const{ + ASSERT(axis < dimension, ""); + return 0.5 * (max_vertex_[axis] + min_vertex_[axis]); + } + + /** + * Returns true is the box element contains @p point + * + * @param point Testing point + * @return True if box element contains point + */ + bool contains_point(const Point &point) const + { + for (unsigned int i=0; i b2.max_vertex_[i] + epsilon) || + (b2.min_vertex_[i] > max_vertex_[i] + epsilon ) ) return false; + } + return true; + } + + /** + * Returns true if projection of the box to @p axis is an interval + * less then (with tolerance) to given @p value. + */ + bool projection_lt(unsigned int axis, double value) const + { + return max_vertex_[axis] + epsilon < value; + } + + /** + * Returns true if projection of the box to @p axis is an interval + * greater then (with tolerance) to given @p value. + */ + bool projection_gt(unsigned int axis, double value) const + { + return min_vertex_[axis] - epsilon > value; + } + + /** + * Split box into two boxes along @p axis by the + * plane going through @p splitting_point on the axis. + */ + void split(unsigned int axis, double splitting_point, + BoundingBox &left, BoundingBox &right ) const + { + ASSERT(axis < dimension, ""); + if (min_vertex_[axis] <= splitting_point && splitting_point <= max_vertex_[axis] ) { + left = *this; + right = *this; + left.max_vertex_[axis] = splitting_point; + right.min_vertex_[axis] = splitting_point; + } else { + throw; + //THROW( ExcSplitting() << EI_interval_left(min_vertex_[axis]) + // << EI_interval_right(max_vertex_[axis]) + // << EI_split_point(splitting_point) ); + } + } + + /** + * Expand bounding box to contain also given @p point. + */ + void expand(const Point &point) { + for(unsigned int j=0; j diff[0]) + ? ( diff[2] > diff[1] ? 2 : 1 ) + : ( diff[2] > diff[0] ? 2 : 0 ); + } + + +private: + /// minimal coordinates of bounding box + Point min_vertex_; + /// maximal coordinates of bounding box + Point max_vertex_; +}; + +/// Overloads output operator for box. +inline ostream &operator<<(ostream &stream, const BoundingBox &box) { + stream << "Box(" + << box.min(0) << " " + << box.min(1) << " " + << box.min(2) << "; " + << box.max(0) << " " + << box.max(1) << " " + << box.max(2) << ")"; + return stream; +} + +#endif /* BOX_ELEMENT_HH_ */ diff --git a/src/BIH_tree/common.hh b/src/BIH_tree/common.hh new file mode 100644 index 0000000..f02814a --- /dev/null +++ b/src/BIH_tree/common.hh @@ -0,0 +1,30 @@ +/*! + * + * Copyright (C) 2015 Technical University of Liberec. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 3 as published by the + * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * + * @file bounding_box.hh + * @brief + */ + +#ifndef COMMON_HH_ +#define COMMON_HH_ + +#if DEBUG +#define ASSERT( COND, MESSAGE) + if (! (COND)) { + cout << MESSAGE; + } +#else +#define ASSERT(COND, MESSAGE) +#endif + +#endif diff --git a/src/BIH_tree/makefile b/src/BIH_tree/makefile new file mode 100644 index 0000000..4b67e22 --- /dev/null +++ b/src/BIH_tree/makefile @@ -0,0 +1,5 @@ +all: + $(CC) -std=c++11 -c bih_tree.cc + +clean: + rm *.o \ No newline at end of file From 50db4e981c15babe946323bcce23ea4dbeb23d10 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 30 Aug 2017 14:15:29 +0200 Subject: [PATCH 04/67] Basic Python files. --- src/bs_curve.py | 12 ++++++++++++ src/bs_surface.py | 5 +++++ src/isec_curv_surf.py | 8 ++++++++ src/isec_surf_surf.py | 8 ++++++++ 4 files changed, 33 insertions(+) create mode 100644 src/bs_curve.py create mode 100644 src/bs_surface.py create mode 100644 src/isec_curv_surf.py create mode 100644 src/isec_surf_surf.py diff --git a/src/bs_curve.py b/src/bs_curve.py new file mode 100644 index 0000000..7a38ed0 --- /dev/null +++ b/src/bs_curve.py @@ -0,0 +1,12 @@ +class BSCurve: + ''' + Class representing a general B-spline curve. + Specializations for particular degree are derived. + ''' + pass + + +class BSCurveD2: + ''' + Quadratic B-spline. + ''' \ No newline at end of file diff --git a/src/bs_surface.py b/src/bs_surface.py new file mode 100644 index 0000000..c10a266 --- /dev/null +++ b/src/bs_surface.py @@ -0,0 +1,5 @@ +class BSSurface: + ''' + Representation of the B-spline surface. + ''' + pass \ No newline at end of file diff --git a/src/isec_curv_surf.py b/src/isec_curv_surf.py new file mode 100644 index 0000000..980ba0c --- /dev/null +++ b/src/isec_curv_surf.py @@ -0,0 +1,8 @@ +class IsecCurvSurf: + ''' + Class for calculation and representation of the intersection of + B-spline curve and B-spline surface. + Currently only (multi)-point intersections are assumed. + ''' + def __init__(self, curve, surface): + pass \ No newline at end of file diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py new file mode 100644 index 0000000..4ff7684 --- /dev/null +++ b/src/isec_surf_surf.py @@ -0,0 +1,8 @@ +class IsecSurfSurf: + ''' + Calculation and representation of intersection of two B-spline surfaces. + + Result is set of B-spline segments approximating the intersection branches. + Every segment consists of: a 3D curve and two 2D curves in parametric UV space of the surfaces. + ''' + \ No newline at end of file From 5c891f433c5ec50890e251eb768dbf13b4aae1b1 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 30 Aug 2017 14:28:27 +0200 Subject: [PATCH 05/67] Add pybind11 as submodule. --- .gitmodules | 4 ++++ external/pybind11 | 1 + 2 files changed, 5 insertions(+) create mode 100644 .gitmodules create mode 160000 external/pybind11 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..53c39bf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "external/pybind11"] + path = external/pybind11 + url = git@github.com:pybind/pybind11.git + branch = stable diff --git a/external/pybind11 b/external/pybind11 new file mode 160000 index 0000000..1df91d3 --- /dev/null +++ b/external/pybind11 @@ -0,0 +1 @@ +Subproject commit 1df91d36cfa997c8987889234304ea5e37cc3e68 From ee3ad4c46450b31975d563133b4beae0e4d61af9 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 31 Aug 2017 19:28:25 +0200 Subject: [PATCH 06/67] Remove PYBIND --- .gitmodules | 4 ---- external/pybind11 | 1 - 2 files changed, 5 deletions(-) delete mode 160000 external/pybind11 diff --git a/.gitmodules b/.gitmodules index 53c39bf..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +0,0 @@ -[submodule "external/pybind11"] - path = external/pybind11 - url = git@github.com:pybind/pybind11.git - branch = stable diff --git a/external/pybind11 b/external/pybind11 deleted file mode 160000 index 1df91d3..0000000 --- a/external/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1df91d36cfa997c8987889234304ea5e37cc3e68 From d54592db6c2ff746f5b904deed0aab2dc1573f2d Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 31 Aug 2017 19:29:37 +0200 Subject: [PATCH 07/67] Remove BIH sources. --- src/BIH_tree/bih_node.hh | 141 --------------- src/BIH_tree/bih_tree.cc | 276 ---------------------------- src/BIH_tree/bih_tree.hh | 148 --------------- src/BIH_tree/bounding_box.hh | 336 ----------------------------------- src/BIH_tree/common.hh | 30 ---- src/BIH_tree/makefile | 5 - 6 files changed, 936 deletions(-) delete mode 100644 src/BIH_tree/bih_node.hh delete mode 100644 src/BIH_tree/bih_tree.cc delete mode 100644 src/BIH_tree/bih_tree.hh delete mode 100644 src/BIH_tree/bounding_box.hh delete mode 100644 src/BIH_tree/common.hh delete mode 100644 src/BIH_tree/makefile diff --git a/src/BIH_tree/bih_node.hh b/src/BIH_tree/bih_node.hh deleted file mode 100644 index f7ac1f7..0000000 --- a/src/BIH_tree/bih_node.hh +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * - * Copyright (C) 2015 Technical University of Liberec. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License version 3 as published by the - * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * - * @file bih_node.hh - * @brief - */ - -#ifndef BIH_NODE_HH_ -#define BIH_NODE_HH_ - -//#include "system/system.hh" -#include "bounding_box.hh" -//#include -#include - -class BIHNode { -public: - - /// count of subareas - don't change - static const unsigned int child_count = 2; - /// count of dimensions - static const unsigned char dimension = 3; - - /** - * Set leaf node. - */ - void set_leaf(unsigned int begin, unsigned int end, double bound,unsigned int depth) { - child_[0]=begin; - child_[1]=end; - bound_ = bound; - axis_=dimension+depth; - } - - /** - * Set non-leaf node. - */ - void set_non_leaf(unsigned int left, unsigned int right, unsigned int axis) { - ASSERT(axis < dimension," "); - ASSERT(is_leaf(), " "); // first must be leaf node (must set bound_) - axis_=axis; - child_[0]=left; - child_[1]=right; - } - - /// return true if node is leaf - bool is_leaf() const - { return axis_ >= dimension; } - - /// return depth of leaf node - - inline unsigned char depth() const - { - ASSERT( is_leaf(), "Not leaf node."); - return axis_ - dimension; - } - - unsigned int leaf_begin() const - { - ASSERT( is_leaf(), "Not leaf node."); - return child_[0]; - } - - unsigned int leaf_end() const - { - ASSERT( is_leaf(), "Not leaf node."); - return child_[1]; - } - - /** - * Get count of elements stored in - * - * @return Count of elements contained in node - */ - unsigned int leaf_size() const - { - ASSERT( is_leaf(), "Not leaf node."); - return child_[1] - child_[0]; - } - - /// return axes (coordination of splitting) of inner node - unsigned int axis() const - { - ASSERT(!is_leaf(), "Not in branch node.\n"); - return axis_; - } - - double bound() const - { - return bound_; - } - - /// Return index of child node. - unsigned int child(unsigned int i_child) const - { - ASSERT(!is_leaf(), "Not in branch node.\n"); - ASSERT( i_child < child_count, "" ); - return child_[i_child]; - - } -private: - - - /** - * Set depth of node to axes_ class members - * - * @param depth Depth of node in tree. - */ - void set_depth(unsigned int depth) { - axis_ = depth + dimension; - } - - /// child nodes indexes - unsigned int child_[child_count]; - - /** - * A non-leaf node has two childs (left and right). Their bounding boxes are created from the bounding box of parent - * so that in one direction the left child set max to its bound_ and the right child set its min to bound_. - * This way we can always repcreate bounding box of every node when traversing the tree from the root. - */ - double bound_; - - /** - * Value stores coordination of splitting area for inner nodes or depth for leaf nodes - * - values 0,1,2 indicate inner node of tree and coordination of splitting area - * - values 3 and greater indicate leaf node of tree and store depth of node (depth = axes_ - 3) - */ - unsigned char axis_; - -}; - -#endif /* BIH_NODE_HH_ */ diff --git a/src/BIH_tree/bih_tree.cc b/src/BIH_tree/bih_tree.cc deleted file mode 100644 index 925b79c..0000000 --- a/src/BIH_tree/bih_tree.cc +++ /dev/null @@ -1,276 +0,0 @@ -/*! - * - * Copyright (C) 2015 Technical University of Liberec. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License version 3 as published by the - * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * - * @file bih_tree.cc - * @brief - */ - -#include "bih_tree.hh" -#include "bih_node.hh" - -//#include -#include - -/** - * Minimum reduction of box size to allow - * splitting of a node during tree creation. - */ -const double BIHTree::size_reduce_factor = 0.8; - - -BIHTree::BIHTree(unsigned int soft_leaf_size_limit) -: leaf_size_limit(soft_leaf_size_limit), r_gen(123) -{ -} - - -BIHTree::~BIHTree() { -} - -void BIHTree::add_boxes(const std::vector &boxes) { - for(const BoundingBox & box : boxes) { - this->elements_.push_back(box); - main_box_.expand(box); - } -} - -void BIHTree::construct() { - ASSERT( elements_.size() > 0, " "); - - max_n_levels = 2*log(elements_.size())/log(2); - nodes_.reserve(2*elements_.size() / leaf_size_limit); - in_leaves_.resize(elements_.size()); - for(unsigned int i=0; i= median ) { - right_bound = std::min( right_bound, elements_[ *right ].min(axis) ); - --right; - } - std::swap( *left, *right); - } - } - // in any case left==right is now the first element of the right group - - if ( elements_[ *left ].projection_center(axis) < median) { - left_bound = std::max( left_bound, elements_[ *left ].max(axis) ); - ++left; - ++right; - } else { - right_bound = std::min( right_bound, elements_[ *right ].min(axis) ); - } - - unsigned int left_begin = node.leaf_begin(); - unsigned int left_end = left - in_leaves_.begin(); - unsigned int right_end = node.leaf_end(); - unsigned int depth = node.depth()+1; - // create new leaf nodes and possibly call split_node on them - // can not use node reference anymore - nodes_.push_back(BIHNode()); - nodes_.back().set_leaf(left_begin, left_end, left_bound, depth); - nodes_.push_back(BIHNode()); - nodes_.back().set_leaf(left_end, right_end, right_bound, depth); - - nodes_[node_idx].set_non_leaf(nodes_.size()-2, nodes_.size()-1, axis); - -// xprintf(Msg, "%d %f %f %f %f %d %d\n", node_idx, node_box.min(axis), left_bound, right_bound, node_box.max(axis), -// left_end - left_begin, right_end - left_end ); -} - - -void BIHTree::make_node(const BoundingBox &box, unsigned int node_idx) { - // we must refer to the node by index to prevent seg. fault due to nodes_ reallocation - - split_node(box,node_idx); - - { - BIHNode &node = nodes_[node_idx]; - BIHNode &child = nodes_[ node.child(0) ]; - BoundingBox node_box(box); - node_box.set_max(node.axis(), child.bound() ); - if ( child.leaf_size() > leaf_size_limit - && child.depth() < max_n_levels) -// && ( node.axis() != node_box.longest_axis() -// || node_box.size(node_box.longest_axis()) < box.size(node.axis()) * size_reduce_factor ) -// ) - { - make_node(node_box, node.child(0) ); - } -// else{ -// xprintf(Msg,"%d %d %f %f\n",node_idx, child.leaf_size(), -// node_box.size(node_box.longest_axis()), -// box.size(node.axis())); -// } - } - - { - BIHNode &node = nodes_[node_idx]; - BIHNode &child = nodes_[ node.child(1) ]; - BoundingBox node_box(box); - node_box.set_min(node.axis(), child.bound() ); - if ( child.leaf_size() > leaf_size_limit - && child.depth() < max_n_levels) -// && ( node.axis() != node_box.longest_axis() -// || node_box.size(node_box.longest_axis()) < box.size(node.axis()) * size_reduce_factor ) -// ) - { - make_node(node_box, node.child(1) ); - } -// else{ -// xprintf(Msg,"%d %d %f %f\n",node_idx, child.leaf_size(), -// node_box.size(node_box.longest_axis()), -// box.size(node.axis())); -// } - } -} - - -double BIHTree::estimate_median(unsigned char axis, const BIHNode &node) -{ - unsigned int median_idx; - unsigned int n_elements = node.leaf_size(); - - // TODO: possible optimizations: - // - try to apply nth_element directly to in_leaves_ array - // - if current approach is better (due to cache memory), check randomization of median for large meshes - // - good balancing of tree is crutial both for creation and find method - -// unsigned int sample_size = 50+n_elements/5; -// if (n_elements > sample_size) { -// // random sample -// std::uniform_int_distribution distribution(node.leaf_begin(), node.leaf_end()-1); -// coors_.resize(sample_size); -// for (unsigned int i=0; ir_gen); -// -// coors_[i] = elements_[ in_leaves_[ median_idx ] ].projection_center(axis); -// } -// -// } else - { - // all elements - coors_.resize(n_elements); - for (unsigned int i=0; i &result_list, bool full_list) const -{ - std::stack > node_stack; - ASSERT(result_list.size() == 0, ""); - - unsigned int counter = 0; - node_stack.push(0); - while (! node_stack.empty()) { - const BIHNode &node = nodes_[node_stack.top()]; - //DebugOut().fmt("node: {}\n", node_stack.top() ); - node_stack.pop(); - - - if (node.is_leaf()) { - - counter ++; - //START_TIMER("leaf"); - for (unsigned int i=node.leaf_begin(); i cpy(result_list); - sort(cpy.begin(), cpy.end()); - std::vector::iterator it = unique(cpy.begin(), cpy.end()); - OLD_ASSERT_EQUAL(cpy.size() , it - cpy.begin()); -#endif -} - - -void BIHTree::find_point(const Point &point, std::vector &result_list, bool full_list) const -{ - find_bounding_box(BoundingBox(point), result_list, full_list); -} - - - diff --git a/src/BIH_tree/bih_tree.hh b/src/BIH_tree/bih_tree.hh deleted file mode 100644 index 3d55fc5..0000000 --- a/src/BIH_tree/bih_tree.hh +++ /dev/null @@ -1,148 +0,0 @@ -/*! - * - * Copyright (C) 2015 Technical University of Liberec. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License version 3 as published by the - * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * - * @file bih_tree.hh - * @brief - */ - -#ifndef BIH_TREE_HH_ -#define BIH_TREE_HH_ - -#include "bih_node.hh" -//#include "mesh/mesh.h" -//#include "mesh/point.hh" -//#include - - - -/** - * @brief Class for O(log N) lookup for intersections with a set of bounding boxes. - * - * Notes: - * Assumes spacedim=3. Implementation was designed for arbitrary number of childs per node, but - * currently it supports max 2 childs per node (binary tree). - * - */ -class BIHTree { -public: - /// count of dimensions - static const unsigned int dimension = 3; - /// max count of elements to estimate median - value must be even - static const unsigned int max_median_sample_size = 5; - - /** - * Constructor - * - * Set class members and call functions which create tree - * @param mesh - Mesh used for creation the tree - * @param soft_leaf_size_limit - Maximal number of elements stored in a leaf node of BIH tree. - */ - BIHTree(unsigned int soft_leaf_size_limit = 20); - - void add_boxes(const std::vector &boxes); - - void construct(); - - /** - * Destructor - */ - ~BIHTree(); - - /** - * Get count of elements stored in tree - * - * @return Count of bounding boxes stored in elements_ member - */ - unsigned int get_element_count() const; - - /** - * Main bounding box of the whole tree. - */ - const BoundingBox &tree_box() const; - - /** - * Gets elements which can have intersection with bounding box - * - * @param boundingBox Bounding box which is tested if has intersection - * @param result_list vector of ids of suspect elements - * @param full_list put to result_list all suspect elements found in leaf node or add only those that has intersection with boundingBox - */ - void find_bounding_box(const BoundingBox &boundingBox, std::vector &result_list, bool full_list = false) const; - - /** - * Gets elements which can have intersection with point - * - * @param point Point which is tested if has intersection - * @param result_list vector of ids of suspect elements - * @param full_list put to result_list all suspect elements found in leaf node or add only those that has intersection with point - */ - void find_point(const Point &point, std::vector &result_list, bool full_list = false) const; - - /** - * Get vector of mesh elements bounding boxes - * - * @return elements_ vector - */ - std::vector &get_elements() { return elements_; } - - /// Gets bounding box of element of given index @p ele_index. - const BoundingBox & ele_bounding_box(unsigned int ele_idx) const; - -protected: - /// required reduction in size of box to allow further splitting - static const double size_reduce_factor; - - /// create bounding boxes of element - //void element_boxes(); - - /// split tree node given by node_idx, distribute elements to child nodes - void split_node(const BoundingBox &node_box, unsigned int node_idx); - - /// create child nodes of node given by node_idx - void make_node(const BoundingBox &box, unsigned int node_idx); - - /** - * For given node takes projection of centers of bounding boxes of its elements to axis given by - * @p node::axis() - * and estimate median of these values. That is optimal split point. - * Precise median is computed for sets smaller then @p max_median_sample_size - * estimate from random sample is used for larger sets. - */ - double estimate_median(unsigned char axis, const BIHNode &node); - - /// mesh - //Mesh* mesh_; - /// vector of mesh elements bounding boxes (from mesh) - std::vector elements_; - /// Main bounding box. (from mesh) - BoundingBox main_box_; - - /// vector of tree nodes - std::vector nodes_; - /// Maximal number of elements stored in a leaf node of BIH tree. - unsigned int leaf_size_limit; - /// Maximal count of BIH tree levels - unsigned int max_n_levels; - - /// vector stored element indexes in leaf nodes - std::vector in_leaves_; - /// temporary vector stored values of coordinations for calculating median - std::vector coors_; - - // random generator - std::mt19937 r_gen; - - -}; - -#endif /* BIH_TREE_HH_ */ diff --git a/src/BIH_tree/bounding_box.hh b/src/BIH_tree/bounding_box.hh deleted file mode 100644 index afb3947..0000000 --- a/src/BIH_tree/bounding_box.hh +++ /dev/null @@ -1,336 +0,0 @@ -/*! - * - * Copyright (C) 2015 Technical University of Liberec. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License version 3 as published by the - * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * - * @file bounding_box.hh - * @brief - */ - -#ifndef BOX_ELEMENT_HH_ -#define BOX_ELEMENT_HH_ - - -#include -//#include -#include - -#include "common.hh" -#include -#include - -using namespace std; - -struct Point -{ - std::array p_; - - Point() : - p_({0,0,0}) - {} - - Point(double x, double y, double z) : - p_({x,y,z}) - {} - - double &operator [](uint i) { - return p_[i]; - } - - const double &operator [](uint i) const { - return p_[i]; - } - - -}; - -Point operator+(Point a, Point b) { - return Point( a[0]+b[0], a[1]+b[1], a[2]+b[2]); -} - -Point operator-(Point a, Point b) { - return Point( a[0]-b[0], a[1]-b[1], a[2]-b[2]); -} - -Point operator*(double a, Point b) { - return Point( a*b[0], a*b[1], a*b[2]); -} - - -bool operator <=(Point a, Point b) { - return - a[0] <= b[0] && - a[1] <= b[1] && - a[2] <= b[2]; -} - -/** - * @brief Bounding box in 3d ambient space. - * - * Primary intention is usage in BIHTree and various speedups of non-compatible - * intersections. - * - * Copy constructor and assignment are default provided by compiler. - * These can be used to set bounds latter on without particular method - * to this end: - * - * @code - * BoundingBox box; // non-initialized box - * box=BoundingBox( arma::vec3("0 1 2"), arma::vec3("4 5 6") ); - * @endcode - * - * Don;t worry about performance, all is inlined. - */ -class BoundingBox { -public: - - /// Currently we set dimension to 3. - static const unsigned int dimension = 3; - /// stabilization parameter - static constexpr double epsilon = 64 * std::numeric_limits::epsilon(); - - - - /** - * Default constructor. - * No initialization of vertices. Be very careful using this. - * One necessary usage is vector of BoundigBox. - */ - BoundingBox() {} - - /** - * Constructor for point box. - */ - BoundingBox(const Point &min) - : min_vertex_(min), max_vertex_(min) - {}; - - /** - * Constructor. - * - * From given minimal and maximal vertex. - */ - BoundingBox(const Point &min, const Point &max) - : min_vertex_(min), max_vertex_(max) - { - ASSERT( min <= max , "Wrong coordinates in constructor."); - }; - - /** - * Constructor. - * - * Make bounding box for set of points. - */ - BoundingBox(const vector &points) { - ASSERT( 0 < points.size(), "" ); - - auto it = points.begin(); - max_vertex_ = min_vertex_ = *it; - ++it; - for(; it != points.end(); ++it) expand( *it ); - } - - - /** - * Set maximum in given axis. - */ - void set_max(unsigned int axis, double max) { - ASSERT(axis < dimension, ""); - ASSERT( min(axis) <= max, ""); - max_vertex_[axis] = max; - } - - /** - * Set minimum on given axis. - */ - void set_min(unsigned int axis, double min) { - ASSERT(axis < dimension, ""); - ASSERT(min <= max(axis), ""); - min_vertex_[axis] = min; - } - - /** - * Return minimal vertex of the bounding box. - */ - const Point &min() const { - return min_vertex_; - } - - /** - * Return maximal vertex of the bounding box. - */ - const Point &max() const { - return max_vertex_; - } - - /** - * Return minimal value on given axis. - */ - double min(unsigned int axis) const { - return min()[axis]; - } - - /** - * Return maximal value on given axis. - */ - double max(unsigned int axis) const { - return max()[axis]; - } - - - /** - * Return size of the box in given axis. - */ - double size(unsigned int axis) const { - return max()[axis] - min()[axis]; - } - - - /** - * Return center of the bounding box. - */ - Point center() const { - return 0.5 * (max_vertex_ + min_vertex_); - } - - /** - * Return center of projection of the bounding box to given @p axis. - * Axis coding is: 0 - axis x, 1 - axis y, 2 - axis z. - */ - double projection_center(unsigned int axis) const{ - ASSERT(axis < dimension, ""); - return 0.5 * (max_vertex_[axis] + min_vertex_[axis]); - } - - /** - * Returns true is the box element contains @p point - * - * @param point Testing point - * @return True if box element contains point - */ - bool contains_point(const Point &point) const - { - for (unsigned int i=0; i b2.max_vertex_[i] + epsilon) || - (b2.min_vertex_[i] > max_vertex_[i] + epsilon ) ) return false; - } - return true; - } - - /** - * Returns true if projection of the box to @p axis is an interval - * less then (with tolerance) to given @p value. - */ - bool projection_lt(unsigned int axis, double value) const - { - return max_vertex_[axis] + epsilon < value; - } - - /** - * Returns true if projection of the box to @p axis is an interval - * greater then (with tolerance) to given @p value. - */ - bool projection_gt(unsigned int axis, double value) const - { - return min_vertex_[axis] - epsilon > value; - } - - /** - * Split box into two boxes along @p axis by the - * plane going through @p splitting_point on the axis. - */ - void split(unsigned int axis, double splitting_point, - BoundingBox &left, BoundingBox &right ) const - { - ASSERT(axis < dimension, ""); - if (min_vertex_[axis] <= splitting_point && splitting_point <= max_vertex_[axis] ) { - left = *this; - right = *this; - left.max_vertex_[axis] = splitting_point; - right.min_vertex_[axis] = splitting_point; - } else { - throw; - //THROW( ExcSplitting() << EI_interval_left(min_vertex_[axis]) - // << EI_interval_right(max_vertex_[axis]) - // << EI_split_point(splitting_point) ); - } - } - - /** - * Expand bounding box to contain also given @p point. - */ - void expand(const Point &point) { - for(unsigned int j=0; j diff[0]) - ? ( diff[2] > diff[1] ? 2 : 1 ) - : ( diff[2] > diff[0] ? 2 : 0 ); - } - - -private: - /// minimal coordinates of bounding box - Point min_vertex_; - /// maximal coordinates of bounding box - Point max_vertex_; -}; - -/// Overloads output operator for box. -inline ostream &operator<<(ostream &stream, const BoundingBox &box) { - stream << "Box(" - << box.min(0) << " " - << box.min(1) << " " - << box.min(2) << "; " - << box.max(0) << " " - << box.max(1) << " " - << box.max(2) << ")"; - return stream; -} - -#endif /* BOX_ELEMENT_HH_ */ diff --git a/src/BIH_tree/common.hh b/src/BIH_tree/common.hh deleted file mode 100644 index f02814a..0000000 --- a/src/BIH_tree/common.hh +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * - * Copyright (C) 2015 Technical University of Liberec. All rights reserved. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License version 3 as published by the - * Free Software Foundation. (http://www.gnu.org/licenses/gpl-3.0.en.html) - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * - * @file bounding_box.hh - * @brief - */ - -#ifndef COMMON_HH_ -#define COMMON_HH_ - -#if DEBUG -#define ASSERT( COND, MESSAGE) - if (! (COND)) { - cout << MESSAGE; - } -#else -#define ASSERT(COND, MESSAGE) -#endif - -#endif diff --git a/src/BIH_tree/makefile b/src/BIH_tree/makefile deleted file mode 100644 index 4b67e22..0000000 --- a/src/BIH_tree/makefile +++ /dev/null @@ -1,5 +0,0 @@ -all: - $(CC) -std=c++11 -c bih_tree.cc - -clean: - rm *.o \ No newline at end of file From 2877357cf3cdd4a1ca2f836cb3054ff60a2aad88 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 31 Aug 2017 19:29:54 +0200 Subject: [PATCH 08/67] Install script. --- README.md | 13 ++++++++++++- bih_install.sh | 17 +++++++++++++++++ requirements.txt | 1 - 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 bih_install.sh diff --git a/README.md b/README.md index a51ec23..3bbb984 100644 --- a/README.md +++ b/README.md @@ -13,4 +13,15 @@ Library focus on fast intersection algorithms for non-degenerate intersections o of small degree (especially quadratic). Requirements ------------- \ No newline at end of file +------------ + +* g++ 4.x or newer +* cmake 3.x + +In order to install BIH package locally for development just run the 'bih_install.sh' script. + +Theory +------ +[Patrikalakis-Maekawa-Cho](http://web.mit.edu/hyperbook/Patrikalakis-Maekawa-Cho/mathe.html) + + \ No newline at end of file diff --git a/bih_install.sh b/bih_install.sh new file mode 100644 index 0000000..3f38ead --- /dev/null +++ b/bih_install.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +echo("Installing BIH locally.") +echo("g++ 4.x and cmake 3.x or newer are assumed.") + +git submodule update --init --recursive +cd external/bih +mkdir build +cd build +cmake .. +make + +BIH_PY_PATH=`pwd` +echo "Modifiing your .bashrc:" +echo "Add "$BIH_PY_PATH" to PYTHONPATH" +echo "PYTHONPATH=\"\$PYTHONPATH:$BIH_PY_PATH\"" + diff --git a/requirements.txt b/requirements.txt index 55b033e..e69de29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +0,0 @@ -pytest \ No newline at end of file From 72715b69b54489084616089862507d7fd119eac8 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 31 Aug 2017 19:36:52 +0200 Subject: [PATCH 09/67] Fix install script, add matlab-isect submodule --- .gitmodules | 6 ++++++ bih_install.sh | 6 +++--- external/bih | 1 + external/matlab-intersections | 1 + 4 files changed, 11 insertions(+), 3 deletions(-) mode change 100644 => 100755 bih_install.sh create mode 160000 external/bih create mode 160000 external/matlab-intersections diff --git a/.gitmodules b/.gitmodules index e69de29..4d1b072 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "external/bih"] + path = external/bih + url = git@github.com:flow123d/bih.git +[submodule "external/matlab-intersections"] + path = external/matlab-intersections + url = git@github.com:jirikopal/Intersections.git diff --git a/bih_install.sh b/bih_install.sh old mode 100644 new mode 100755 index 3f38ead..d293b8a --- a/bih_install.sh +++ b/bih_install.sh @@ -1,7 +1,7 @@ #!/bin/bash -echo("Installing BIH locally.") -echo("g++ 4.x and cmake 3.x or newer are assumed.") +echo "Installing BIH locally." +echo "g++ 4.x and cmake 3.x or newer are assumed." git submodule update --init --recursive cd external/bih @@ -13,5 +13,5 @@ make BIH_PY_PATH=`pwd` echo "Modifiing your .bashrc:" echo "Add "$BIH_PY_PATH" to PYTHONPATH" -echo "PYTHONPATH=\"\$PYTHONPATH:$BIH_PY_PATH\"" +echo "PYTHONPATH=\"\$PYTHONPATH:$BIH_PY_PATH\"" >>"${HOME}/.bashrc" diff --git a/external/bih b/external/bih new file mode 160000 index 0000000..b549511 --- /dev/null +++ b/external/bih @@ -0,0 +1 @@ +Subproject commit b54951182c73698c0bbafd37aa30e334c2ed3900 diff --git a/external/matlab-intersections b/external/matlab-intersections new file mode 160000 index 0000000..a10a099 --- /dev/null +++ b/external/matlab-intersections @@ -0,0 +1 @@ +Subproject commit a10a099c06d3033de3e5cb52e0726449fb8ac42a From b0e6a4123db253229362799a4638f9effb7a275d Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 31 Aug 2017 19:38:02 +0200 Subject: [PATCH 10/67] Remove old matlab implementation. --- matlab/basetestvec.m | 21 --- matlab/boundary_point.m | 55 ------ matlab/bounding_boxes_intersection.m | 55 ------ matlab/build_LS_matrix.m | 14 -- matlab/build_reg_matrix.m | 82 --------- matlab/compute_control_points.m | 23 --- matlab/compute_grid_lines.m | 21 --- matlab/compute_patch_edges.m | 23 --- matlab/find_int.m | 59 ------- matlab/get_errors.m | 14 -- matlab/get_knot_vector.m | 10 -- matlab/getpoints.m | 23 --- matlab/getpoints2.m | 23 --- matlab/interpolate_intersections.m | 255 --------------------------- matlab/intersections.m | 192 -------------------- matlab/patch_patch_intersection.m | 71 -------- matlab/plotresultsvec.m | 73 -------- matlab/rangetest.m | 51 ------ matlab/solve.m | 38 ---- matlab/spline_surf_vec.m | 160 ----------------- matlab/splinebasevec.m | 80 --------- matlab/swaplines.m | 8 - matlab/transform.m | 55 ------ matlab/vec2mat.m | 11 -- 24 files changed, 1417 deletions(-) delete mode 100644 matlab/basetestvec.m delete mode 100644 matlab/boundary_point.m delete mode 100644 matlab/bounding_boxes_intersection.m delete mode 100644 matlab/build_LS_matrix.m delete mode 100644 matlab/build_reg_matrix.m delete mode 100644 matlab/compute_control_points.m delete mode 100644 matlab/compute_grid_lines.m delete mode 100644 matlab/compute_patch_edges.m delete mode 100644 matlab/find_int.m delete mode 100644 matlab/get_errors.m delete mode 100644 matlab/get_knot_vector.m delete mode 100644 matlab/getpoints.m delete mode 100644 matlab/getpoints2.m delete mode 100644 matlab/interpolate_intersections.m delete mode 100644 matlab/intersections.m delete mode 100644 matlab/patch_patch_intersection.m delete mode 100644 matlab/plotresultsvec.m delete mode 100644 matlab/rangetest.m delete mode 100644 matlab/solve.m delete mode 100644 matlab/spline_surf_vec.m delete mode 100644 matlab/splinebasevec.m delete mode 100644 matlab/swaplines.m delete mode 100644 matlab/transform.m delete mode 100644 matlab/vec2mat.m diff --git a/matlab/basetestvec.m b/matlab/basetestvec.m deleted file mode 100644 index 64ec858..0000000 --- a/matlab/basetestvec.m +++ /dev/null @@ -1,21 +0,0 @@ -function [ ] = basetestvec( knots ) -n = 1000; -n_basf = length(knots)-3; -one = ones(n_basf,1); -y = sparse(zeros(n,n_basf)); -for i=1:n - y(i,:) = splinebasevec(knots,min(knots) + max(knots)*(i-1)/(n-1),0); -end -% spy(y) -% pause -x = linspace(min(knots),max(knots),n); -x -y -%spy(y) -plot(x,y) -%figure -if norm(y*one - ones(n,1))< n_basf*eps - disp('OK') -end -end - diff --git a/matlab/boundary_point.m b/matlab/boundary_point.m deleted file mode 100644 index 353766d..0000000 --- a/matlab/boundary_point.m +++ /dev/null @@ -1,55 +0,0 @@ -function [ bool ] = boundary_point(uv,iujv,u_knots,v_knots) -bool = [-1,-1]; - -uint = [u_knots(iujv(1)+2), u_knots(iujv(1)+3)]; -vint = [v_knots(iujv(2)+2), v_knots(iujv(2)+3)]; -uv; - - -if uint(1) == 0 - if abs(uv(1)-0)= mnXs) && (mnX <= mxXs)) || ... - ((mxX >= mnXs) && (mxX <= mxXs)) ) == 1 - - if (( (mnY >= mnYs) && (mnY <= mxYs)) || ... - ((mxY >= mnYs) && (mxY <= mxYs)) ) == 1 - if (( (mnZ >= mnZs) && (mnZ <= mxZs)) || ... - ((mxZ >= mnZs) && (mxZ <= mxZs)) ) == 1 - - - n_its(k,l) = n_its(k,l) + 1; - its(k,l,n_its(k,l)) = sp_i; - - end - end - end - end - end - end -end - - -end - diff --git a/matlab/build_LS_matrix.m b/matlab/build_LS_matrix.m deleted file mode 100644 index fcd2360..0000000 --- a/matlab/build_LS_matrix.m +++ /dev/null @@ -1,14 +0,0 @@ -function [ B,Interv ] = build_LS_matrix( u_knots,v_knots, Xp ) -u_n_basf = length(u_knots)-3; -v_n_basf = length(v_knots)-3; -[np k] = size(Xp); % k unused -B =spalloc(np,u_n_basf*v_n_basf,9*np); -Interv = zeros(np,2); -for j = 1:np - [uf, k] = splinebasevec(u_knots,Xp(j,1),0); - [vf, i] = splinebasevec(v_knots,Xp(j,2),0); - Interv(j,:) = [k,i]; - B(j,:) = kron(vf',uf'); -end -end - diff --git a/matlab/build_reg_matrix.m b/matlab/build_reg_matrix.m deleted file mode 100644 index 6f79078..0000000 --- a/matlab/build_reg_matrix.m +++ /dev/null @@ -1,82 +0,0 @@ -function [ A ] = build_reg_matrix( u_knots,v_knots, P0,P1,P2,P3,nnzA ) - -u_n_basf = length(u_knots)-3; -v_n_basf = length(v_knots)-3; -u_n_inter = length(u_knots) - 5 -v_n_inter = length(v_knots) - 5 - - a = P3 - P2; - b = P0 - P1; - c = P1 - P2; - d = P0 - P3; - -A =spalloc(u_n_basf*v_n_basf,u_n_basf*v_n_basf,nnzA); - -% Qpoints = [0 (1/2 - 1/sqrt(20)) (1/2 + 1/sqrt(20)) 1]; -% weights = [1/6 5/6 5/6 1/6]; - -Qpoints = [-0.90618 -0.538469 0 0.538469 0.90618] /2 + 0.5; -weights = [0.236927 0.478629 0.568889 0.478629 0.236927]; - - - -n_points = length(Qpoints); - -u_point_val = spalloc(u_n_basf,u_n_inter*n_points,u_n_inter*n_points*3); -ud_point_val = spalloc(u_n_basf,u_n_inter*n_points,u_n_inter*n_points*3); -q_u_point = zeros(u_n_inter*n_points,1); - -n = 0; -for i = 1:u_n_inter - us = u_knots(i+2); - uil = u_knots(i+3)- u_knots(i+2); - for k = 1:n_points - up = us + uil*Qpoints(k); - n = n+1; - q_u_point(n) = up; - u_point_val(:,n) = splinebasevec(u_knots,up,0,i); - ud_point_val(:,n) = splinebasevec(u_knots,up,1,i); - end -end - -%%% - -v_point_val = spalloc(v_n_basf,v_n_inter*n_points,v_n_inter*n_points*3); -vd_point_val = spalloc(v_n_basf,v_n_inter*n_points,v_n_inter*n_points*3); -q_v_point = zeros(v_n_inter*n_points,1); - -n = 0; -for i = 1:v_n_inter - vs = v_knots(i+2); - vil = v_knots(i+3)- v_knots(i+2); - for k = 1:n_points - vp = vs + vil*Qpoints(k); - n = n+1; - q_v_point(n) = vp; - v_point_val(:,n) = splinebasevec(v_knots,vp,0,i); - vd_point_val(:,n) = splinebasevec(v_knots,vp,1,i); - end -end - -%%% - -for i= 1:v_n_inter - for k = 1:n_points - v_point = v_point_val(:,(i-1)*n_points+k); - vd_point =vd_point_val(:,(i-1)*n_points+k); - for l =1:u_n_inter - for m = 1:n_points - u_point = u_point_val(:,(l-1)*n_points+m); - ud_point = ud_point_val(:,(l-1)*n_points+m); - vd = kron(vd_point,u_point); - ud = kron(v_point,ud_point); - v = q_v_point((i-1)*n_points +k); - u = q_u_point((l-1)*n_points +m); - J = det([v * a + (1 - v) * b, u * c + (1 - u) * d]); - A = A + J * weights(m)*weights(k)*(kron(ud,ud') +kron(vd,vd')); - end - end - end -end - -end diff --git a/matlab/compute_control_points.m b/matlab/compute_control_points.m deleted file mode 100644 index e93861b..0000000 --- a/matlab/compute_control_points.m +++ /dev/null @@ -1,23 +0,0 @@ -function [ X_coor,Y_coor ] = compute_control_points( u_knots, v_knots, P0,P1,P2,P3) -%UNTITLED Summary of this function goes here -% Detailed explanation goes here -u_n_basf = length(u_knots)-3; -v_n_basf = length(v_knots)-3; - - -X_coor = zeros(u_n_basf,v_n_basf); -Y_coor = zeros(u_n_basf,v_n_basf); - -for i =1:u_n_basf - u = (u_knots(i+1)+u_knots(i+2))/2; - for j =1:v_n_basf - v = (v_knots(j+1)+v_knots(j+2))/2; - P = u * (v * P2 + (1-v) * P1) + (1-u) * ( v * P3 + (1-v) * P0) ; - X_coor(i,j) = P(1); - Y_coor(i,j) = P(2); - end -end - - -end - diff --git a/matlab/compute_grid_lines.m b/matlab/compute_grid_lines.m deleted file mode 100644 index 41deea4..0000000 --- a/matlab/compute_grid_lines.m +++ /dev/null @@ -1,21 +0,0 @@ -function [ GX,GY ] = compute_grid_lines(u_knots,v_knots, P0,P1,P2,P3 ) - -u_n_grid = length(u_knots)-2; -v_n_grid = length(v_knots)-2; - -GX = zeros(u_n_grid,v_n_grid); -GY = zeros(u_n_grid,v_n_grid); - - -for i =1:u_n_grid - u = u_knots(i+2); - for j =1:v_n_grid - v = v_knots(j+2); - P = u * (v * P2 + (1-v) * P1) + (1-u) * ( v * P3 + (1-v) * P0) ; - GX(i,j) = P(1); - GY(i,j) = P(2); - end -end - -end - diff --git a/matlab/compute_patch_edges.m b/matlab/compute_patch_edges.m deleted file mode 100644 index ff068f3..0000000 --- a/matlab/compute_patch_edges.m +++ /dev/null @@ -1,23 +0,0 @@ -function [ X_coor,Y_coor ] = compute_patch_edges( u_knots, v_knots, P0,P1,P2,P3) -%UNTITLED Summary of this function goes here -% Detailed explanation goes here -u_n_bound = length(u_knots)-4; -v_n_bound = length(v_knots)-4; - - -X_coor = zeros(u_n_bound,v_n_bound); -Y_coor = zeros(u_n_bound,v_n_bound); - -for i =1:u_n_bound - u = u_knots(i+2); - for j =1:v_n_bound - v = v_knots(j+2); - P = u * (v * P2 + (1-v) * P1) + (1-u) * ( v * P3 + (1-v) * P0) ; - X_coor(i,j) = P(1); - Y_coor(i,j) = P(2); - end -end - - -end - diff --git a/matlab/find_int.m b/matlab/find_int.m deleted file mode 100644 index bfdd725..0000000 --- a/matlab/find_int.m +++ /dev/null @@ -1,59 +0,0 @@ -function [ k ] = find_int( T,t ) - -n = length(T); -mn = 3; -mx = n-2; -est = 3+floor(t*(n -5)); - -if t == T(mn) - k = mn-2; -elseif t == T(mx) - k = mx-3; -end - -if t>= T(est) - mn =est; -elseif t < T(est) % = - mx = est; -end - -s = max(ceil(log2(mx-mn)),1); - -for p=1:s+1 - if t < T(mn+1) % = - k = mn-2; - break - - elseif t>T(mx-1) % = - k = mx-3; - break - end - mid = mn + floor((mx-mn)/2); - %mn - %mx - if mid ~= mn - if t< T(mid) - mx = mid; - elseif t>= T(mid) - mn = mid; - end - else - k = mn-2; - break - end -end - -% T -% [T(mn) T(mx)] -% if (t~=0) && (t~=1) -% if (t>=T(k) && t<=T(k+1) ) -% [T T >t] -% t -% k -% disp('problem') -% pause -% end -% end - -end - diff --git a/matlab/get_errors.m b/matlab/get_errors.m deleted file mode 100644 index 266a59a..0000000 --- a/matlab/get_errors.m +++ /dev/null @@ -1,14 +0,0 @@ -function [ Err ] = get_errors(err,Interv,u_n,v_n ) - -n = length(err); -Err = zeros(v_n-2,u_n-2); -for j =1:n - Err(Interv(j,2),Interv(j,1)) = Err(Interv(j,2),Interv(j,1)) + err(j); -end - -%UNTITLED2 Summary of this function goes here -% Detailed explanation goes here - - -end - diff --git a/matlab/get_knot_vector.m b/matlab/get_knot_vector.m deleted file mode 100644 index a7bc984..0000000 --- a/matlab/get_knot_vector.m +++ /dev/null @@ -1,10 +0,0 @@ -function [ knots ] = get_knot_vector( n_basf ) - - -knots = zeros(n_basf+3,1); -knots(3:n_basf+1) = linspace(0,1,n_basf-1); -knots(n_basf+2:n_basf+3) = 1; - - -end - diff --git a/matlab/getpoints.m b/matlab/getpoints.m deleted file mode 100644 index 80211c3..0000000 --- a/matlab/getpoints.m +++ /dev/null @@ -1,23 +0,0 @@ -function [ X ] = getpoints() - -nu = 36; -nv = 26; -h = 0; -X = zeros(nu*nv,4); - -for i=1:nu - for j = 1:nv - h = h+1; - x = 2*(i-1)/(nu-1); - y = 2*(j-1)/(nv-1); - X(h,1) = x; - X(h,2) = y; - %X(h,3) = 0.3*cos(3*pi*exp(-x))*cos(3*pi*y^1.2)*cos(3*pi*sqrt(y^2+x^2)); - %X(h,3) = x^2+y^2 + 1; - X(h,3) = (2-x^2)+(2-y^2) + 1; - X(h,4) = 1; - end -end - -end - diff --git a/matlab/getpoints2.m b/matlab/getpoints2.m deleted file mode 100644 index 3f23f9a..0000000 --- a/matlab/getpoints2.m +++ /dev/null @@ -1,23 +0,0 @@ -function [ X ] = getpoints2() - -nu = 20; -nv = 20; -h = 0; -X = zeros(nu*nv,4); - -for i=1:nu - for j = 1:nv - h = h+1; - x = 2*(i-1)/(nu-1); - y = 2*(j-1)/(nv-1); - X(h,1) = x; - X(h,2) = y; - %X(h,3) = cos(10*pi*x)*cos(3*pi*y)+exp(2*sin(x*y)); - %X(h,3) = cos(3*pi*exp(-x))*cos(3*pi*y^2)+exp(2*sin(x*y)); - X(h,3) = 3*x*y; %- 0.5*rand(1,1) +10;%+exp(2*sin(x*y)); + 0.01*exp(x*y)+ - X(h,4) = 1; - end -end - -end - diff --git a/matlab/interpolate_intersections.m b/matlab/interpolate_intersections.m deleted file mode 100644 index 6089bbb..0000000 --- a/matlab/interpolate_intersections.m +++ /dev/null @@ -1,255 +0,0 @@ -%close all - -spline_surf_vec -intersections - - plot3(GXs,GYs,5*ones(us_n_basf+1,vs_n_basf+1)); - hold on - plot3(GYs,GXs,5*ones(us_n_basf+1,vs_n_basf+1)); - - for i=1:m - plot3(point(i,5),point(i,6),point(i,7),'.','MarkerSize',50); - end - -%pointx = point; -pointb = point; - - -%%% sort intersections by patches (1st patch) - - sp_i = (pointb(:,10)-1)*(us_n_intervs-1) + pointb(:,11); - -[ssp_i idx] = sort(sp_i); - -m = length(sp_i); %m-out - - - -pointb = pointb(idx,:); - -%%% Compute intersectioned patches -patch_int(1,2) = 1; -patch_int(1,1) = ssp_i(1); -%a = sp_i(1); -different = 1; -for i =2:m - if ssp_i(i) == patch_int(different,1)%a - patch_int(different,2) = patch_int(different,2) +1; - continue - else - %a = sp_i(i); - different = different +1; - patch_int(different,1) = ssp_i(i); - patch_int(different,2) = 1; - end -end - - -different; - - - -%return - - - -coinf = zeros(m,2); - - -a = 1; %?? - -%%% detect intersection point types -% -1 - interion -% 0 - global boundary -% 1 - patch boundary - -for j=1:m - coi = boundary_point(pointb(j,3:4),pointb(j,10:11),us_knots,vs_knots); - coinf(j,:) = coi; % type of interrsection (u,v) % 1D -end - -out = zeros(different,1); -point_type = zeros(m,1); % 2D - -offset = 0; -for j=1:different - for i = 1:patch_int(j,2) - - if coinf(i+offset,1) > -1 || coinf(i+offset,2) > -1 % number of boundary points for patch - out(j) = out(j) +1; - end - - % define intersection type - if coinf(i+offset,1) == -1 && coinf(i+offset,2) == -1 % internal - point_type(i+offset) = -1; - elseif coinf(i+offset,1) == 0 || coinf(i+offset,2) == 0 % global boundary - point_type(i+offset) = 0; - elseif coinf(i+offset,1) == 1 || coinf(i+offset,2) == 1 % patch internal boundary - point_type(i+offset) = 1; - end - - - - end - offset = offset + patch_int(j,2); -end - - -% number of outputs - - - -% % Sort points in patch -% offset = 0; -% for j=1:different -% -% -% patch_points= offset:offset+patch_int(j,2); -% -% boundg = find(point_type(patch_points) == 0) -% boundi = find(point_type(patch_points) == 1) -% bound = [boundg , boundi]; -% -% l = lenght(bound) -% if l >2 -% disp('more then 2 boundary points') -% end -% -% dist = zeros(patch_int(j,2)); -% -% for k = 1: offset+patch_int(j,2) -% for i = 1: offset+patch_int(j,2) -% -% dist(i) = norm(pointsb(patch_points(k),1:2)- pointsb(patch_points(i),1:2)) -% i+offset -% -% end -% -% end -% offset = offset + patch_int(j,2); -% end -% - -% Sort points in surface (create components) - - offset = 0; - bound = find(point_type == 0); - pointb = [pointb coinf point_type]; - [a,b] = size(pointb); - splinepoint = pointb; - splinepoint = swaplines(splinepoint,1,bound(1)); - - for j=1:m-1 - dist = 1/eps * ones(m,1); - for k=j+1:m - dist(k) = norm(splinepoint(j,1:2) - splinepoint(k,1:2)); - end - [a b] = min(dist); - splinepoint = swaplines(splinepoint,b,j+1); - end - - % Sort intersection on patches - boundg = find(point_type == 0); - boundl = find(point_type == 1); - bound = [boundg;boundl] - -% offset =0; -% for k=1:different -% boundg = find(point_type(1+offset:patch_int(different,2)+offset)== 0); -% boundl = find(point_type(1+offset:patch_int(different,2)+offset)== 1); -% bound = [boundg;boundl] -% %pause -% pointb = swaplines(pointb,bound(1)+offset,1+offset) -% for l=1:patch_int(k,2)-1 -% dist = 1/eps * ones(patch_int(k,2)-l,1); -% for j=1:patch_int(k,2)-l -% dist(j) = norm(splinepoint(l,1:2) - splinepoint(l+j,1:2)); -% end -% [a b] = min(dist); -% splinepoint = swaplines(splinepoint,b+offset,l+1+offset); -% -% end -% offset = offset + patch_int(k,2) -% end - - - - - %%%% Remove duplicite points () - - matrixpoints = ones(m,1); - a = 0; - for i=1:m -% if splinepoint(i,14) == 1 -% a = 1; -% end - if splinepoint(i,14)*a == 1 - matrixpoints(i) = 0; - a = 0; - elseif splinepoint(i,14) == 1 - a =1; - else - a = 0; - end - end - - -% Interpolate points -% -% -% pointb = pointb(1:m-out,:) -% -% mm = m - out; -% -% %return -% -mr = sum(matrixpoints); -XYZ = zeros(mr,3); -deltas = zeros(mr,1); -j = 0; - -for i=1:m - if matrixpoints(i)==1 - j = j+1; - XYZ(j,:) =splinepoint(i,5:7); - if j~=1 - deltas(j) = norm(XYZ(j,:)-XYZ(j-1,:)); - end - end -end - -Deltas = zeros(mr,1); - -for i=2:mr - Deltas(i)=sum(deltas(1:i)); -end - -pointspersegment=4; -nbasf = ceil(mr/pointspersegment); -t_knots = get_knot_vector(nbasf+2); -t = Deltas/Deltas(mr); -X = zeros(mr,nbasf+2); -for i=1:mr - - [tf, ~] = splinebasevec(t_knots,t(i),0); - X(i,:) = tf; -end - -sol = X\XYZ; -X*sol - XYZ; - -%X'*X\X'*XYZ - -ns = 100; -td = linspace(0,1,ns); -for i=1:ns - PT(i,:)=splinebasevec(t_knots,td(i),0)'*sol; - -end -plot3(PT(:,1),PT(:,2),PT(:,3),'r','LineWidth',3); -% -% -% -% -% -% \ No newline at end of file diff --git a/matlab/intersections.m b/matlab/intersections.m deleted file mode 100644 index 82bf21b..0000000 --- a/matlab/intersections.m +++ /dev/null @@ -1,192 +0,0 @@ - -% spline_surf_vec - %close all -% % % -% plotresultsvec(u_knots, v_knots,P0,P1,P2,P3,X,z,Err) -% hold on -% plotresultsvec(us_knots, vs_knots,P0s,P1s,P2s,P3s,Xs,zs,Errs) -hold on - - -%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Compute intersections -%%%%%%%%%%%%%%%%%%%%%%%%% - -% u_n_basf = length(u_knots)-3; -% v_n_basf = length(v_knots)-3; -% us_n_basf = length(us_knots)-3; -% vs_n_basf = length(vs_knots)-3; - -u_n_intervs = u_n_basf - 2; -v_n_intervs = v_n_basf - 2; -us_n_intervs = us_n_basf - 2; -vs_n_intervs = vs_n_basf - 2; - -u_n_grid = u_n_basf+1; -v_n_grid = v_n_basf+1; -us_n_grid = us_n_basf+1; -vs_n_grid = vs_n_basf+1; - -% u_n_intervs = u_n_basf - 2; -% v_n_intervs = v_n_basf - 2; -% us_n_intervs = us_n_basf - 2; -% vs_n_intervs = vs_n_basf - 2; - -% Compute X & Y grid intersections - -%%% Grid Boundary -[ GX,GY ] = compute_grid_lines(u_knots,v_knots, P0,P1,P2,P3 ); -[ GXs,GYs ] = compute_grid_lines(us_knots,vs_knots, P0s,P1s,P2s,P3s ); - -%%% Grid centers -[ X_coor,Y_coor] = compute_control_points( u_knots, v_knots, P0,P1,P2,P3); -[ Xs_coor,Ys_coor] = compute_control_points( us_knots, vs_knots, P0s,P1s,P2s,P3s); - -%%% Compute Bounding Boxes -Z_coor = vec2mat( z,u_n_basf,v_n_basf ); -Zs_coor = vec2mat( zs,us_n_basf,vs_n_basf ); - -[patch_bound_X,patch_bound_Y] = compute_patch_edges( u_knots, v_knots, P0,P1,P2,P3); -[patch_bound_Xs,patch_bound_Ys] = compute_patch_edges( us_knots, vs_knots, P0s,P1s,P2s,P3s); - -% [ BB_X,BB_Y,BB_Z ] = compute_bounding_box( X_coor,Y_coor,Z_coor, u_n_intervs,v_n_intervs); -% [ BB_Xs,BB_Ys,BB_Zs ] = compute_bounding_box( Xs_coor,Ys_coor,Zs_coor, us_n_intervs,vs_n_intervs); - -%%% Bonding boxes intersections -[isec,n_isec] = bounding_boxes_intersection( patch_bound_X,patch_bound_Y,Z_coor,patch_bound_Xs,patch_bound_Ys,Zs_coor); - - -x = X_coor(:); -y = Y_coor(:); -xs = Xs_coor(:); -ys = Ys_coor(:); - -Xs = [xs ys zs]; - -X = [x y z]; -nt = 6; % number of main lines -n_points = 0; -ninter = zeros(u_n_intervs,v_n_intervs); -for k=1:u_n_intervs - us1 = u_knots(k+2); - ue1 = u_knots(k+3); - u1_c =(us1 + ue1)/2; - ui = [us1 ue1] ; - for l=1:v_n_intervs - vs1 = v_knots(l+2); - ve1 = v_knots(l+3); - v1_c = (vs1 + ve1)/2; - vi = [ vs1 ve1]; - s=0; - if n_isec(k,l) ~= 0 - for p =1: n_isec(k,l) - - m = ceil(isec(k,l,p) / us_n_intervs); - o = isec(k,l,p) - (m-1)*us_n_intervs; - sp_i = (m-1)*(us_n_intervs) + o; - % v2 fixed - u2i = [us_knots(m+2) us_knots(m+3)]; - v_knot = linspace(vs_knots(o+2),vs_knots(o+3),nt); - for h =1:length(v_knot) - u2_c = (us_knots(m+2) + us_knots(m+3))/2; - v2i = v_knot(h); - nit = 10; - uvu2v2 = [u1_c;v1_c;u2_c]; - [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); - if conv ~= 0 - s = s+1; - plot3(ptl(1),ptl(2),ptl(3),'.','MarkerSize',50) - n_points = n_points +1; - point(n_points,:) = [uvu2v2',v_knot(h),ptl(1),ptl(2),ptl(3),k,l,m,o]; - end - end - - % u2 fixed - v2i = [vs_knots(o+2) vs_knots(o+3)]; - u_knot = linspace(us_knots(m+2),us_knots(m+3),nt); - for h =1:length(u_knot) - v2_c = (vs_knots(o+2) + vs_knots(o+3))/2; - u2i = u_knot(h); - nit = 10; - uvu2v2 = [u1_c;v1_c;v2_c]; - [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); - if conv ~= 0 - s = s+1; - plot3(ptl(1),ptl(2),ptl(3),'.','MarkerSize',50) - n_points = n_points +1; - point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o]; - end - end - ninter(k,l) = s; - end - end - end -end - - ninter -%[isec,n_isec] = bounding_boxes_intersection2( patch_bound_X,patch_bound_Y,Z_coor,patch_bound_Xs,patch_bound_Ys,Zs_coor); -%[isec2,n_isec2] = bounding_boxes_intersection2( patch_bound_Xs,patch_bound_Ys,Zs_coor,patch_bound_X,patch_bound_Y,Z_coor); - -% for m=1:us_n_intervs -% us2 = us_knots(m+2); -% ue2 = us_knots(m+3); -% u2_c = (us2+ue2)/2; -% ui2 = [us2 ue2] ; -% for o=1:vs_n_intervs -% vs2 = vs_knots(o+2); -% ve2 = vs_knots(o+3); -% v2_c = (vs2+ve2)/2; -% vi2 = [ vs2 ve2]; -% s = 0; -% if n_isec2(m,o) ~= 0 -% for p =1: n_isec2(m,o) -% k = ceil(isec2(m,o,p) / u_n_intervs); -% l = isec2(m,o,p) - (k-1)*u_n_intervs; -% sp_i = (k-1)*(u_n_intervs) + l; -% % [sp_i isec2(m,o,p)] -% % pause -% t0 = 0.5; -% -% % v fixed -% v_knot = linspace(v_knots(l+2),v_knots(l+3),nt); -% for h =1:length(v_knot) -% u_val = linspace(u_knots(k+2),u_knots(k+3),3); -% v_val = v_knot(h); -% XYZ = get_span(u_knots,v_knots,X,u_val,v_val); -% nit = 6; -% uvt0 = [u2_c;v2_c;t0]; -% [ uvtl, ptl ,conv] = compute_initial_guess( uvt0, ui2,vi2,us_knots, vs_knots, Xs, XYZ, nit,m,o); -% if conv ~= 0 -% s = s+1; -% plot3(ptl(1),ptl(2),ptl(3),'.','MarkerSize',50) -% end -% end -% -% % u fixed -% u_knot = linspace(u_knots(k+2),u_knots(k+3),nt); -% for h =1:length(u_knot) -% u_val = u_knot(h); -% v_val = linspace(v_knots(l+2),v_knots(l+3),3); -% XYZ = get_span(u_knots,v_knots,X,u_val,v_val); -% nit = 6; -% uvt0 = [u2_c;v2_c;t0]; -% [ uvtl, ptl ,conv] = compute_initial_guess( uvt0, ui2,vi2,us_knots, vs_knots, Xs, XYZ, nit,m,o); -% if conv ~= 0 -% s = s+1; -% plot3(ptl(1),ptl(2),ptl(3),'.','MarkerSize',50) -% end -% end -% -% ninter(m,o) = ninter(m,o) + s; -% end -% end -% end -% end - -%t = toc - -%cas = sum(sum(ninter))/t - -ninter - plot3(GXs,GYs,5*ones(us_n_basf+1,vs_n_basf+1)) - plot3(GYs,GXs,5*ones(us_n_basf+1,vs_n_basf+1)) diff --git a/matlab/patch_patch_intersection.m b/matlab/patch_patch_intersection.m deleted file mode 100644 index b39ea02..0000000 --- a/matlab/patch_patch_intersection.m +++ /dev/null @@ -1,71 +0,0 @@ -function [ uvt,pt,conv ] = patch_patch_intersection( uvt, ui,vi,u2i,v2i,u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l) - -pt =zeros(3,1); -conv =0; -tol = 1e-4; -tol2 = 1e-6; - -if length(u2i) == 1 - [u2f, ~] = splinebasevec(us_knots,u2i,0,m); -end -if length(v2i) == 1 - [v2f, ~] = splinebasevec(vs_knots,v2i,0,o); -end - - -for i=1:nit - [uf, ~] = splinebasevec(u_knots,uvt(1),0,k); - [vf, ~] = splinebasevec(v_knots,uvt(2),0,l); - [ufd, ~] = splinebasevec(u_knots,uvt(1),1,k); - [vfd, ~] = splinebasevec(v_knots,uvt(2),1,l); - - if length(u2i) == 1 - [v2f, ~] = splinebasevec(vs_knots,uvt(3),0,o); - [v2fd, ~] = splinebasevec(vs_knots,uvt(3),1,o); - dXYZp2 = (kron(v2fd',u2f')*Xs)'; - end - if length(v2i) == 1 - [u2f, ~] = splinebasevec(us_knots,uvt(3),0,m); - [u2fd, ~] = splinebasevec(us_knots,uvt(3),1,m); - dXYZp2 = (kron(v2f',u2fd')*Xs)'; - end - - dXYZu1 = (kron(vf',ufd')*X)'; % - dXYZv1 = (kron(vfd',uf')*X)'; % - - J = [dXYZu1 dXYZv1 -dXYZp2]; - - deltaXYZ = (kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)'; - uvt = uvt- J\deltaXYZ; - %if i~=nit - [test,uvt] = rangetest(uvt,ui,vi,u2i,v2i,0.0); - %end -end - -[test,uvt] = rangetest(uvt,ui,vi,u2i,v2i,tol2); -if test == 1 - [uf, ~] = splinebasevec(u_knots,uvt(1),0,k); - [vf, ~] = splinebasevec(v_knots,uvt(2),0,l); - - if length(u2i) == 1 - [v2f, ~] = splinebasevec(vs_knots,uvt(3),0,o); - end - if length(v2i) == 1 - [u2f, ~] = splinebasevec(us_knots,uvt(3),0,m); - end - - deltaXYZ = (kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)'; - dist = norm(deltaXYZ); -end - -if test == 1 - if dist <= tol - pt =kron(vf',uf')*X; - conv =1; - end -else - uvt = zeros(3,1); -end - -end - diff --git a/matlab/plotresultsvec.m b/matlab/plotresultsvec.m deleted file mode 100644 index 7d510b8..0000000 --- a/matlab/plotresultsvec.m +++ /dev/null @@ -1,73 +0,0 @@ -function [ ] = plotresultsvec( u_knots, v_knots,P0,P1,P2,P3,X,z, Err) - -[np k] = size(X); - -u_n_basf = length(u_knots)-3; -v_n_basf = length(v_knots)-3; - - -nu = 60; -nv = 60; - -Zsurf = zeros(nu,nv); -Xsurf = zeros(nu,nv); -Ysurf = zeros(nu,nv); - -% Compute X & Y control points - - -[ X_coor,Y_coor] = compute_control_points( u_knots, v_knots, P0,P1,P2,P3); - -x = X_coor(:); -y = Y_coor(:); - - -% Compute fine grid in X & Y & Z for draw - -uf = spalloc(u_n_basf,nu,3*nu); -vf = spalloc(v_n_basf,nv,3*nv); - -for j =1:nu - u = (j-1)/(nu-1); - uf(:,j) = splinebasevec(u_knots,u,0); -end - -for k =1:nv - v = (k-1)/(nv-1); - vf(:,k) = splinebasevec(v_knots,v,0); -end - -for k =1:nv - Zsurf(:,k) = kron(vf',uf(:,k)') * z; - Xsurf(:,k) = kron(vf',uf(:,k)') * x; - Ysurf(:,k) = kron(vf',uf(:,k)') * y; -end - - - -surf(Xsurf,Ysurf,Zsurf); - - -%%plot original points - -% hold on -% for k=1:np -% if X(k,4) ~=0 -% plot3(X(k,1),X(k,2),X(k,3),'.k','MarkerSize',25); -% end -% end - -%% - -% hold off -% -% figure -% -% Xsurferr = kron(xv(2:u_n_basf-1)',ones(v_n_basf-2,1)); -% Ysurferr = kron(yv(2:v_n_basf-1),ones(u_n_basf-2,1)'); -% -% -% surf(Xsurferr,Ysurferr,Err); - -end - diff --git a/matlab/rangetest.m b/matlab/rangetest.m deleted file mode 100644 index af8ef7c..0000000 --- a/matlab/rangetest.m +++ /dev/null @@ -1,51 +0,0 @@ -function [ test, uvt ] = rangetest( uvt,ui,vi,u2i,v2i,tol ) - - -test = 0; - -du = [uvt(1)-ui(1), ui(2)- uvt(1)]; -dv = [uvt(2)-vi(1), vi(2)- uvt(2)]; - -if length(v2i) == 1 -d2p = [uvt(3)-u2i(1), u2i(2)- uvt(3)]; -pi = u2i; -end - -if length(u2i) == 1 -d2p = [uvt(3)-v2i(1), v2i(2)- uvt(3)]; -pi = v2i; -end - -%dv = [uvt(2)-vi(1), vi(2)- uvt(2)]; - - -for i =1:2 - if (du(i) < -tol) - uvt(1) = ui(i); - end -end - -for i =1:2 - if (dv(i) < -tol) - uvt(2) = vi(i); - end -end - -for i =1:2 - if (d2p(i) < -tol) - uvt(3) = pi(i); - end -end - - -if (uvt(1)>=ui(1)) && (uvt(1)<=ui(2)) - if (uvt(2)>=vi(1)) && (uvt(2)<=vi(2)) - if ((uvt(3)>=pi(1)) && (uvt(3)<=pi(2))) - test = 1; - end - end -end - - -end - diff --git a/matlab/solve.m b/matlab/solve.m deleted file mode 100644 index 7f1d395..0000000 --- a/matlab/solve.m +++ /dev/null @@ -1,38 +0,0 @@ -function [ Xp ] = solve( Xp,P0,P1,P2,P3 ) - -[np k] = size(Xp); -Pi = [P0 P1 P2 P3]; - -%%% Compute local coordinates and drop all - -A = P3 - P2; -B = P0 - P1; -C = P1 - P2; -D = P0 - P3; - -for j=1:np - - P = [Xp(j,5); Xp(j,6)]; - - u = Xp(j,1); - v = Xp(j,2); - - uv = [u;v]; - - iters = 10; - - for i = 1:iters - u = uv(1); - v = uv(2); - - % J = [ v * A(1) + (1-v) * B(1), u * C(1) + (1 - u) * D(1); - % v * A(2) + (1-v) * B(2), u * C(2) + (1 - u) * D(2)]; - J = [ v * A + (1-v) * B, u * C + (1 - u) * D]; - Fxy = P - u * (v * P2 + (1-v) * P1) - (1-u) * ( v * P3 + (1-v) * P0); - uv = [u;v] - inv(J) * Fxy; - end - Xp(j,1) = uv(1); - Xp(j,2) = uv(2); -end -end - diff --git a/matlab/spline_surf_vec.m b/matlab/spline_surf_vec.m deleted file mode 100644 index 5d067a8..0000000 --- a/matlab/spline_surf_vec.m +++ /dev/null @@ -1,160 +0,0 @@ - -clc -clear all -close all - - -u_n_basf = 15; -v_n_basf = 15; -u_knots = get_knot_vector(u_n_basf); -v_knots = get_knot_vector(v_n_basf); - -%%% base test - -% basetestvec(v_knots); -% return -%%% patch boundary - -% P0 = [0.2; 0.3]; -% P1 = [1.2; 0.5]; -% P2 = [1.5; 2]; -% P3 = [0.9; 1.7]; - -P0 = [0.0; 0.0]; -P1 = [2.0; 0.0]; -P2 = [2.0; 2.0]; -P3 = [0.0; 2.0]; - -%%% Reduce points to be approximatex (i.e., points which are in convex hull of the points P0,P1,P2,P3) - -X = getpoints(); -[Xp X] = transform( X,P0,P1,P2,P3 ); -[np k] = size(Xp); -[ Xp ] = solve( Xp,P0,P1,P2,P3 ); - -%%% Construction of the matrix - - -%interv ?? -[B, Interv ] = build_LS_matrix( u_knots,v_knots, Xp); - -W = sparse(diag(Xp(:,4))); - -g = Xp(:,3); -b = B'*W*g; - -C = B'*W*B; -nnzC = nnz(C); - - -A = build_reg_matrix( u_knots,v_knots, P0,P1,P2,P3,nnzC); - -nC = norm(full(C)); -nA = norm(full(A)); -r = norm(full(C))/ norm(full(A)); -%r = 0.0; - -% [q r] = qr(B); -% -% -% z = r\(q'*g) - -S = C+0.01*r*A; - -z = pcg(S,b,1e-12,500); - -%%% Solution - -% Direct Solver -% C = B'*W*B;%+A; -% b = B'*W*g; -% z = C\b; - - -% Errors -Err = get_errors(abs(W*B*z-g),Interv,u_n_basf,v_n_basf); - - -%%% PLOT results - -plotresultsvec(u_knots, v_knots,P0,P1,P2,P3,X,z,Err) - -%return - -%%%%%%%%%%%%%%%%%% -%%% Second Surface -%%%%%%%%%%%%%%%%%% - - -us_n_basf = 10; -vs_n_basf = 10; -us_knots = get_knot_vector(us_n_basf); -vs_knots = get_knot_vector(vs_n_basf); - -%%% patch boundary - -% P0s = [0.4; 0.2]; -% P1s = [1.5; 0.1]; -% P2s = [1.1; 1.8]; -% P3s = [0.6; 1.7]; - -P0s = [0.0; 0.0]; -P1s = [2.0; 0.0]; -P2s = [2.0; 2.0]; -P3s = [0.0; 2.0]; - - -%%% Reduce points to be approximatex (i.e., points which are in convex hull of the points P0,P1,P2,P3) - -Xs = getpoints2(); -[Xps Xs] = transform( Xs,P0s,P1s,P2s,P3s ); -[nps ks] = size(Xps); -[ Xps ] = solve( Xps,P0s,P1s,P2s,P3s ); - -%%% Construction of the matrix - -%interv ?? -[Bs, Intervs ] = build_LS_matrix( us_knots,vs_knots, Xps); - -Ws = sparse(diag(Xps(:,4))); - -gs = Xps(:,3); -bs = Bs'*Ws*gs; - -Cs = Bs'*Ws*Bs; -nnzCs = nnz(Cs); - - -As = build_reg_matrix( us_knots,vs_knots, P0s,P1s,P2s,P3s,nnzCs); - -nCs= norm(full(Cs)); -nAs = norm(full(As)); -rs = norm(full(Cs))/ norm(full(As)); -%rs = 0.0; - -Ss = Cs+0.0010*rs*As; - -zs = pcg(Ss,bs,1e-14,500); -%zs = Ss\bs; - -%%% Solution - -% Direct Solver -% C = B'*W*B;%+A; -% b = B'*W*g; -% z = C\b; - - -% Errors -Errs = get_errors(abs(Ws*Bs*zs-gs),Intervs,us_n_basf,vs_n_basf); - - -%%% PLOT results -hold on -plotresultsvec(us_knots, vs_knots,P0s,P1s,P2s,P3s,Xs,zs,Errs) -hold off - - -%intersections - -% \ No newline at end of file diff --git a/matlab/splinebasevec.m b/matlab/splinebasevec.m deleted file mode 100644 index 007e303..0000000 --- a/matlab/splinebasevec.m +++ /dev/null @@ -1,80 +0,0 @@ -function [ f, k ] = splinebasevec( T,t,order,varargin) - -% T - knot vector -% k - index of basis function, k = 1,...,length(T)-3 -% t - parameter t \in [0,1] -% f - vector of basis functions values -% k - kth interval - -n = length(T); - -f = spalloc(n-3,1,3); - -optargin = length(varargin); - -if optargin == 0 - k = find_int( T,t ); -elseif optargin == 1 - k = varargin{1}; -% if (T(k) <= t+2) && (T(k+3) >= t) -% disp('OK') -% [T(k+2), t,T(k+3)] -% else -% disp('PROBLEM') -% return -% end -if (T(k) <= t+2) && (T(k+3) >= t) - % disp('OK'); - [T(k+2), t,T(k+3)]; -else - disp('PROBLEM') - pause - return -end -end - - -% for k = 1:n-5 -% if(t>=T(k+2) && t<=T(k+3)) -% % k_int = k; -% break; -% end -% end -% -% if k ~=k2-2 -% [k k2-2] -% pause -% end - -tk1 = T(k+1); -tk2 = T(k+2); -tk3 = T(k+3); -tk4 = T(k+4); - -d31 = tk3-tk1; -d32 = tk3-tk2; -d42 = tk4-tk2; - -d3t = tk3-t; -dt1 = t-tk1; -dt2 = t-tk2; -d4t = tk4-t; - -d31d32 = d31*d32; -d42d32 = d42*d32; - -% f(k) = (tk3-t)^2/((tk3-tk1)*(tk3-tk2)); -% f(k+1)= (t-tk1)*(tk3 -t)/((tk3-tk1)*(tk3-tk2)) + (t-tk2)*(tk4 -t)/((tk4-tk2)*(tk3-tk2)); -% f(k+2) = (t-tk2)^2/((tk4 - tk2)*(tk3-tk2)); - -if order == 0 - f(k) = d3t^2/d31d32; - f(k+1)= dt1*d3t/d31d32 + dt2*d4t/d42d32; - f(k+2) = dt2^2/d42d32; -elseif order ==1 - f(k) = -2*d3t/d31d32; - f(k+1)= (d3t - dt1)/d31d32 + (d4t - dt2)/d42d32; - f(k+2) = 2*dt2/d42d32; -end - -end \ No newline at end of file diff --git a/matlab/swaplines.m b/matlab/swaplines.m deleted file mode 100644 index b9ad811..0000000 --- a/matlab/swaplines.m +++ /dev/null @@ -1,8 +0,0 @@ -function [ mat ] = swaplines( mat,i,j ) - -temp = mat(j,:); -mat(j,:) = mat(i,:); -mat(i,:)= temp; - -end - diff --git a/matlab/transform.m b/matlab/transform.m deleted file mode 100644 index 932957f..0000000 --- a/matlab/transform.m +++ /dev/null @@ -1,55 +0,0 @@ -function [ Xp X] = transform( X,P0,P1,P2,P3 ) - -[np k] = size(X); - -Pi = [P0 P1 P2 P3]; - -%%% Compute normalized normals - -Ni = zeros(2,4); - - -Ni(:,1) = Pi(:,1) - Pi(:,4); -nt = Ni(1,1); -Ni(1,1) = -Ni(2,1); -Ni(2,1) = nt; -Ni(:,1) = Ni(:,1)/norm(Ni(:,1)); - - - -for i =2:4 - Ni(:,i) = Pi(:,i) - Pi(:,i-1); - nt = Ni(1,i); - Ni(1,i) = -Ni(2,i); - Ni(2,i) = nt; - Ni(:,i) = Ni(:,i)/norm(Ni(:,i)); -end - -%%% Compute local coordinates and drop all - -h = 0; -for j=1:np - - P = [X(j,1); X(j,2)]; - - u = (P - Pi(:,1))' * Ni(:,1) / ( (P - Pi(:,1))' * Ni(:,1) + (P - Pi(:,3))' * Ni(:,3) ) ; - v = (P - Pi(:,1))' * Ni(:,2) / ( (P - Pi(:,1))' * Ni(:,2) + (P - Pi(:,4))' * Ni(:,4) ) ; -% P - Pi(:,1) -% P - Pi(:,4) -% Ni(:,2) -% Ni(:,4) -% (P - Pi(:,1))' * Ni(:,2) -% (P - Pi(:,4))' * Ni(:,4) -% pause - - if (u >= 0) && (u <= 1) && (v >= 0) && (v <= 1) - h = h+1; - Xp(h,1) = u; - Xp(h,2) = v; - Xp(h,3:4) = X(j,3:4); - Xp(h,5:6) = X(j,1:2); - end -end - -end - diff --git a/matlab/vec2mat.m b/matlab/vec2mat.m deleted file mode 100644 index 7f0d202..0000000 --- a/matlab/vec2mat.m +++ /dev/null @@ -1,11 +0,0 @@ -function [ Z_coor ] = vec2mat( zs,u_n_basf,v_n_basf ) -%UNTITLED3 Summary of this function goes here -% Detailed explanation goes here -Z_coor = zeros(u_n_basf,v_n_basf); - -for i =1:v_n_basf - Z_coor(:,i) = zs(1+(i-1)*(u_n_basf):i*(u_n_basf)); -end - -end - From 1ca90a9169944d6054e836d2c5fe43308a1c2a84 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 11 Sep 2017 10:09:24 +0200 Subject: [PATCH 11/67] Move forward to new version of matlab code --- external/matlab-intersections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/matlab-intersections b/external/matlab-intersections index a10a099..e1d242e 160000 --- a/external/matlab-intersections +++ b/external/matlab-intersections @@ -1 +1 @@ -Subproject commit a10a099c06d3033de3e5cb52e0726449fb8ac42a +Subproject commit e1d242e4c2e43966b6dfda0e9c82ee3f8a4ddf03 From a9727d785132b11fb6b04614735e9c58b894c43e Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 11 Sep 2017 17:25:40 +0200 Subject: [PATCH 12/67] Update BIH submodule --- external/bih | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/bih b/external/bih index b549511..2d7b774 160000 --- a/external/bih +++ b/external/bih @@ -1 +1 @@ -Subproject commit b54951182c73698c0bbafd37aa30e334c2ed3900 +Subproject commit 2d7b774bae113c23e07b39b59253f9c49cce075e From 82d687a2f324dce103c298c6b22b0099f321355e Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 11 Sep 2017 17:35:53 +0200 Subject: [PATCH 13/67] Fix tracking of matlab-intersections --- external/matlab-intersections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/matlab-intersections b/external/matlab-intersections index e1d242e..2bf73a4 160000 --- a/external/matlab-intersections +++ b/external/matlab-intersections @@ -1 +1 @@ -Subproject commit e1d242e4c2e43966b6dfda0e9c82ee3f8a4ddf03 +Subproject commit 2bf73a4a4f65aefdc17c955c84471897b67bc32a From 2f2e44bdfa213a706e03acc5de65c1232bbd1d11 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Fri, 15 Sep 2017 18:45:56 +0200 Subject: [PATCH 14/67] Classes for general B-spline and rational curves and surfaces. - representation and avaluation of curves and surfaces - plotting module - basic tests (mainly via plotting) --- src/bs_curve.py | 12 - src/bs_surface.py | 5 - src/bspline.py | 700 ++++++++++++++++++++++++++++++++++++++++++ src/bspline_plot.py | 84 +++++ tests/test_bspline.py | 133 ++++++++ 5 files changed, 917 insertions(+), 17 deletions(-) delete mode 100644 src/bs_curve.py delete mode 100644 src/bs_surface.py create mode 100644 src/bspline.py create mode 100644 src/bspline_plot.py create mode 100644 tests/test_bspline.py diff --git a/src/bs_curve.py b/src/bs_curve.py deleted file mode 100644 index 7a38ed0..0000000 --- a/src/bs_curve.py +++ /dev/null @@ -1,12 +0,0 @@ -class BSCurve: - ''' - Class representing a general B-spline curve. - Specializations for particular degree are derived. - ''' - pass - - -class BSCurveD2: - ''' - Quadratic B-spline. - ''' \ No newline at end of file diff --git a/src/bs_surface.py b/src/bs_surface.py deleted file mode 100644 index c10a266..0000000 --- a/src/bs_surface.py +++ /dev/null @@ -1,5 +0,0 @@ -class BSSurface: - ''' - Representation of the B-spline surface. - ''' - pass \ No newline at end of file diff --git a/src/bspline.py b/src/bspline.py new file mode 100644 index 0000000..4aa0271 --- /dev/null +++ b/src/bspline.py @@ -0,0 +1,700 @@ +""" +Module with classes representing various B-spline and NURMS curves and surfaces. +These classes provide just basic functionality: +- storing the data +- evaluation of XYZ for UV +- derivatives +In future: +- serialization and deserialization using JSONdata - must make it an installable module +""" + +""" +This module tries to approximate 2.5D array of terrain points +using B-Spline surface. +""" + +import matplotlib.pyplot as plt +import numpy as np +import math +import numpy.linalg as la + + +__author__ = 'Jan Brezina , Jiri Hnidek , Jiri Kopal ' + + +class ParamError(Exception): + pass + +def check_matrix(mat, shape, values, idx=[]): + ''' + Check shape and type of scalar, vector or matrix. + :param mat: Scalar, vector, or vector of vectors (i.e. matrix). Vector may be list or other iterable. + :param shape: List of dimensions: [] for scalar, [ n ] for vector, [n_rows, n_cols] for matrix. + If a value in this list is None, the dimension can be arbitrary. The shape list is set fo actual dimensions + of the matrix. + :param values: Type or tuple of allowed types of elements of the matrix. E.g. ( int, float ) + :param idx: Internal. Used to pass actual index in the matrix for possible error messages. + :return: + ''' + try: + if len(shape) == 0: + if not isinstance(mat, values): + raise ParamError("Element at index {} of type {}, expected instance of {}.".format(idx, type(mat), values)) + else: + if shape[0] is None: + shape[0] = len(mat) + l=None + if not hasattr(mat, '__len__'): + l=0 + elif len(mat) != shape[0]: + l=len(mat) + if not l is None: + raise ParamError("Wrong len {} of element {}, should be {}.".format(l, idx, shape[0])) + for i, item in enumerate(mat): + sub_shape = shape[1:] + check_matrix(item, sub_shape, values, idx = [i] + idx) + shape[1:] = sub_shape + return shape + except ParamError: + raise + except Exception as e: + raise ParamError(e) + + +def check_knots(deg, knots, N): + total_multiplicity = 0 + for knot, mult in knots: + # This condition must hold if we assume only (0,1) interval of curve or surface parameters. + #assert float(knot) >= 0.0 and float(knot) <= 1.0 + total_multiplicity += mult + assert total_multiplicity == deg + N + 1 + + +scalar_types = (int, float, np.int64) + + + + + + + + + + + + + + +class SplineBasis: + """ + Represents a spline basis for a given knot vector and degree. + Provides canonical evaluation for the bases functions and their derivatives, knot vector lookup etc. + """ + + @classmethod + def make_equidistant(cls, degree, n_intervals, knot_range=[0.0, 1.0]): + """ + Returns spline basis for an eqidistant knot vector + having 'n_intervals' subintervals. + :param degree: degree of the spline basis + :param n_intervals: length of vector + :param knot_range: support of the spline, min and max valid 't' + :return: np array of knots + """ + n = n_intervals + 2 * degree + 1 + knots = np.array((knot_range[0],) * n) + diff = (knot_range[1] - knot_range[0]) / n_intervals + for i in range(degree + 1, n - degree): + knots[i] = (i - degree) * diff + knot_range[0] + knots[-degree - 1:] = knot_range[1] + return cls(degree, knots) + + @classmethod + def make_from_packed_knots(cls, degree, knots): + full_knots = [ q for q, mult in knots for i in range(mult) ] + return cls(degree, full_knots) + + + def __init__(self, degree, knots): + """ + Constructor of the basis. + :param degree: Degree of Bezier polynomials >=0. + :param knots: Numpy array of the knots including multiplicities. + """ + assert degree >=0 + self.degree = degree + + # check free ends (and full degree along the whole curve) + for i in range(self.degree): + assert knots[i] == knots[i+1] + assert knots[-i-1] == knots[-i-2] + self.knots = knots + + self.size = len(self.knots) - self.degree -1 + self.knots_idx_range = [self.degree, len(self.knots) - self.degree - 1] + self.domain = self.knots[self.knots_idx_range] + self.domain_size = self.domain[1] - self.domain[0] + # Number of basis functions. + + def pack_knots(self): + last, mult = self.knots[0], 0 + packed_knots = [] + for q in self.knots: + if q == last: + mult+=1 + else: + packed_knots.append( (last, mult) ) + last, mult = q, 1 + return packed_knots + + def find_knot_interval(self, t): + """ + Find the first non-empty knot interval containing the value 't'. + i.e. knots[i] <= t <= knots[i+1], where knots[i] < knots[i+1] + Returns I = i - degree, which is the index of the first basis function + nonzero in 't'. + + :param t: float, must be within knots limits. + :return: I + """ + assert self.knots[0] <= t <= self.knots[-1] + + """ + This function try to find index for given t_param in knot_vec that + is covered by all (3) base functions. + :param self.knots: + :param t: + :return: + """ + + # get range without multiplicities + mn = self.knots_idx_range[0] + mx = self.knots_idx_range[1] + diff = mx - mn + + while diff > 1: + # estimate for equidistant knots + t_01 = (t - self.knots[mn]) / self.domain_size + est = int(t_01 * diff + mn) + if t > self.knots[est]: + if mn == est : + break + mn = est + else: + if mx == est: + mn = mx + break + mx = est + diff = mx - mn + return min(mn, self.knots_idx_range[1] - 1) - self.degree + + def _basis(self, deg, idx, t): + """ + Recursive evaluation of basis function of given degree and index. + + :param deg: Degree of the basis function + :param idx: Index of the basis function to evaluate. + :param t: Point of evaluation. + :return Value of the basis function. + """ + + if deg == 0: + t_0 = self.knots[idx] + t_1 = self.knots[idx + 1] + value = 1.0 if t_0 <= t < t_1 else 0.0 + return value + else: + t_i = self.knots[idx] + t_ik = self.knots[idx + deg] + top = t - t_i + bottom = t_ik - t_i + if bottom != 0: + value = top / bottom * self._basis(deg-1, idx, t) + else: + value = 0.0 + + t_ik1 = self.knots[idx + deg + 1] + t_i1 = self.knots[idx + 1] + top = t_ik1 - t + bottom = t_ik1 - t_i1 + if bottom != 0: + value += top / bottom * self._basis(deg-1, idx+1, t) + + return value + + def fn_supp(self, i_base): + """ + Support of the base function 'i_base'. + :param i_base: + :return: (t_min, t_max) + """ + return (self.knots[i_base], self.knots[i_base + self.degree + 1]) + + def eval(self, i_base, t): + """ + :param i_base: Index of base function to evaluate. + :param t: point in which evaluate + :return: b_i(y) + """ + assert 0 <= i_base < self.size + if i_base == self.size -1 and t == self.domain[1]: + return 1.0 + return self._basis(self.degree, i_base, t) + + + +class Curve: + + @classmethod + def make_raw(cls, poles, knots, rational=False, degree=2): + """ + Construct a B-spline curve. + :param poles: List of poles (control points) ( X, Y, Z ) or weighted points (X,Y,Z, w). X,Y,Z,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: List of tuples (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. Total number of knots, i.e. sum of their multiplicities, must be + degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. + :param degree: Non-negative int + """ + basis = SplineBasis(degree, knots) + return cls(basis, poles, rational) + + """ + Defines a 3D (or (dim -D) curve as B-spline. We shall work only with B-splines of degree 2. + Corresponds to "B-spline Curve - <3D curve record 7>" from BREP format description. + """ + def __init__(self, basis, poles, rational = False): + self.basis = basis + dim = len(poles[0]) - rational + check_matrix(poles, [self.basis.size, dim + rational], scalar_types ) + + self.poles=np.array(poles) # N x D + self.rational=rational + if rational: + self._weights = poles[:, dim] + self._poles = (poles[:, 0:dim].T * self._weights ).T + + + def eval(self, t): + + it = self.basis.find_knot_interval(t) + dt = self.basis.degree + 1 + t_base_vec = np.array([self.basis.eval(jt, t) for jt in range(it, it + dt)]) + + if self.rational: + top_value = np.inner(t_base_vec, self._poles[it: it + dt, :].T) + bot_value = np.inner(t_base_vec, self._weights[it: it + dt]) + return top_value / bot_value + else: + return np.inner(t_base_vec, self.poles[it: it + dt, :].T) + + + + + +class Surface: + """ + Defines a B-spline surface. + """ + + @classmethod + def make_raw(cls, poles, knots, rational=False, degree=(2,2)): + """ + Construct a B-spline curve. + :param poles: List of poles (control points) ( X, Y, Z ) or weighted points (X,Y,Z, w). X,Y,Z,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: List of tuples (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. Total number of knots, i.e. sum of their multiplicities, must be + degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. + :param degree: Non-negative int + """ + u_basis = SplineBasis(degree[0], knots[0]) + v_basis = SplineBasis(degree[0], knots[0]) + return cls( (u_basis, v_basis), poles, rational) + + + def __init__(self, basis, poles, rational=False): + """ + Construct a B-spline in 3d space. + :param poles: Matrix (list of lists) of Nu times Nv poles (control points). + Single pole is a points ( X, Y, Z ) or weighted point (X,Y,Z, w). X,Y,Z,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: Tuple (u_knots, v_knots). Both u_knots and v_knots are lists of tuples + (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. For both U and V knot vector the total number of knots, + i.e. sum of their multiplicities, must be degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. BREP format have two independent flags + for U and V parametr, but only choices 0,0 and 1,1 have sense. + :param degree: (u_degree, v_degree) Both positive ints. + """ + + self.u_basis, self.v_basis = basis + self.rational = rational + dim = len(poles[0][0]) - rational + check_matrix(poles, [self.u_basis.size, self.v_basis.size, dim + rational], scalar_types ) + self.poles=np.array(poles) + assert self.poles.shape == (self.u_basis.size, self.v_basis.size, dim + rational) + if rational: + self._weights = poles[:, :, dim] + self._poles = (poles[:,:,0:dim].T * self._weights.T ).T + + def eval(self, u, v): + iu = self.u_basis.find_knot_interval(u) + iv = self.v_basis.find_knot_interval(v) + du = self.u_basis.degree + 1 + dv = self.v_basis.degree + 1 + u_base_vec = np.array([self.u_basis.eval(ju, u) for ju in range(iu, iu + du)]) + v_base_vec = np.array([self.v_basis.eval(jv, v) for jv in range(iv, iv + dv)]) + + if self.rational: + top_value = np.inner(u_base_vec, self._poles[iu: iu + du, iv: iv + dv, :].T) + top_value = np.inner(top_value, v_base_vec) + bot_value = np.inner(u_base_vec, self._weights[iu: iu + du, iv: iv + dv].T) + bot_value = np.inner(bot_value, v_base_vec) + return top_value / bot_value + else: + #print("u: {} v: {} p: {}".format(u_base_vec.shape, v_base_vec.shape, self.poles[iu: iu + du, iv: iv + dv, :].shape)) + # inner product sums along the last axis of its parameters + value = np.inner(u_base_vec, self.poles[iu: iu + du, iv: iv + dv, :].T ) + #print("val: {}".format(value.shape)) + return np.inner( value, v_base_vec) + + + + +class Z_Surface: + """ + Simplified B-spline surface that use just linear or bilinear transform between XY and UV. + + TODO: + - We need conversion to full 3D surface for the BREP output + - Optimization: simplified Bspline evaluation just for the singel coordinate + """ + + def eval_xy(self, xy_points): + pass + + + + + + + + + + + + + + + + + + + + + + + + + + +# Cache used of knot vector (computation of differences) +KVN_CACHE = {} +SB_CACHE = {} + + + +def spline_base_vec(knot_vec, t_param, order, sparse=False): + """ + This function compute normalized blending function aka base function of B-Spline curve or surface. + :param knot_vec: + :param t_param: + :param order: (0: function value, 1: derivative function value) + :param sparse: + :return: + """ + + + idx = find_index(knot_vec, t_param) + n_basf = len(knot_vec) - 3 + + # Create sparse matrix + if sparse is True: + basis_values = np.zeros(3) + else: + basis_values = np.zeros(n_basf) + + tk1 = knot_vec[idx + 1] + tk2 = knot_vec[idx + 2] + tk3 = knot_vec[idx + 3] + tk4 = knot_vec[idx + 4] + + d31 = tk3 - tk1 + d32 = tk3 - tk2 + d42 = tk4 - tk2 + + dt1 = t_param - tk1 + dt2 = t_param - tk2 + d3t = tk3 - t_param + d4t = tk4 - t_param + + d31_d32 = d31 * d32 + d42_d32 = d42 * d32 + + # basis function values + if order == 0: + if sparse is True: + basis_values[0] = (d3t * d3t) / d31_d32 + basis_values[1] = ((dt1 * d3t) / d31_d32) + ((dt2 * d4t) / d42_d32) + basis_values[2] = (dt2 * dt2) / d42_d32 + + else: + basis_values[idx] = (d3t * d3t) / d31_d32 + basis_values[idx + 1] = ((dt1 * d3t) / d31_d32) + ((dt2 * d4t) / d42_d32) + basis_values[idx + 2] = (dt2 * dt2) / d42_d32 + + # basis function derivatives + elif order == 1: + if sparse is True: + basis_values[0] = -2 * d3t / d31_d32 + basis_values[1] = (d3t - dt1) / d31_d32 + (d4t - dt2) / d42_d32 + basis_values[2] = 2 * dt2 / d42_d32 + else: + basis_values[idx] = -2*d3t / d31_d32 + basis_values[idx + 1] = (d3t - dt1) / d31_d32 + (d4t - dt2) / d42_d32 + basis_values[idx + 2] = 2 * dt2 / d42_d32 + + return basis_values, idx + + +def test_spline_base_vec(knots=np.array((0.0, 0.0, 0.0, 1/3.0, 2/3.0, 1.0, 1.0, 1.0)), sparse=False): + """ + Test optimized version of spline base function + :param knots: numpy array of knots + :param sparse: is sparse matrix used + :return: + """ + + num = 100 + n_basf = len(knots) - 3 + y_coords = {} + for k in range(0, n_basf): + temp = {} + for i in range(0, num+1): + t_param = min(knots) + max(knots) * i / float(num) + if sparse is True: + temp[i] = spline_base_vec(knots, t_param, 0, sparse)[0].toarray()[0] + else: + temp[i] = spline_base_vec(knots, t_param, 0, sparse)[0] + y_coords[k] = temp + + diff_x = (max(knots) - min(knots)) / num + x_coord = [min(knots) + diff_x*i for i in range(0, num+1)] + + for temp in y_coords.values(): + plt.plot(x_coord, temp.values()) + plt.show() + + + + +def spline_base(knot_vec, basis_fnc_idx, t_param): + """ + Evaluate a second order bases function in 't_param'. + This function compute normalized blending function aka base function of B-Spline curve or surface. + This function implement some optimization. + :param knot_vec: knot vector + :param basis_fnc_idx: index of basis function + :param t_param: parameter t in interval <0, 1> + :return: value of basis function + """ + + # When basis function has zero value at given interval, then return 0 + if t_param < knot_vec[basis_fnc_idx] or knot_vec[basis_fnc_idx+3] < t_param: + return 0.0 + + try: + value = SB_CACHE[(tuple(knot_vec), basis_fnc_idx, t_param)] + except KeyError: + try: + kvn = KVN_CACHE[tuple(knot_vec)] + except KeyError: + knot_vec_len = len(knot_vec) + kvn = [0] * knot_vec_len + i = 0 + while i < knot_vec_len - 1: + if knot_vec[i] - knot_vec[i+1] != 0: + kvn[i] = 1.0 + i += 1 + KVN_CACHE[tuple(knot_vec)] = kvn + tks = [knot_vec[basis_fnc_idx + k] for k in range(0, 4)] + + value = 0.0 + if knot_vec[basis_fnc_idx] <= t_param <= knot_vec[basis_fnc_idx+1] and kvn[basis_fnc_idx] != 0: + value = (t_param - tks[0])**2 / ((tks[2] - tks[0]) * (tks[1] - tks[0])) + elif knot_vec[basis_fnc_idx+1] <= t_param <= knot_vec[basis_fnc_idx+2] and kvn[basis_fnc_idx+1] != 0: + value = ((t_param - tks[0]) * (tks[2] - t_param)) / ((tks[2] - tks[0]) * (tks[2] - tks[1])) + \ + ((t_param - tks[1]) * (tks[3] - t_param)) / ((tks[3] - tks[1]) * (tks[2] - tks[1])) + elif knot_vec[basis_fnc_idx+2] <= t_param <= knot_vec[basis_fnc_idx+3] and kvn[basis_fnc_idx+2] != 0: + value = (tks[3] - t_param)**2 / ((tks[3] - tks[1]) * (tks[3] - tks[2])) + SB_CACHE[(tuple(knot_vec), basis_fnc_idx, t_param)] = value + + return value + + +KNOT_VEC_CACHE = {} + + +def spline_surface(poles, u_param, v_param, u_knots, v_knots, u_mults, v_mults): + """ + Compute coordinate of one point at B-Surface (u and v degree is 2) + :param poles: matrix of "poles" + :param u_param: X coordinate in range <0, 1> + :param v_param: Y coordinate in range <0, 1> + :param u_knots: list of u knots + :param v_knots: list of v knots + :param u_mults: list of u multiplicities + :param v_mults: list of v multiplicities + :return: tuple of (x, y, z) coordinate of B-Spline surface + """ + + # "Decompress" knot vectors using multiplicities + # e.g + # u_knots: (0.0, 0.5, 1.0) u_mults: (3, 1, 3) will be converted to + # _u_knot: (0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0) + _u_knots = [] + _v_knots = [] + try: + _u_knots = KNOT_VEC_CACHE[(u_knots, u_mults)] + except KeyError: + for idx, mult in enumerate(u_mults): + _u_knots.extend([u_knots[idx]] * mult) + KNOT_VEC_CACHE[(u_knots, u_mults)] = _u_knots + try: + _v_knots = KNOT_VEC_CACHE[(v_knots, v_mults)] + except KeyError: + for idx, mult in enumerate(v_mults): + _v_knots.extend([v_knots[idx]] * mult) + KNOT_VEC_CACHE[(v_knots, v_mults)] = _v_knots + + u_n_basf = len(_u_knots) - 3 + v_n_basf = len(_v_knots) - 3 + + uf_mat = [0.0] * u_n_basf + vf_mat = [0.0] * v_n_basf + + # Pre-compute base values of functions + for k in range(0, u_n_basf): + uf_mat[k] = spline_base(_u_knots, k, u_param) + for k in range(0, v_n_basf): + vf_mat[k] = spline_base(_v_knots, k, v_param) + + x_coord, y_coord, z_coord = 0.0, 0.0, 0.0 + + # Compute point at B-Spline surface + for i in range(0, u_n_basf): + for j in range(0, v_n_basf): + base_i_j = uf_mat[i] * vf_mat[j] + x_coord += poles[i][j][0] * base_i_j + y_coord += poles[i][j][1] * base_i_j + z_coord += poles[i][j][2] * base_i_j + + return x_coord, y_coord, z_coord + + + + + +def transform_points(quad, terrain_data): + """ + Function computes corresponding (u,v) for (x,y) + :param terrain_data: matrix of 3D terrain data, N rows, 3 cols + :param quad: points defining quadrangle area (array) 2 rows 4 cols, + :return: transformed and cropped terrain points + """ + + # if quad points are given counter clock wise, this is outer normal in XY plane + mat_n = np.empty_like(quad) + + mat_n[:, 0] = quad[:, 0] - quad[:, 3] + nt = mat_n[0, 0] + mat_n[0, 0] = -mat_n[1, 0] + mat_n[1, 0] = nt + mat_n[:, 0] = mat_n[:, 0] / la.norm(mat_n[:, 0]) + + for i in range(1, 4): + mat_n[:, i] = quad[:, i] - quad[:, i-1] + nt = mat_n[0, i] + mat_n[0, i] = -mat_n[1, i] + mat_n[1, i] = nt + mat_n[:, i] = mat_n[:, i] / la.norm(mat_n[:, i]) + + terrain_len = len(terrain_data) + + # Compute local coordinates and drop all points outside quadraangle + param_terrain = np.empty_like(terrain_data) + # indexing + d0 = (terrain_data[:, 0:2] - quad[:, 0].transpose()) + d2 = (terrain_data[:, 0:2] - quad[:, 2].transpose()) + d3 = (terrain_data[:, 0:2] - quad[:, 3].transpose()) + d0_n0 = d0 * mat_n[:, 0] + d0_n1 = d0 * mat_n[:, 1] + d2_n2 = d2 * mat_n[:, 2] + d3_n3 = d3 * mat_n[:, 3] + u = np.divide(d0_n0, d0_n0 + d2_n2) + v = np.divide(d0_n1, d0_n1 + d3_n3) + + h = -1 + for j in range(0,terrain_len): + if (u[j] >= 0.0) and (u[j] <= 1.0) and (v[j] >= 0.0) and (v[j] <= 1.0): + h += 1 + param_terrain[h, 0] = u[j] + param_terrain[h, 1] = v[j] + param_terrain[h, 2] = terrain_data[j, 2] + + param_terrain.resize(h+1, 3) + + uv = np.reshape(param_terrain[:, 0:2], 2*h+2).transpose() + + a = quad[:, 3] - quad[:, 2] + b = quad[:, 0] - quad[:, 1] + c = quad[:, 1] - quad[:, 2] + d = quad[:, 0] - quad[:, 3] + + ldiag = np.zeros([2 * h + 1, 1]) + diag = np.zeros([2 * h + 2, 1]) + udiag = np.zeros([2 * h + 1, 1]) + + # fixed point Newton iteration + for i in range(0, 1): # 1->5 + for j in range(0, h+1): + mat_j = compute_jacobi(uv[2 * j, 0], uv[2 * j + 1, 0], a, b, c, d, -1) + ldiag[2 * j, 0] = mat_j[1, 0] + diag[2 * j, 0] = mat_j[0, 0] + diag[2 * j + 1, 0] = mat_j[1, 1] + udiag[2 * j, 0] = mat_j[0, 1] + + mat_jg = scipy.sparse.diags([ldiag[:, 0], diag[:, 0], udiag[:, 0]], [-1, 0, 1], format="csr") + uv = uv - mat_jg.dot(uv) + + uv = uv.reshape([h + 1, 2]) + + # Tresholding of the refined coordinates + for j in range(0, h+1): + if uv[j, 0] < 0: + uv[:, 0] = 0 + elif uv[j, 0] > 1: + uv[j, 0] = 1 + elif uv[j, 1] < 0: + uv[:, 1] = 0 + elif uv[j, 1] > 1: + uv[j, 1] = 1 + + param_terrain[:, 0:2] = uv + + return param_terrain + + diff --git a/src/bspline_plot.py b/src/bspline_plot.py new file mode 100644 index 0000000..e6b888c --- /dev/null +++ b/src/bspline_plot.py @@ -0,0 +1,84 @@ +""" +Functions to plot Bspline curves and surfaces. +""" + +import matplotlib.pyplot as plt +from matplotlib import cm +import numpy as np + + + +def plot_curve_2d(curve, n_points=100, **kwargs): + """ + Plot a 2d Bspline curve. + :param curve: Curve t -> x,y + :param n_points: Number of evaluated points. + :param: kwargs: Additional parameters passed to the mtplotlib plot command. + :return: Plot object. + """ + + basis = curve.basis + t_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + + coords = [curve.eval(t) for t in t_coord] + x_coord, y_coord = zip(*coords) + return plt.plot(x_coord, y_coord, **kwargs) + + +def plot_curve_poles_2d(curve, **kwargs): + """ + Plot poles of the B-spline curve. + :param curve: Curve t -> x,y + :param: kwargs: Additional parameters passed to the mtplotlib plot command. + :return: Plot object. + """ + x_poles, y_poles = curve.poles.T[0:2, :] # remove weights + return plt.plot(x_poles, y_poles, 'bo', color='red', **kwargs) + + +def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): + """ + Plot a surface in 3d. + Usage: + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.pyplot as plt + fig = plt.figure() + ax = fig.gca(projection='3d') + plot_surface_3d(surface, ax) + plt.show() + + :param surface: Parametric surface in 3d. + :param fig_ax: Axes object: + :param n_points: (nu, nv), nu*nv - number of evaluation point + :param kwargs: surface_plot additional options + :return: The plot object. + """ + + u_basis, v_basis = surface.u_basis, surface.v_basis + + u_coord = np.linspace(u_basis.domain[0], u_basis.domain[1], n_points[0]) + v_coord = np.linspace(v_basis.domain[0], v_basis.domain[1], n_points[1]) + + U, V = np.meshgrid(u_coord, v_coord) + points = np.vstack( [U.ravel(), V.ravel()] ) + + xyz = np.array([surface.eval(point[0], point[1]) for point in points.T ]) + X, Y, Z = xyz.T + + X = X.reshape(U.shape) + Y = Y.reshape(U.shape) + Z = Z.reshape(U.shape) + + # Plot the surface. + return fig_ax.plot_surface(X, Y, Z, **kwargs) + + +def plot_surface_poles_3d(surface, fig_ax, **kwargs): + """ + Plot poles of the B-spline curve. + :param curve: Curve t -> x,y + :param: kwargs: Additional parameters passed to the mtplotlib plot command. + :return: Plot object. + """ + x_poles, y_poles, z_poles = surface.poles[:, :, 0:3].reshape(-1, 3).T # remove weights and flatten nu, nv + return fig_ax.scatter(x_poles, y_poles, z_poles, color='red', **kwargs) diff --git a/tests/test_bspline.py b/tests/test_bspline.py new file mode 100644 index 0000000..4d20bb0 --- /dev/null +++ b/tests/test_bspline.py @@ -0,0 +1,133 @@ +import pytest +import bspline as bs +import numpy as np +import math +import bspline_plot as bs_plot +import matplotlib.pyplot as plt + + +class TestSplineBasis: + def test_find_knot_interval(self): + eq_basis = bs.SplineBasis.make_equidistant(2, 100) + assert eq_basis.find_knot_interval(0.0) == 0 + assert eq_basis.find_knot_interval(0.001) == 0 + assert eq_basis.find_knot_interval(0.01) == 0 + assert eq_basis.find_knot_interval(0.011) == 1 + assert eq_basis.find_knot_interval(0.5001) == 50 + assert eq_basis.find_knot_interval(1.0 - 0.011) == 98 + assert eq_basis.find_knot_interval(1.0 - 0.01) == 98 + assert eq_basis.find_knot_interval(1.0 - 0.001) == 99 + assert eq_basis.find_knot_interval(1.0) == 99 + + def plot_basis(self, degree): + + eq_basis = bs.SplineBasis.make_equidistant(degree, 4) + + n_points = 401 + dx = (eq_basis.domain[1] - eq_basis.domain[0]) / (n_points -1) + x_coord = [eq_basis.domain[0] + dx * i for i in range(n_points)] + + for i_base in range(eq_basis.size): + y_coord = [ eq_basis.eval(i_base, x) for x in x_coord ] + plt.plot(x_coord, y_coord) + + plt.show() + + + def test_eval(self): + #self.plot_basis(0) + #self.plot_basis(1) + #self.plot_basis(2) + #self.plot_basis(3) + + eq_basis = bs.SplineBasis.make_equidistant(0, 2) + assert eq_basis.eval(0, 0.0) == 1.0 + assert eq_basis.eval(1, 0.0) == 0.0 + assert eq_basis.eval(0, 0.5) == 0.0 + assert eq_basis.eval(1, 0.5) == 1.0 + assert eq_basis.eval(1, 1.0) == 1.0 + + eq_basis = bs.SplineBasis.make_equidistant(1, 4) + assert eq_basis.eval(0, 0.0) == 1.0 + assert eq_basis.eval(1, 0.0) == 0.0 + assert eq_basis.eval(2, 0.0) == 0.0 + assert eq_basis.eval(3, 0.0) == 0.0 + assert eq_basis.eval(4, 0.0) == 0.0 + + assert eq_basis.eval(0, 0.125) == 0.5 + assert eq_basis.eval(1, 0.125) == 0.5 + assert eq_basis.eval(2, 1.0) == 0.0 + + +class TestCurve: + + def plot_4p(self): + degree = 2 + poles = [ [0., 0.], [1.0, 0.5], [2., -2.], [3., 1.] ] + basis = bs.SplineBasis.make_equidistant(degree, 2) + curve = bs.Curve(basis, poles) + + bs_plot.plot_curve_2d(curve) + bs_plot.plot_curve_poles_2d(curve) + plt.show() + + def test_evaluate(self): + #self.plot_4p() + pass + + +class TestSurface: + + + def make_function_grid(self, fn, nu, nv): + X_grid = np.linspace(0, 1.0, nu) + Y_grid = np.linspace(0, 1.0, nv) + Y, X = np.meshgrid(Y_grid, X_grid) + + points_uv = np.stack([X.ravel(), Y.ravel()], 1) + Z = np.apply_along_axis(fn, 1, points_uv) + points = np.stack([X.ravel(), Y.ravel(), Z], 1) + + return points.reshape( (nu, nv, 3) ) + + def plot_extrude(self): + fig = plt.figure() + ax = fig.gca(projection='3d') + + # curve extruded to surface + poles_yz = [[0., 0.], [1.0, 0.5], [2., -2.], [3., 1.]] + poles_x = [0, 1, 2] + poles = [ [ [x] + yz for yz in poles_yz ] for x in poles_x ] + u_basis = bs.SplineBasis.make_equidistant(2, 1) + v_basis = bs.SplineBasis.make_equidistant(2, 2) + surface_extrude = bs.Surface( (u_basis, v_basis), poles) + bs_plot.plot_surface_3d(surface_extrude, ax) + bs_plot.plot_surface_poles_3d(surface_extrude, ax) + plt.show() + + def plot_function(self): + # function surface + def function(x): + return math.sin(x[0]) * math.cos(x[1]) + + fig = plt.figure() + ax = fig.gca(projection='3d') + + poles = self.make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles) + bs_plot.plot_surface_3d(surface_func, ax) + bs_plot.plot_surface_poles_3d(surface_func, ax) + + plt.show() + + def test_evaluate(self): + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.pyplot as plt + + self.plot_extrude() + self.plot_function() + + + From a6ec4e8e4a63798d17ace72c469e985f4a14cc58 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sat, 16 Sep 2017 11:16:43 +0200 Subject: [PATCH 15/67] Tested Z-surface --- src/bspline.py | 401 ++++++++++-------------------------------- src/bspline_plot.py | 3 + tests/test_bspline.py | 80 +++++++-- 3 files changed, 168 insertions(+), 316 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 4aa0271..fad4e2b 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -3,9 +3,12 @@ These classes provide just basic functionality: - storing the data - evaluation of XYZ for UV -- derivatives In future: +- evaluation and xy<->uv functions accepting np.arrays, - serialization and deserialization using JSONdata - must make it an installable module +- use de Boor algorithm for evaluation of curves and surfaces +- evaluation of derivatives +- implement degree increasing and knot insertion """ """ @@ -109,6 +112,7 @@ def make_equidistant(cls, degree, n_intervals, knot_range=[0.0, 1.0]): knots[-degree - 1:] = knot_range[1] return cls(degree, knots) + @classmethod def make_from_packed_knots(cls, degree, knots): full_knots = [ q for q, mult in knots for i in range(mult) ] @@ -136,6 +140,7 @@ def __init__(self, degree, knots): self.domain_size = self.domain[1] - self.domain[0] # Number of basis functions. + def pack_knots(self): last, mult = self.knots[0], 0 packed_knots = [] @@ -147,6 +152,7 @@ def pack_knots(self): last, mult = q, 1 return packed_knots + def find_knot_interval(self, t): """ Find the first non-empty knot interval containing the value 't'. @@ -241,6 +247,17 @@ def eval(self, i_base, t): return 1.0 return self._basis(self.degree, i_base, t) + def make_linear_poles(self): + """ + Return poles of basis functions to get a f(x) = x. + :return: + """ + poles= [ 0.0 ] + for i in range(self.size-1): + pp = poles[-1] + (self.knots[i + self.degree + 1] - self.knots[i + 1]) / float(self.degree) + poles.append(pp) + return poles + class Curve: @@ -266,14 +283,14 @@ def make_raw(cls, poles, knots, rational=False, degree=2): """ def __init__(self, basis, poles, rational = False): self.basis = basis - dim = len(poles[0]) - rational - check_matrix(poles, [self.basis.size, dim + rational], scalar_types ) + self.dim = len(poles[0]) - rational + check_matrix(poles, [self.basis.size, self.dim + rational], scalar_types ) self.poles=np.array(poles) # N x D self.rational=rational if rational: - self._weights = poles[:, dim] - self._poles = (poles[:, 0:dim].T * self._weights ).T + self._weights = poles[:, self.dim] + self._poles = (poles[:, 0:self.dim].T * self._weights ).T def eval(self, t): @@ -332,13 +349,13 @@ def __init__(self, basis, poles, rational=False): self.u_basis, self.v_basis = basis self.rational = rational - dim = len(poles[0][0]) - rational - check_matrix(poles, [self.u_basis.size, self.v_basis.size, dim + rational], scalar_types ) + self.dim = len(poles[0][0]) - rational + check_matrix(poles, [self.u_basis.size, self.v_basis.size, self.dim + rational], scalar_types ) self.poles=np.array(poles) - assert self.poles.shape == (self.u_basis.size, self.v_basis.size, dim + rational) + assert self.poles.shape == (self.u_basis.size, self.v_basis.size, self.dim + rational) if rational: - self._weights = poles[:, :, dim] - self._poles = (poles[:,:,0:dim].T * self._weights.T ).T + self._weights = poles[:, :, self.dim] + self._poles = (poles[:,:,0:self.dim].T * self._weights.T ).T def eval(self, u, v): iu = self.u_basis.find_knot_interval(u) @@ -372,329 +389,105 @@ class Z_Surface: - We need conversion to full 3D surface for the BREP output - Optimization: simplified Bspline evaluation just for the singel coordinate """ + def __init__(self, xy_quad, z_surface): + """ + Construct a surface given by the 1d surface for the Z coordinate and XY quadrilateral + for the bilinear UV -> XY mapping. + :param xy_quad: np array N x 2 + Four or three points, determining bilinear or linear mapping, respectively. + Four points giving XY coordinates for the uv corners: (0,0), (0,1), (1,0), (1,1) + Three points giving XY coordinates for the uv corners: (0,0), (0,1), (1,0) + :param z_surface: !D Surface object. + """ + assert z_surface.dim == 1 + self.z_surface = z_surface + self.u_basis = z_surface.u_basis + self.v_basis = z_surface.v_basis + self.dim = 3 + + if len(xy_quad) == 3: + # linear case + self.xy_shift = xy_quad[0] + v_vec = xy_quad[1] - xy_quad[0] + u_vec = xy_quad[2] - xy_quad[0] + self.mat_uv_to_xy = np.column_stack((u_vec, v_vec)) + self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) + + self.xy_to_uv = self._linear_xy_to_uv + self.uv_to_xy = self._linear_uv_to_xy + + elif len(xy_quad) == 4: + # bilinear case + self.quad = xy_quad + + self.xy_to_uv = self._bilinear_xy_to_uv + self.uv_to_xy = self._bilinear_uv_to_xy - def eval_xy(self, xy_points): - pass - - - - - - - - - - - - - - - - - - - - - - - - - - -# Cache used of knot vector (computation of differences) -KVN_CACHE = {} -SB_CACHE = {} - - - -def spline_base_vec(knot_vec, t_param, order, sparse=False): - """ - This function compute normalized blending function aka base function of B-Spline curve or surface. - :param knot_vec: - :param t_param: - :param order: (0: function value, 1: derivative function value) - :param sparse: - :return: - """ - + else: + assert False, "Three or four points must be given." - idx = find_index(knot_vec, t_param) - n_basf = len(knot_vec) - 3 + def make_full_surface(self): + """ + Return representation of the surface by the 3d Surface object. + Compute redundant XY poles. + :return: Surface. + """ + basis = (self.z_surface.u_basis, self.z_surface.v_basis) - # Create sparse matrix - if sparse is True: - basis_values = np.zeros(3) - else: - basis_values = np.zeros(n_basf) + u = basis[0].make_linear_poles() + v = basis[1].make_linear_poles() + V, U = np.meshgrid(v,u) + uv_poles = np.stack([U, V], axis=2) + xy_poles = np.apply_along_axis(lambda x: self.uv_to_xy(x[0], x[1]), 2, uv_poles) + poles = np.concatenate( (xy_poles, self.z_surface.poles), axis = 2 ) - tk1 = knot_vec[idx + 1] - tk2 = knot_vec[idx + 2] - tk3 = knot_vec[idx + 3] - tk4 = knot_vec[idx + 4] + return Surface(basis, poles) - d31 = tk3 - tk1 - d32 = tk3 - tk2 - d42 = tk4 - tk2 + def _linear_uv_to_xy(self, u, v): + #assert uv_points.shape[0] == 2, "Size: {}".format(uv_points.shape) + uv_points = np.array([u, v]) + return self.mat_uv_to_xy.dot(uv_points) + self.xy_shift - dt1 = t_param - tk1 - dt2 = t_param - tk2 - d3t = tk3 - t_param - d4t = tk4 - t_param - d31_d32 = d31 * d32 - d42_d32 = d42 * d32 + def _bilinear_uv_to_xy(self, u, v): + weights = np.array([ (1-u)*(1-v), (1-u)*v, u*(1-v), u*v ]) + return self.quad.T.dot( weights ) - # basis function values - if order == 0: - if sparse is True: - basis_values[0] = (d3t * d3t) / d31_d32 - basis_values[1] = ((dt1 * d3t) / d31_d32) + ((dt2 * d4t) / d42_d32) - basis_values[2] = (dt2 * dt2) / d42_d32 + def _linear_xy_to_uv(self, x, y): + # assert xy_points.shape[0] == 2 + xy_points = np.array([x,y]) + return self.mat_xy_to_uv.dot((xy_points - self.xy_shift)) - else: - basis_values[idx] = (d3t * d3t) / d31_d32 - basis_values[idx + 1] = ((dt1 * d3t) / d31_d32) + ((dt2 * d4t) / d42_d32) - basis_values[idx + 2] = (dt2 * dt2) / d42_d32 - - # basis function derivatives - elif order == 1: - if sparse is True: - basis_values[0] = -2 * d3t / d31_d32 - basis_values[1] = (d3t - dt1) / d31_d32 + (d4t - dt2) / d42_d32 - basis_values[2] = 2 * dt2 / d42_d32 - else: - basis_values[idx] = -2*d3t / d31_d32 - basis_values[idx + 1] = (d3t - dt1) / d31_d32 + (d4t - dt2) / d42_d32 - basis_values[idx + 2] = 2 * dt2 / d42_d32 - return basis_values, idx + def _bilinear_xy_to_uv(self, x, y): + assert False, "Not implemented yet." -def test_spline_base_vec(knots=np.array((0.0, 0.0, 0.0, 1/3.0, 2/3.0, 1.0, 1.0, 1.0)), sparse=False): - """ - Test optimized version of spline base function - :param knots: numpy array of knots - :param sparse: is sparse matrix used - :return: - """ - - num = 100 - n_basf = len(knots) - 3 - y_coords = {} - for k in range(0, n_basf): - temp = {} - for i in range(0, num+1): - t_param = min(knots) + max(knots) * i / float(num) - if sparse is True: - temp[i] = spline_base_vec(knots, t_param, 0, sparse)[0].toarray()[0] - else: - temp[i] = spline_base_vec(knots, t_param, 0, sparse)[0] - y_coords[k] = temp - - diff_x = (max(knots) - min(knots)) / num - x_coord = [min(knots) + diff_x*i for i in range(0, num+1)] - - for temp in y_coords.values(): - plt.plot(x_coord, temp.values()) - plt.show() - + def eval(self, u, v): + z = self.z_surface.eval(u, v) + x, y = self.uv_to_xy(u, v) + return np.array( [x, y, z] ) + def eval_xy(self, x, y): + u, v = self.xy_to_uv(x, y) + z = self.z_surface.eval(u, v) + return np.array([x, y, z]) -def spline_base(knot_vec, basis_fnc_idx, t_param): - """ - Evaluate a second order bases function in 't_param'. - This function compute normalized blending function aka base function of B-Spline curve or surface. - This function implement some optimization. - :param knot_vec: knot vector - :param basis_fnc_idx: index of basis function - :param t_param: parameter t in interval <0, 1> - :return: value of basis function - """ - # When basis function has zero value at given interval, then return 0 - if t_param < knot_vec[basis_fnc_idx] or knot_vec[basis_fnc_idx+3] < t_param: - return 0.0 - try: - value = SB_CACHE[(tuple(knot_vec), basis_fnc_idx, t_param)] - except KeyError: - try: - kvn = KVN_CACHE[tuple(knot_vec)] - except KeyError: - knot_vec_len = len(knot_vec) - kvn = [0] * knot_vec_len - i = 0 - while i < knot_vec_len - 1: - if knot_vec[i] - knot_vec[i+1] != 0: - kvn[i] = 1.0 - i += 1 - KVN_CACHE[tuple(knot_vec)] = kvn - tks = [knot_vec[basis_fnc_idx + k] for k in range(0, 4)] - - value = 0.0 - if knot_vec[basis_fnc_idx] <= t_param <= knot_vec[basis_fnc_idx+1] and kvn[basis_fnc_idx] != 0: - value = (t_param - tks[0])**2 / ((tks[2] - tks[0]) * (tks[1] - tks[0])) - elif knot_vec[basis_fnc_idx+1] <= t_param <= knot_vec[basis_fnc_idx+2] and kvn[basis_fnc_idx+1] != 0: - value = ((t_param - tks[0]) * (tks[2] - t_param)) / ((tks[2] - tks[0]) * (tks[2] - tks[1])) + \ - ((t_param - tks[1]) * (tks[3] - t_param)) / ((tks[3] - tks[1]) * (tks[2] - tks[1])) - elif knot_vec[basis_fnc_idx+2] <= t_param <= knot_vec[basis_fnc_idx+3] and kvn[basis_fnc_idx+2] != 0: - value = (tks[3] - t_param)**2 / ((tks[3] - tks[1]) * (tks[3] - tks[2])) - SB_CACHE[(tuple(knot_vec), basis_fnc_idx, t_param)] = value - - return value - - -KNOT_VEC_CACHE = {} - - -def spline_surface(poles, u_param, v_param, u_knots, v_knots, u_mults, v_mults): - """ - Compute coordinate of one point at B-Surface (u and v degree is 2) - :param poles: matrix of "poles" - :param u_param: X coordinate in range <0, 1> - :param v_param: Y coordinate in range <0, 1> - :param u_knots: list of u knots - :param v_knots: list of v knots - :param u_mults: list of u multiplicities - :param v_mults: list of v multiplicities - :return: tuple of (x, y, z) coordinate of B-Spline surface - """ - # "Decompress" knot vectors using multiplicities - # e.g - # u_knots: (0.0, 0.5, 1.0) u_mults: (3, 1, 3) will be converted to - # _u_knot: (0.0, 0.0, 0.0, 0.5, 1.0, 1.0, 1.0) - _u_knots = [] - _v_knots = [] - try: - _u_knots = KNOT_VEC_CACHE[(u_knots, u_mults)] - except KeyError: - for idx, mult in enumerate(u_mults): - _u_knots.extend([u_knots[idx]] * mult) - KNOT_VEC_CACHE[(u_knots, u_mults)] = _u_knots - try: - _v_knots = KNOT_VEC_CACHE[(v_knots, v_mults)] - except KeyError: - for idx, mult in enumerate(v_mults): - _v_knots.extend([v_knots[idx]] * mult) - KNOT_VEC_CACHE[(v_knots, v_mults)] = _v_knots - u_n_basf = len(_u_knots) - 3 - v_n_basf = len(_v_knots) - 3 - uf_mat = [0.0] * u_n_basf - vf_mat = [0.0] * v_n_basf - # Pre-compute base values of functions - for k in range(0, u_n_basf): - uf_mat[k] = spline_base(_u_knots, k, u_param) - for k in range(0, v_n_basf): - vf_mat[k] = spline_base(_v_knots, k, v_param) - x_coord, y_coord, z_coord = 0.0, 0.0, 0.0 - # Compute point at B-Spline surface - for i in range(0, u_n_basf): - for j in range(0, v_n_basf): - base_i_j = uf_mat[i] * vf_mat[j] - x_coord += poles[i][j][0] * base_i_j - y_coord += poles[i][j][1] * base_i_j - z_coord += poles[i][j][2] * base_i_j - return x_coord, y_coord, z_coord -def transform_points(quad, terrain_data): - """ - Function computes corresponding (u,v) for (x,y) - :param terrain_data: matrix of 3D terrain data, N rows, 3 cols - :param quad: points defining quadrangle area (array) 2 rows 4 cols, - :return: transformed and cropped terrain points - """ - # if quad points are given counter clock wise, this is outer normal in XY plane - mat_n = np.empty_like(quad) - - mat_n[:, 0] = quad[:, 0] - quad[:, 3] - nt = mat_n[0, 0] - mat_n[0, 0] = -mat_n[1, 0] - mat_n[1, 0] = nt - mat_n[:, 0] = mat_n[:, 0] / la.norm(mat_n[:, 0]) - - for i in range(1, 4): - mat_n[:, i] = quad[:, i] - quad[:, i-1] - nt = mat_n[0, i] - mat_n[0, i] = -mat_n[1, i] - mat_n[1, i] = nt - mat_n[:, i] = mat_n[:, i] / la.norm(mat_n[:, i]) - - terrain_len = len(terrain_data) - - # Compute local coordinates and drop all points outside quadraangle - param_terrain = np.empty_like(terrain_data) - # indexing - d0 = (terrain_data[:, 0:2] - quad[:, 0].transpose()) - d2 = (terrain_data[:, 0:2] - quad[:, 2].transpose()) - d3 = (terrain_data[:, 0:2] - quad[:, 3].transpose()) - d0_n0 = d0 * mat_n[:, 0] - d0_n1 = d0 * mat_n[:, 1] - d2_n2 = d2 * mat_n[:, 2] - d3_n3 = d3 * mat_n[:, 3] - u = np.divide(d0_n0, d0_n0 + d2_n2) - v = np.divide(d0_n1, d0_n1 + d3_n3) - - h = -1 - for j in range(0,terrain_len): - if (u[j] >= 0.0) and (u[j] <= 1.0) and (v[j] >= 0.0) and (v[j] <= 1.0): - h += 1 - param_terrain[h, 0] = u[j] - param_terrain[h, 1] = v[j] - param_terrain[h, 2] = terrain_data[j, 2] - - param_terrain.resize(h+1, 3) - - uv = np.reshape(param_terrain[:, 0:2], 2*h+2).transpose() - - a = quad[:, 3] - quad[:, 2] - b = quad[:, 0] - quad[:, 1] - c = quad[:, 1] - quad[:, 2] - d = quad[:, 0] - quad[:, 3] - - ldiag = np.zeros([2 * h + 1, 1]) - diag = np.zeros([2 * h + 2, 1]) - udiag = np.zeros([2 * h + 1, 1]) - - # fixed point Newton iteration - for i in range(0, 1): # 1->5 - for j in range(0, h+1): - mat_j = compute_jacobi(uv[2 * j, 0], uv[2 * j + 1, 0], a, b, c, d, -1) - ldiag[2 * j, 0] = mat_j[1, 0] - diag[2 * j, 0] = mat_j[0, 0] - diag[2 * j + 1, 0] = mat_j[1, 1] - udiag[2 * j, 0] = mat_j[0, 1] - - mat_jg = scipy.sparse.diags([ldiag[:, 0], diag[:, 0], udiag[:, 0]], [-1, 0, 1], format="csr") - uv = uv - mat_jg.dot(uv) - - uv = uv.reshape([h + 1, 2]) - - # Tresholding of the refined coordinates - for j in range(0, h+1): - if uv[j, 0] < 0: - uv[:, 0] = 0 - elif uv[j, 0] > 1: - uv[j, 0] = 1 - elif uv[j, 1] < 0: - uv[:, 1] = 0 - elif uv[j, 1] > 1: - uv[j, 1] = 1 - - param_terrain[:, 0:2] = uv - - return param_terrain diff --git a/src/bspline_plot.py b/src/bspline_plot.py index e6b888c..74a4a65 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -82,3 +82,6 @@ def plot_surface_poles_3d(surface, fig_ax, **kwargs): """ x_poles, y_poles, z_poles = surface.poles[:, :, 0:3].reshape(-1, 3).T # remove weights and flatten nu, nv return fig_ax.scatter(x_poles, y_poles, z_poles, color='red', **kwargs) + + + diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 4d20bb0..0cf26ec 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -59,6 +59,18 @@ def test_eval(self): assert eq_basis.eval(2, 1.0) == 0.0 + def test_linear_poles(self): + eq_basis = bs.SplineBasis.make_equidistant(2, 4) + poles = eq_basis.make_linear_poles() + + t_vec = np.linspace(0.0, 1.0, 21) + x_vec = [] + for t in t_vec: + b_vals = np.array([ eq_basis.eval(i, t) for i in range(eq_basis.size) ]) + x = np.dot(b_vals, poles) + assert np.abs( x - t ) < 1e-15 + + class TestCurve: def plot_4p(self): @@ -75,20 +87,29 @@ def test_evaluate(self): #self.plot_4p() pass + # TODO: test rational curves, e.g. circle + + + + -class TestSurface: - def make_function_grid(self, fn, nu, nv): - X_grid = np.linspace(0, 1.0, nu) - Y_grid = np.linspace(0, 1.0, nv) - Y, X = np.meshgrid(Y_grid, X_grid) +def make_function_grid(fn, nu, nv): + X_grid = np.linspace(0, 1.0, nu) + Y_grid = np.linspace(0, 1.0, nv) + Y, X = np.meshgrid(Y_grid, X_grid) - points_uv = np.stack([X.ravel(), Y.ravel()], 1) - Z = np.apply_along_axis(fn, 1, points_uv) - points = np.stack([X.ravel(), Y.ravel(), Z], 1) + points_uv = np.stack([X.ravel(), Y.ravel()], 1) + Z = np.apply_along_axis(fn, 1, points_uv) + points = np.stack([X.ravel(), Y.ravel(), Z], 1) - return points.reshape( (nu, nv, 3) ) + return points.reshape( (nu, nv, 3) ) + + + + +class TestSurface: def plot_extrude(self): fig = plt.figure() @@ -113,7 +134,7 @@ def function(x): fig = plt.figure() ax = fig.gca(projection='3d') - poles = self.make_function_grid(function, 4, 5) + poles = make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles) @@ -126,8 +147,43 @@ def test_evaluate(self): from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt - self.plot_extrude() - self.plot_function() + #self.plot_extrude() + #self.plot_function() + + + # TODO: test rational surfaces, e.g. sphere + +class TestZ_Surface: + + + + + def plot_function_uv(self): + # function surface + def function(x): + return math.sin(x[0]*4) * math.cos(x[1]*4) + + fig = plt.figure() + ax = fig.gca(projection='3d') + + poles = make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) + + quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) + #quad = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) + z_surf = bs.Z_Surface(quad, surface_func) + full_surf = z_surf.make_full_surface() + + bs_plot.plot_surface_3d(z_surf, ax) + bs_plot.plot_surface_3d(full_surf, ax, color='red') + #bs_plot.plot_surface_poles_3d(surface_func, ax) + + plt.show() + def test_eval_uv(self): + self.plot_function_uv() + pass \ No newline at end of file From 06ea538107b46604ceb9e8981ddb835cc14752f9 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sat, 16 Sep 2017 14:39:22 +0200 Subject: [PATCH 16/67] Integrate GridSurface --- src/bspline.py | 265 +++++++++++++++++++++++++++++++++++++++--- src/bspline_plot.py | 4 +- tests/test_bspline.py | 74 +++++++++++- 3 files changed, 318 insertions(+), 25 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index fad4e2b..f075bfb 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -306,7 +306,13 @@ def eval(self, t): else: return np.inner(t_base_vec, self.poles[it: it + dt, :].T) - + def eval_array(self, t_points): + """ + Evaluate in array of t-points. + :param t_points: array N x float + :return: Numpy array N x D, D is dimension of the curve. + """ + return np.array( [ self.eval(t) for t in t_points] ) @@ -378,6 +384,13 @@ def eval(self, u, v): #print("val: {}".format(value.shape)) return np.inner( value, v_base_vec) + def eval_array(self, uv_points): + """ + Evaluate in array of t-points. + :param uv_points: numpy array N x [u, v] + :return: Numpy array N x D, D is dimension of the curve. + """ + return np.array( [ self.eval(u, v) for u, v in uv_points] ) @@ -405,6 +418,9 @@ def __init__(self, xy_quad, z_surface): self.v_basis = z_surface.v_basis self.dim = 3 + self.z_scale = 1.0 + self.z_shift = 0.0 + if len(xy_quad) == 3: # linear case self.xy_shift = xy_quad[0] @@ -437,48 +453,259 @@ def make_full_surface(self): u = basis[0].make_linear_poles() v = basis[1].make_linear_poles() V, U = np.meshgrid(v,u) - uv_poles = np.stack([U, V], axis=2) - xy_poles = np.apply_along_axis(lambda x: self.uv_to_xy(x[0], x[1]), 2, uv_poles) + uv_poles_vec = np.stack([U.ravel(), V.ravel()], axis=1) + xy_poles = self.uv_to_xy(uv_poles_vec).reshape(U.shape[0], U.shape[1], 2) poles = np.concatenate( (xy_poles, self.z_surface.poles), axis = 2 ) return Surface(basis, poles) - def _linear_uv_to_xy(self, u, v): - #assert uv_points.shape[0] == 2, "Size: {}".format(uv_points.shape) - uv_points = np.array([u, v]) - return self.mat_uv_to_xy.dot(uv_points) + self.xy_shift + + def transform(self, xy_mat, z_mat): + """ + Transform the surface by arbitrary XY linear transform and Z linear transform. + :param xy_mat: np array, 2 rows 3 cols, last column is xy shift + :param z_shift: [ z_scale, z_shift] + :return: None + """ + assert xy_mat.shape == (2, 3) + assert z_mat.shape == (2, ) + self.mat_uv_to_xy = xy_mat[0:2,0:2].dot( self.mat_uv_to_xy ) + self.xy_shift = xy_mat[0:2,0:2].dot( self.xy_shift ) + xy_mat[0:2, 2] + self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) + + self.z_scale *= z_mat[0] + self.z_shift = z_mat[0] * self.z_shift + z_mat[1] + + + + """ + def uv_to_xy(self, uv_points): + + Abstract method. Converts array of uv points to array of xy points. + :param uv_points: numpy array N x [u,v] + :return: numpy array N x [x,y] + """ + def _linear_uv_to_xy(self, uv_points): + assert uv_points.shape[0] == 2, "Size: {}".format(uv_points.shape) + return (self.mat_uv_to_xy.dot(uv_points).T + self.xy_shift).T - def _bilinear_uv_to_xy(self, u, v): - weights = np.array([ (1-u)*(1-v), (1-u)*v, u*(1-v), u*v ]) - return self.quad.T.dot( weights ) + def _bilinear_uv_to_xy(self, uv_points): + xy_list = [] + for u,v in uv_points: + weights = np.array([ (1-u)*(1-v), (1-u)*v, u*(1-v), u*v ]) + xy_list.append( self.quad.T.dot(weights) ) + return np.array(xy_list) - def _linear_xy_to_uv(self, x, y): + """ + def xy_to_uv(self, xy_points): + + Abstract method. Converts array of xy points to array of uv points. + :param xy_points: numpy array N x [x,y] + :return: numpy array N x [u,v] + """ + def _linear_xy_to_uv(self, xy_points): # assert xy_points.shape[0] == 2 - xy_points = np.array([x,y]) - return self.mat_xy_to_uv.dot((xy_points - self.xy_shift)) + assert xy_points.shape[0] == 2 + return self.mat_xy_to_uv.dot((xy_points.T - self.xy_shift).T) - def _bilinear_xy_to_uv(self, x, y): + def _bilinear_xy_to_uv(self, xy_points): assert False, "Not implemented yet." def eval(self, u, v): z = self.z_surface.eval(u, v) - x, y = self.uv_to_xy(u, v) + uv_points = np.array([[u, v]]) + x, y = self.uv_to_xy( uv_points )[0] return np.array( [x, y, z] ) - def eval_xy(self, x, y): - u, v = self.xy_to_uv(x, y) - z = self.z_surface.eval(u, v) - return np.array([x, y, z]) + def eval_array(self, uv_points): + z_points = self.z_surface.eval_array(uv_points) + xy_points = self.uv_to_xy(uv_points) + return np.concatenate( (xy_points, z_points), axis = 1) + def eval_xy_array(self, xy_points): + uv_points = self.xy_to_uv(xy_points) + z_points = self.z_surface.eval_array(uv_points) + return np.concatenate( (xy_points, z_points), axis = 1) + + def z_eval_array(self, uv_points): + z_points = self.z_surface.eval_array(uv_points) + return z_points + + def z_eval_xy_array(self, uv_points): + uv_points = self.xy_to_uv(xy_points) + z_points = self.z_surface.eval_array(uv_points) + return z_points + +class InvalidGridExc(Exception): + pass + + +class GridSurface: + """ + Surface given as bilinear interpolation of a regular grid of points. + TODO: This can be viewed as a degree 1 Z-function B-spline. Make it a common class for any degree?? + """ + step_tolerance = 1e-10 + + def __init__(self): + """ + Initialize point grid from numpy array. + :param grid: NxMx3 numpy array of NxM grid of #D coordinates + """ + self.grid=None + self.mat_xy_to_uv=None + self.mat_uv_to_xy=None + self.shift=None + self.shape = (0,0) + self.uv_step = (0,0) + def load(self, filename): + """ + Load the grid surface from file + :param filename: + :return: + """ + point_seq = np.loadtxt(filename) + assert min(point_seq.shape) > 1 + self.init_from_seq(point_seq.T) + + + def init_from_seq(self, point_seq): + """ + Get 2d transform matrix 2 rows 3 cols to map a grid of XY points to unit square + :param point_seq: numpy array N x 2 + :return: + """ + + assert point_seq.shape[0] == 3 + n_points = point_seq.shape[1] + point_seq_xy = point_seq[0:2,:] + + vtx_00 = point_seq_xy[0:2, 0] + vtx_du = point_seq_xy[0:2, 1] + + u_step = vtx_du - vtx_00 + for i in range(2, n_points): + step = point_seq_xy[:,i] - point_seq_xy[:,i-1] + if la.norm(u_step - step) > self.step_tolerance: + break + + vtx_dv = point_seq_xy[:,i] + v_step = vtx_dv - vtx_00 + + nu = i + nv = int(n_points / nu) + if not n_points == nu*nv: + raise InvalidGridExc("Not a M*N grid.") + + # check total range of the grid + vtx_10 = point_seq_xy[:, nu-1] + vtx_01 = point_seq_xy[:, -nu] + vtx_11 = point_seq_xy[:, -1] + u_range_0 = vtx_10 - vtx_00 + u_range_1 = vtx_11 - vtx_01 + v_range_0 = vtx_01 - vtx_00 + v_range_1 = vtx_11 - vtx_10 + + if not la.norm(u_range_0 - u_range_1) < self.step_tolerance or \ + not la.norm(v_range_0 - v_range_1) < self.step_tolerance: + raise InvalidGridExc("Grid XY envelope is not a parallelogram.") + + u_step = u_range_0 / (nu-1) + v_step = v_range_0 / (nv-1) + + # check regularity of the grid + for i in range(nu*nv): + pred_x = i - 1 + pred_y = i - nu + if i%nu == 0: + pred_x= -1 + if pred_x > 0 and not la.norm(point_seq_xy[:, i] - point_seq_xy[:, pred_x] - u_step) < 2*self.step_tolerance: + raise InvalidGridExc("Irregular grid in X direction, point %d"%i) + if pred_y > 0 and not la.norm(point_seq_xy[:, i] - point_seq_xy[:, pred_y] - v_step) < 2*self.step_tolerance: + raise InvalidGridExc("Irregular grid in Y direction, point %d"%i) + + quad = np.stack([vtx_00, vtx_01, vtx_10], axis = 0) + u_basis = SplineBasis.make_equidistant(1, nu-1) + v_basis = SplineBasis.make_equidistant(1, nv-1) + + self.grid = point_seq[2, :].reshape(nv, nu) + poles_z = np.transpose( point_seq[2, :].reshape(nv, nu, 1), axes = [1, 0, 2] ) + self.z_surface = Z_Surface(quad, Surface((u_basis, v_basis), poles_z) ) + + self.shape = (nu, nv) + self.uv_step = (1.0 / float(nu - 1), 1.0 / float(nv - 1)) + + + def get_xy_envelope(self): + """ + Returns the envelope of the surface domain in XY plane as a quadrilateral polygon. + :return: Polygon as a array of the XY points in conunter clockwise direction. + """ + return self.uv_to_xy(np.array([[0,0], [1,0], [1,1], [0,1]]).T) + + + + def xy_to_uv(self, xy_points): + """ + :param xy_points: numpy matrix 2 rows N cols + :return: matrix of UV coordinates + """ + return self.z_surface.xy_to_uv(xy_points) + + + def uv_to_xy(self, uv_points): + """ + :param xy_points: numpy matrix 2 rows N cols + :return: matrix of UV coordinates + """ + return self.z_surface.uv_to_xy(uv_points) + + + def eval_array(self, uv_points): + return self.z_surface.eval_array(uv_points) + + + def z_eval_xy_array(self, xy_points): + """ + Return np array of z-values. Optimized version. + :param points: np array N x 2 of XY cooridinates + :return: array of Z values + """ + assert xy_points.shape[0] == 2, "Size: {}".format(xy_points.shape) + uv_points = self.z_surface.xy_to_uv(xy_points) + return self.z_eval_array(uv_points) + + + def z_eval_array(self, uv_points): + """ + Return np array of z-values. Optimized version. + :param points: np array N x 2 of XY cooridinates + :return: array of Z values + """ + assert uv_points.shape[0] == 2 + + result = np.zeros(uv_points.shape[1]) + for i, uv in enumerate(uv_points.T): + iuv = np.floor(uv / self.uv_step) + iu = max(0, min(self.shape[0] - 2, int(iuv[0]))) + iv = max(0, min(self.shape[1] - 2, int(iuv[1]))) + iuv = np.array([iu, iv]) + + uv_loc = uv / self.uv_step - iuv + u_loc = np.array([1 - uv_loc[0], uv_loc[0]]) + v_loc = np.array([1 - uv_loc[1], uv_loc[1]]) + Z_mat = self.grid[iv: (iv + 2), iu: (iu + 2)] + result[i] = self.z_surface.z_scale*(v_loc.dot(Z_mat).dot(u_loc)) + self.z_surface.z_shift + return result diff --git a/src/bspline_plot.py b/src/bspline_plot.py index 74a4a65..6c843d8 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -60,9 +60,9 @@ def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): v_coord = np.linspace(v_basis.domain[0], v_basis.domain[1], n_points[1]) U, V = np.meshgrid(u_coord, v_coord) - points = np.vstack( [U.ravel(), V.ravel()] ) + points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) - xyz = np.array([surface.eval(point[0], point[1]) for point in points.T ]) + xyz = surface.eval_array(points) X, Y, Z = xyz.T X = X.reshape(U.shape) diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 0cf26ec..b20dea6 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -64,7 +64,6 @@ def test_linear_poles(self): poles = eq_basis.make_linear_poles() t_vec = np.linspace(0.0, 1.0, 21) - x_vec = [] for t in t_vec: b_vals = np.array([ eq_basis.eval(i, t) for i in range(eq_basis.size) ]) x = np.dot(b_vals, poles) @@ -96,6 +95,13 @@ def test_evaluate(self): def make_function_grid(fn, nu, nv): + """ + Make a grid of points on a graph of the function. + :param fn: fn( [x, y] ) -> z + :param nu: n-points in u-direction + :param nv: n-points in v-direction + :return: array of points: nu x nv x 3 + """ X_grid = np.linspace(0, 1.0, nu) Y_grid = np.linspace(0, 1.0, nv) Y, X = np.meshgrid(Y_grid, X_grid) @@ -157,7 +163,7 @@ def test_evaluate(self): class TestZ_Surface: - + # TODO: Compute max norm of the difference of two surfaces and assert that it is cose to zero. def plot_function_uv(self): @@ -185,5 +191,65 @@ def function(x): plt.show() def test_eval_uv(self): - self.plot_function_uv() - pass \ No newline at end of file + #self.plot_function_uv() + pass + + + +class TestPointGrid: + + @staticmethod + def function(x): + return math.sin(x[0]) * math.cos(x[1]) + + + def make_point_grid(self): + nu, nv = 5,6 + grid = make_function_grid(TestPointGrid.function, 5, 6).reshape(nu*nv, 3) + surf = bs.GridSurface() + surf.init_from_seq(grid.T) + return surf + + def check_surface(self, surf, xy_mat, xy_shift, z_mat): + """ + TODO: Make this a general function - evaluate a surface on a grid, use it also in other tests + to compare evaluation on the grid to the original function. Can be done after we have approximations. + """ + + nu, nv = 30, 40 + # surface on unit square + U = np.linspace(0.0, 1.0, nu) + V = np.linspace(0.0, 1.0, nv) + U_grid, V_grid = np.meshgrid(U,V) + + UV = np.vstack([U_grid.ravel(), V_grid.ravel()]) + XY = xy_mat.dot(UV).T + xy_shift + Z_grid = surf.z_eval_xy_array(XY.T) + eps = 0.0 + hx = 1.0 / surf.shape[0] + hy = 1.0 / surf.shape[1] + tol = 0.5* ( hx*hx + 2*hx*hy + hy*hy) + for xy, z_approx in zip(UV.T, Z_grid): + z_func = z_mat[0]*self.function(xy) + z_mat[1] + eps = max(eps, math.fabs( z_approx - z_func)) + assert math.fabs( z_approx - z_func) < tol + print("Max norm: ", eps, "Tol: ", tol) + + def test_grid_surface(self): + xy_mat = np.array([ [1.0, 0.0], [0.0, 1.0] ]) + xy_shift = np.array([0.0, 0.0 ]) + z_shift = np.array([1.0, 0.0]) + surface = self.make_point_grid() + + self.check_surface(surface, xy_mat, xy_shift, z_shift) + + # transformed surface + xy_mat = np.array([ [3.0, -3.0], [2.0, 2.0] ]) / math.sqrt(2) + xy_shift = np.array([[-2.0, 5.0 ]]) + z_shift = np.array([1.0, 1.3]) + + surface = self.make_point_grid() + surface.z_surface.transform(np.concatenate((xy_mat, xy_shift.T), axis=1), z_shift) + self.check_surface(surface, xy_mat, xy_shift, z_shift) + + From e53ee3ad0dc65911379a55616e34623a7f9c960a Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 17 Sep 2017 13:35:25 +0200 Subject: [PATCH 17/67] Import surface approximation from bapprox - reuse functions from SplineBasis - add optimized versions of basis evaluation - simplify approximation code - graphical tests --- src/bspline.py | 185 ++++++++++++++++---- src/bspline_approx.py | 374 ++++++++++++++++++++++++++++++++++++++++ tests/test_bs_approx.py | 46 +++++ tests/test_bspline.py | 124 +++++++++---- 4 files changed, 666 insertions(+), 63 deletions(-) create mode 100644 src/bspline_approx.py create mode 100644 tests/test_bs_approx.py diff --git a/src/bspline.py b/src/bspline.py index f075bfb..b71079c 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -138,8 +138,12 @@ def __init__(self, degree, knots): self.knots_idx_range = [self.degree, len(self.knots) - self.degree - 1] self.domain = self.knots[self.knots_idx_range] self.domain_size = self.domain[1] - self.domain[0] + self.n_intervals = self.size - self.degree # Number of basis functions. + if self.degree == 2: + self.eval_base_vector = self._eval_base_vector_deg_2 + self.eval_diff_base_vector = self._eval_diff_base_vector_deg_2 def pack_knots(self): last, mult = self.knots[0], 0 @@ -236,6 +240,7 @@ def fn_supp(self, i_base): """ return (self.knots[i_base], self.knots[i_base + self.degree + 1]) + def eval(self, i_base, t): """ :param i_base: Index of base function to evaluate. @@ -247,6 +252,28 @@ def eval(self, i_base, t): return 1.0 return self._basis(self.degree, i_base, t) + + def eval_diff(self, i_base, t): + assert 0 <= i_base < self.size + deg = self.degree + i = i_base + if i_base == self.size - 2 and t == self.domain[1]: + return - deg / (self.knots[i + deg + 1] - self.knots[i + 1]) + + if i_base == self.size - 1 and t == self.domain[1]: + return deg / (self.knots[i + deg] - self.knots[i]) + + diff = 0.0 + if i > 0: + B = self._basis(deg-1, i, t) + diff += deg * B / (self.knots[i + deg] - self.knots[i]) + if i < self.size - 1: + B = self._basis(deg-1, i + 1, t) + diff -= deg * B / (self.knots[i + deg + 1] - self.knots[i + 1]) + + return diff + + def make_linear_poles(self): """ Return poles of basis functions to get a f(x) = x. @@ -259,6 +286,77 @@ def make_linear_poles(self): return poles + def eval_base_vector(self, i_base, t): + values = [] + for ib in range(i_base, i_base + self.degree + 1): + values.append( self.eval(ib, t)) + return values + + + def eval_diff_base_vector(self, i_base, t): + values = [] + for ib in range(i_base, i_base + self.degree + 1): + values.append( self.eval_diff(ib, t)) + return values + + + def _eval_base_vector_deg_2(self, i_base, t): + """ + This function compute normalized blending function aka base function of B-Spline curve or surface. + :param knot_vec: + :param t_param: + :param order: (0: function value, 1: derivative function value) + :param sparse: + :return: + """ + + basis_values = np.zeros(3) + + tk1, tk2, tk3, tk4 = self.knots[i_base + 1 : i_base + 5] + + d31 = tk3 - tk1 + d32 = tk3 - tk2 + d42 = tk4 - tk2 + + dt1 = t - tk1 + dt2 = t - tk2 + d3t = tk3 - t + d4t = tk4 - t + + d31_d32 = d31 * d32 + d42_d32 = d42 * d32 + + basis_values[0] = (d3t * d3t) / d31_d32 + basis_values[1] = ((dt1 * d3t) / d31_d32) + ((dt2 * d4t) / d42_d32) + basis_values[2] = (dt2 * dt2) / d42_d32 + + return basis_values + + + def _eval_diff_base_vector_deg_2(self, i_base, t): + + basis_values = np.zeros(3) + + tk1, tk2, tk3, tk4 = self.knots[i_base + 1: i_base + 5] + + d31 = tk3 - tk1 + d32 = tk3 - tk2 + d42 = tk4 - tk2 + + dt1 = t - tk1 + dt2 = t - tk2 + d3t = tk3 - t + d4t = tk4 - t + + d31_d32 = d31 * d32 + d42_d32 = d42 * d32 + + basis_values[0] = -2 * d3t / d31_d32 + basis_values[1] = (d3t - dt1) / d31_d32 + (d4t - dt2) / d42_d32 + basis_values[2] = 2 * dt2 / d42_d32 + + return basis_values + class Curve: @@ -408,8 +506,8 @@ def __init__(self, xy_quad, z_surface): for the bilinear UV -> XY mapping. :param xy_quad: np array N x 2 Four or three points, determining bilinear or linear mapping, respectively. - Four points giving XY coordinates for the uv corners: (0,0), (0,1), (1,0), (1,1) - Three points giving XY coordinates for the uv corners: (0,0), (0,1), (1,0) + Four points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0), (1,1) + Three points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0) :param z_surface: !D Surface object. """ assert z_surface.dim == 1 @@ -423,9 +521,9 @@ def __init__(self, xy_quad, z_surface): if len(xy_quad) == 3: # linear case - self.xy_shift = xy_quad[0] - v_vec = xy_quad[1] - xy_quad[0] - u_vec = xy_quad[2] - xy_quad[0] + self.xy_shift = xy_quad[1] + v_vec = xy_quad[0] - xy_quad[1] + u_vec = xy_quad[2] - xy_quad[1] self.mat_uv_to_xy = np.column_stack((u_vec, v_vec)) self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) @@ -486,14 +584,15 @@ def uv_to_xy(self, uv_points): :return: numpy array N x [x,y] """ def _linear_uv_to_xy(self, uv_points): - assert uv_points.shape[0] == 2, "Size: {}".format(uv_points.shape) - return (self.mat_uv_to_xy.dot(uv_points).T + self.xy_shift).T + assert uv_points.shape[1] == 2, "Size: {}".format(uv_points.shape) + return ( np.dot(uv_points, self.mat_uv_to_xy.T) + self.xy_shift) def _bilinear_uv_to_xy(self, uv_points): + assert uv_points.shape[1] == 2, "Size: {}".format(uv_points.shape) xy_list = [] for u,v in uv_points: - weights = np.array([ (1-u)*(1-v), (1-u)*v, u*(1-v), u*v ]) + weights = np.array([ (1-u)*v, (1-u)*(1-v), u*(1-v), u*v ]) xy_list.append( self.quad.T.dot(weights) ) return np.array(xy_list) @@ -506,11 +605,12 @@ def xy_to_uv(self, xy_points): """ def _linear_xy_to_uv(self, xy_points): # assert xy_points.shape[0] == 2 - assert xy_points.shape[0] == 2 - return self.mat_xy_to_uv.dot((xy_points.T - self.xy_shift).T) + assert xy_points.shape[1] == 2 + return np.dot((xy_points - self.xy_shift), self.mat_xy_to_uv.T) def _bilinear_xy_to_uv(self, xy_points): + assert xy_points.shape[1] == 2 assert False, "Not implemented yet." @@ -535,12 +635,12 @@ def eval_xy_array(self, xy_points): def z_eval_array(self, uv_points): z_points = self.z_surface.eval_array(uv_points) - return z_points + return z_points.reshape(-1) - def z_eval_xy_array(self, uv_points): + def z_eval_xy_array(self, xy_points): uv_points = self.xy_to_uv(xy_points) z_points = self.z_surface.eval_array(uv_points) - return z_points + return z_points.reshape(-1) class InvalidGridExc(Exception): pass @@ -632,24 +732,29 @@ def init_from_seq(self, point_seq): if pred_y > 0 and not la.norm(point_seq_xy[:, i] - point_seq_xy[:, pred_y] - v_step) < 2*self.step_tolerance: raise InvalidGridExc("Irregular grid in Y direction, point %d"%i) - quad = np.stack([vtx_00, vtx_01, vtx_10], axis = 0) - u_basis = SplineBasis.make_equidistant(1, nu-1) - v_basis = SplineBasis.make_equidistant(1, nv-1) + #self.uv_to_xy(np.array([[0, 1], [0, 0], [1, 0], [1, 1]]).T) + self.quad = np.stack([vtx_01, vtx_00, vtx_10, vtx_11], axis = 0) + # Envelope quad - polygon, oriented counter clockwise. - self.grid = point_seq[2, :].reshape(nv, nu) - poles_z = np.transpose( point_seq[2, :].reshape(nv, nu, 1), axes = [1, 0, 2] ) - self.z_surface = Z_Surface(quad, Surface((u_basis, v_basis), poles_z) ) + self.grid_z = point_seq[2, :].reshape(nv, nu) + # grid of original Z values, format as matrix self.shape = (nu, nv) + # Grid shape. + self.uv_step = (1.0 / float(nu - 1), 1.0 / float(nv - 1)) + # Grid step in u, v direction respectively. + + self.points_xyz = point_seq.T + # Original sequance of XYZ points + + u_basis = SplineBasis.make_equidistant(1, nu-1) + v_basis = SplineBasis.make_equidistant(1, nv-1) + poles_z = np.transpose( point_seq[2, :].reshape(nv, nu, 1), axes = [1, 0, 2] ) + self.z_surface = Z_Surface(self.quad[0:3], Surface((u_basis, v_basis), poles_z) ) + # related bilinear Z-surface, all evaluations just call this object. - def get_xy_envelope(self): - """ - Returns the envelope of the surface domain in XY plane as a quadrilateral polygon. - :return: Polygon as a array of the XY points in conunter clockwise direction. - """ - return self.uv_to_xy(np.array([[0,0], [1,0], [1,1], [0,1]]).T) @@ -679,7 +784,7 @@ def z_eval_xy_array(self, xy_points): :param points: np array N x 2 of XY cooridinates :return: array of Z values """ - assert xy_points.shape[0] == 2, "Size: {}".format(xy_points.shape) + assert xy_points.shape[1] == 2, "Size: {}".format(xy_points.shape) uv_points = self.z_surface.xy_to_uv(xy_points) return self.z_eval_array(uv_points) @@ -691,10 +796,10 @@ def z_eval_array(self, uv_points): :return: array of Z values """ - assert uv_points.shape[0] == 2 + assert uv_points.shape[1] == 2 - result = np.zeros(uv_points.shape[1]) - for i, uv in enumerate(uv_points.T): + result = np.zeros(uv_points.shape[0]) + for i, uv in enumerate(uv_points): iuv = np.floor(uv / self.uv_step) iu = max(0, min(self.shape[0] - 2, int(iuv[0]))) iv = max(0, min(self.shape[1] - 2, int(iuv[1]))) @@ -703,7 +808,7 @@ def z_eval_array(self, uv_points): uv_loc = uv / self.uv_step - iuv u_loc = np.array([1 - uv_loc[0], uv_loc[0]]) v_loc = np.array([1 - uv_loc[1], uv_loc[1]]) - Z_mat = self.grid[iv: (iv + 2), iu: (iu + 2)] + Z_mat = self.grid_z[iv: (iv + 2), iu: (iu + 2)] result[i] = self.z_surface.z_scale*(v_loc.dot(Z_mat).dot(u_loc)) + self.z_surface.z_shift return result @@ -711,6 +816,26 @@ def z_eval_array(self, uv_points): +def make_function_grid(fn, nu, nv): + """ + Make a grid of points on a graph of the function. + :param fn: fn( [x, y] ) -> z + :param nu: n-points in u-direction + :param nv: n-points in v-direction + :return: array of points: nu x nv x 3 + """ + X_grid = np.linspace(0, 1.0, nu) + Y_grid = np.linspace(0, 1.0, nv) + Y, X = np.meshgrid(Y_grid, X_grid) + + points_uv = np.stack([X.ravel(), Y.ravel()], 1) + Z = np.apply_along_axis(fn, 1, points_uv) + points = np.stack([X.ravel(), Y.ravel(), Z], 1) + + return points.reshape( (nu, nv, 3) ) + + + diff --git a/src/bspline_approx.py b/src/bspline_approx.py new file mode 100644 index 0000000..bafde5a --- /dev/null +++ b/src/bspline_approx.py @@ -0,0 +1,374 @@ +""" +Collection of functions to produce Bapline curves and surfaces as approximation of various analytical curves and surfaces. +""" + +import bspline as bs +import numpy as np +import math +import time +#import numpy.linalg +import scipy.sparse +import scipy.sparse.linalg + + +""" +Approximation methods for B/splines of degree 2. + +""" +def plane_surface(vtxs): + """ + Returns B-spline surface of a plane given by 3 points. + We retun also list of UV coordinates of the given points. + :param vtxs: List of tuples (X,Y,Z) + :return: ( Surface, vtxs_uv ) + """ + assert len(vtxs) == 3, "n vtx: {}".format(len(vtxs)) + vtxs.append( (0,0,0) ) + vtxs = np.array(vtxs) + vv = vtxs[1] + vtxs[2] - vtxs[0] + vtx4 = [ vtxs[0], vtxs[1], vv, vtxs[2]] + (surf, vtxs_uv) = bilinear_surface(vtx4) + return (surf, [ vtxs_uv[0], vtxs_uv[1], vtxs_uv[3] ]) + + + +def bilinear_surface(vtxs): + """ + Returns B-spline surface of a bilinear surface given by 4 corner points. + We retun also list of UV coordinates of the given points. + :param vtxs: List of tuples (X,Y,Z) + :return: ( Surface, vtxs_uv ) + """ + assert len(vtxs) == 4, "n vtx: {}".format(len(vtxs)) + vtxs = np.array(vtxs) + def mid(*idx): + return np.mean( vtxs[list(idx)], axis=0) + + # v - direction v0 -> v2 + # u - direction v0 -> v1 + poles = [ [vtxs[0], mid(0, 3), vtxs[3]], + [mid(0,1), mid(0,1,2,3), mid(2,3)], + [vtxs[1], mid(1,2), vtxs[2]] + ] + knots = [(0.0, 3), (1.0, 3)] + basis = bs.SplineBasis(2, knots) + surface = bs.Surface((basis, basis), poles) + vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] + return (surface, vtxs_uv) + + + + +def _line_data(vtxs, overhang=0.0): + ''' + :param vtxs: List of tuples (X,Y) or (X,Y,Z) + :return: + ''' + assert len(vtxs) == 2 + vtxs = np.array(vtxs) + mid = np.mean(vtxs, axis=0) + poles = [ vtxs[0], mid, vtxs[1] ] + knots = [(0.0+overhang, 3), (1.0-overhang, 3)] + basis = bs.SplineBasis(2, knots) + return (basis, poles) + + + +def line_2d(vtxs): + """ + Return B-spline approximation of line from two 2d points + :param vtxs: [ (X0, Y0), (X1, Y1) ] + :return: Curve2D + """ + return bs.Curve( *_line_data(vtxs)) + + + +def line_3d(vtxs): + """ + Return B-spline approximation of line from two 3d points + :param vtxs: [ (X0, Y0, Z0), (X1, Y1, Z0) ] + :return: Curve2D + """ + return bs.Curve(*_line_data(vtxs)) + + + +def from_grid(grid_surface, nuv, **kwargs): + """ + Make a Z_Surface of degree 2 as an approximation of the GridSurface. + :param grid_surface: grid surface to approximate + :param (nu, nv) Prescribed number of poles in u and v directions. + :return: Z_surface object. + """ + approx = _SurfaceApprox(grid_surface, nuv, **kwargs) + return approx.get_approximation() + + + + + + + + +class _SurfaceApprox: + + + + def __init__(self, grid_surface, nuv, **kwargs): + self.grid_surf = grid_surface + self.u_basis = bs.SplineBasis.make_equidistant(2, nuv[0]) + self.v_basis = bs.SplineBasis.make_equidistant(2, nuv[1]) + self.regularization_weight = kwargs.get('reg_weight', 1.0) + + def get_approximation(self): + if not hasattr(self, 'z_surf'): + self.z_surf = self.approx_chol() + return self.z_surf + + + def approx_chol(self): + """ + This function tries to approximate terrain data with B-Spline surface patches + using Cholesky decomposition + :param terrain_data: matrix of 3D terrain data + :param quad: points defining quadrangle area (array) + :param u_knots: array of u knots + :param v_knots: array of v knots + :param sparse: if sparse matrix is used + :param filter_thresh: threshold of filter + :return: B-Spline patch + """ + + print('Transforming points to parametric space ...') + start_time = time.time() + points = self.grid_surf.points_xyz + points_uv = self.grid_surf.xy_to_uv( points[:, 0:2] ) + + + # remove points far from unit square + eps = 1.0e-15 + cut_min = np.array( [ -eps, -eps ]) + cut_max = np.array( [ 1+eps, 1+eps ]) + + in_idx = np.all(np.logical_and(cut_min < points_uv, points_uv <= cut_max), axis=1) + points_uv = points_uv[in_idx] + points_z = points[in_idx, 2][:,None] + + # snap to unit square + points_uv = np.maximum(points_uv, np.array([0.0, 0.0])) + points_uv = np.minimum(points_uv, np.array([1.0, 1.0])) + + + self.grid_uvz = np.concatenate((points_uv, points_z), axis=1) + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) + + # Own computation of approximation + print('Creating B matrix ...') + start_time = time.time() + b_mat, interval = self.build_ls_matrix() + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) + + print('Creating A matrix ...') + start_time = time.time() + a_mat = self.build_sparse_reg_matrix() + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) + + print('Computing B^T B matrix ...') + start_time = time.time() + bb_mat = b_mat.transpose() * b_mat + + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) + + + bb_norm = scipy.sparse.linalg.svds(bb_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, + maxiter=300, return_singular_vectors=False) + a_norm = scipy.sparse.linalg.svds(a_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, + maxiter=300, return_singular_vectors=False) + c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat + + g_vec = self.grid_uvz[:, 2] + b_vec = b_mat.transpose() * g_vec + + + print('Computing Z coordinates ...') + start_time = time.time() + z_vec = scipy.sparse.linalg.spsolve(c_mat, b_vec) + print(type(z_vec)) + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) + + + # print('Computing differences ...') + # start_time = time.time() + # diff = (numpy.matrix(b_mat * z_vec).transpose() - g_vec).tolist() + # diff = [item[0] for item in diff] + # end_time = time.time() + # print('Computed in {0} seconds.'.format(end_time - start_time)) + + # Construct Z-Surface + poles_z = z_vec.reshape(self.u_basis.size, self.v_basis.size, 1) + surface_z = bs.Surface((self.u_basis, self.v_basis), poles_z) + z_surf = bs.Z_Surface(self.grid_surf.quad, surface_z) + + return z_surf + + + + def build_ls_matrix(self): + """ + Construction of the matrix (B) of the system of linear algebraic + equations for control points of the 2th order B-spline surface + :param u_knots: + :param v_knots: + :param terrain: + :param sparse: + :return: + """ + u_n_basf = self.u_basis.size + v_n_basf = self.v_basis.size + n_points = self.grid_uvz.shape[0] + + n_uv_loc_nz = (self.u_basis.degree + 1) * (self.v_basis.degree + 1) + row = np.zeros(n_points * n_uv_loc_nz) + col = np.zeros(n_points * n_uv_loc_nz) + data = np.zeros(n_points * n_uv_loc_nz) + + nnz_b = 0 + + interval = np.empty((n_points, 2)) + + for idx in range(0, n_points): + u, v = self.grid_uvz[idx, 0:2] + iu = self.u_basis.find_knot_interval(u) + iv = self.u_basis.find_knot_interval(v) + u_base_vec = self.u_basis.eval_base_vector(iu, u) + v_base_vec = self.u_basis.eval_base_vector(iv, v) + # Hard-coded Kronecker product (problem based) + for n in range(0, 3): + data[nnz_b + 3 * n:nnz_b + 3 * (n + 1)] = v_base_vec[n] * u_base_vec + for m in range(0, 3): + col[nnz_b + (3 * n) + m] = (iv + n) * u_n_basf + iu + m + row[nnz_b:nnz_b + 9] = idx + nnz_b += 9 + + interval[idx][0] = iu + interval[idx][1] = iv + + mat_b = scipy.sparse.csr_matrix((data, (row, col)), shape=(n_points, u_n_basf * v_n_basf)) + + return mat_b, interval + + def _basis_in_q_points(self, basis): + n_int = basis.n_intervals + nq_points = len(self._q_points) + point_val = np.zeros((3, n_int * nq_points)) + d_point_val = np.zeros((3, n_int * nq_points)) + point_idx = np.zeros((n_int * nq_points, 1)) + q_point = np.zeros((n_int * nq_points, 1)) + + n = 0 + for i in range(n_int): + us = basis.knots[i + 2] + uil = basis.knots[i + 3] - basis.knots[i + 2] + for j in range(nq_points): + up = us + uil * self._q_points[j] + q_point[n] = up + idx = basis.find_knot_interval(up) + u_base_vec = basis.eval_base_vector(idx, up) + u_base_vec_diff = basis.eval_diff_base_vector(idx, up) + point_val[:, n] = u_base_vec + d_point_val[:, n] = u_base_vec_diff + point_idx[n] = idx + n += 1 + return point_val, d_point_val, point_idx, q_point + + def build_sparse_reg_matrix(self): + """ + Construction of the regularization matrix (A) to decrease variation of the terrain + B z = b ---> (B^T B + A)z = B^T b + :param u_knots: vector of v-knots + :param v_knots: vector of u-knots + :param quad: points defining quadrangle area (array) + :return: matrix + + - + """ + + #a = quad[:, 3] - quad[:, 2] + #b = quad[:, 0] - quad[:, 1] + #c = quad[:, 1] - quad[:, 2] + #d = quad[:, 0] - quad[:, 3] + + u_n_basf = self.u_basis.size + v_n_basf = self.v_basis.size + u_n_inter = self.u_basis.n_intervals + v_n_inter = self.v_basis.n_intervals + n_uv_loc_nz = (self.u_basis.degree + 1) * (self.v_basis.degree + 1) + + # TODO: use Gauss quadrature from scipy + # in fact for general degrees we should use different quadrature for u and different for v + self._q_points = [0, (0.5 - 1 / math.sqrt(20)), (0.5 + 1 / math.sqrt(20)), 1] + self._weights = [1.0 / 6, 5.0 / 6, 5.0 / 6, 1.0 / 6] + nq_points = len(self._q_points) + + # TODO: rename: u_vals, u_diffs, u_idxs, u_points + u_point_val, ud_point_val, u_point_idx, q_u_point = self._basis_in_q_points(self.u_basis) + v_point_val, vd_point_val, v_point_idx, q_v_point = self._basis_in_q_points(self.v_basis) + + # Matrix construction + # TODO: Assembly local dense blocks 9*9 and then put these nonzeroes into sparse matirx + # TODO: use numpy opperations to make assembly of local blocks readable, eliminate loops + colv = np.zeros(n_uv_loc_nz) + # TODO:rename data and data2 to something meqningful + data = np.zeros(n_uv_loc_nz) + data2 = np.zeros(n_uv_loc_nz) + row_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) + col_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) + data_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) + nnz_a = 0 + + + for i in range(v_n_inter): + for k in range(nq_points): + v_point = v_point_val[:, i * nq_points + k] + vd_point = vd_point_val[:, i * nq_points + k] + j_idx = v_point_idx[i * nq_points + k] + for l in range(u_n_inter): + for m in range(nq_points): + u_point = u_point_val[:, l * nq_points + m] + ud_point = ud_point_val[:, l * nq_points + m] + i_idx = u_point_idx[l * nq_points + m] + for n in range(0, 3): + # Hard-coded Kronecker product: vd = numpy.kron(vd_point, u_point) + data[3 * n:3 * (n + 1)] = vd_point[n] * u_point + # Hard-coded Kronecker product: ud = numpy.kron(v_point, ud_point) + data2[3 * n:3 * (n + 1)] = v_point[n] * ud_point + # column indices for data & data2 + for p in range(0, 3): + colv[3 * n + p] = (j_idx + n) * u_n_basf + i_idx + p + + # Hard-coded Outer product: + # Jacobian * weights[m] * weights[k] * (numpy.outer(ud, ud) + numpy.outer(vd, vd)) + #u_q = q_u_point[l * nq_points + m, 0] + #v_q = q_v_point[i * nq_points + k, 0] + + # jacobian for UV coordinates should be used + jac = 1.0 / u_n_inter / v_n_inter + coef = self._weights[m] * self._weights[k] * jac + for n in range(0, 9): + row_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv + col_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv[n] + data_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = coef * (data[n] * data + data2[n] * data2) + nnz_a += n_uv_loc_nz * n_uv_loc_nz + + mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), + shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() + + return mat_a + + diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py new file mode 100644 index 0000000..2139527 --- /dev/null +++ b/tests/test_bs_approx.py @@ -0,0 +1,46 @@ +import pytest +import numpy as np +import bspline as bs +import bspline_approx as bs_approx +import math +import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +import bspline_plot as bs_plot + +def function_sin_cos(x): + return math.sin(x[0] * 4) * math.cos(x[1] * 4) + + +class TestSurfaceApprox: + + def test_approx_grid(self): + points = bs.make_function_grid(function_sin_cos, 20, 30) + gs = bs.GridSurface() + gs.init_from_seq(points.reshape(-1, 3).T) + z_surf = bs_approx.from_grid(gs, (3,4) ) + + fig = plt.figure() + ax = fig.gca(projection='3d') + + bs_plot.plot_surface_3d(z_surf, ax) + + plt.show() + + def test_approx_transformed_grid(self): + points = bs.make_function_grid(function_sin_cos, 20, 30) + mat = np.array( [ [2.0, 1.0, 0.0 ], [1.0, 2.0, 0.0 ], [0.0, 0.0, 0.5 ]]) + points = np.dot(points, mat.T) + np.array([10, 20, -5.0]) + gs = bs.GridSurface() + gs.init_from_seq(points.reshape(-1, 3).T) + z_surf = bs_approx.from_grid(gs, (3,4) ) + + fig = plt.figure() + ax = fig.gca(projection='3d') + + bs_plot.plot_surface_3d(z_surf, ax) + + plt.show() + + # todo: transform grid points to uV in Gridsurface + # todo: keep quad in z_surf (OK) + # check that conversion to full keeps the transformation \ No newline at end of file diff --git a/tests/test_bspline.py b/tests/test_bspline.py index b20dea6..226b0ef 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -20,12 +20,9 @@ def test_find_knot_interval(self): assert eq_basis.find_knot_interval(1.0) == 99 def plot_basis(self, degree): - eq_basis = bs.SplineBasis.make_equidistant(degree, 4) - n_points = 401 - dx = (eq_basis.domain[1] - eq_basis.domain[0]) / (n_points -1) - x_coord = [eq_basis.domain[0] + dx * i for i in range(n_points)] + x_coord = np.linspace(eq_basis.domain[0], eq_basis.domain[1], n_points) for i_base in range(eq_basis.size): y_coord = [ eq_basis.eval(i_base, x) for x in x_coord ] @@ -70,6 +67,85 @@ def test_linear_poles(self): assert np.abs( x - t ) < 1e-15 + + def check_eval_vec(self, basis, i, t): + vec = basis.eval_base_vector(i, t) + for j in range(basis.degree + 1): + assert vec[j] == basis.eval(i + j, t) + + def plot_basis_vec(self, basis): + n_points = 401 + x_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + + y_coords = np.zeros( (basis.size, x_coord.shape[0]) ) + for i, x in enumerate(x_coord): + idx = basis.find_knot_interval(x) + y_coords[idx : idx + basis.degree + 1, i] = basis.eval_base_vector(idx, x) + + for i_base in range(basis.size): + plt.plot(x_coord, y_coords[i_base, :]) + + plt.show() + + + def test_eval_base_vec(self): + basis = bs.SplineBasis.make_equidistant(2, 4) + # self.plot_basis_vec(basis) + self.check_eval_vec(basis, 0, 0.1) + self.check_eval_vec(basis, 1, 0.3) + self.check_eval_vec(basis, 2, 0.6) + self.check_eval_vec(basis, 3, 0.8) + self.check_eval_vec(basis, 3, 1.0) + + + basis = bs.SplineBasis.make_equidistant(3, 4) + # self.plot_basis_vec(basis) + self.check_eval_vec(basis, 0, 0.1) + self.check_eval_vec(basis, 1, 0.3) + self.check_eval_vec(basis, 2, 0.6) + self.check_eval_vec(basis, 3, 0.8) + self.check_eval_vec(basis, 3, 1.0) + + + + def check_diff_vec(self, basis, i, t): + vec = basis.eval_diff_base_vector(i, t) + for j in range(basis.degree + 1): + assert np.abs(vec[j] - basis.eval_diff(i + j, t)) < 1e-15 + + def plot_basis_diff(self, basis): + n_points = 401 + x_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + + y_coords = np.zeros( (basis.size, x_coord.shape[0]) ) + for i, x in enumerate(x_coord): + idx = basis.find_knot_interval(x) + y_coords[idx : idx + basis.degree + 1, i] = basis.eval_diff_base_vector(idx, x) + + for i_base in range(basis.size): + plt.plot(x_coord, y_coords[i_base, :]) + + plt.show() + + + def test_eval_diff_base_vec(self): + basis = bs.SplineBasis.make_equidistant(2, 4) + # self.plot_basis_diff(basis) + self.check_diff_vec(basis, 0, 0.1) + self.check_diff_vec(basis, 1, 0.3) + self.check_diff_vec(basis, 2, 0.6) + self.check_diff_vec(basis, 3, 0.8) + self.check_diff_vec(basis, 3, 1.0) + + basis = bs.SplineBasis.make_equidistant(3, 4) + # self.plot_basis_diff(basis) + self.check_diff_vec(basis, 0, 0.1) + self.check_diff_vec(basis, 1, 0.3) + self.check_diff_vec(basis, 2, 0.6) + self.check_diff_vec(basis, 3, 0.8) + self.check_diff_vec(basis, 3, 1.0) + + class TestCurve: def plot_4p(self): @@ -83,7 +159,7 @@ def plot_4p(self): plt.show() def test_evaluate(self): - #self.plot_4p() + # self.plot_4p() pass # TODO: test rational curves, e.g. circle @@ -94,26 +170,6 @@ def test_evaluate(self): -def make_function_grid(fn, nu, nv): - """ - Make a grid of points on a graph of the function. - :param fn: fn( [x, y] ) -> z - :param nu: n-points in u-direction - :param nv: n-points in v-direction - :return: array of points: nu x nv x 3 - """ - X_grid = np.linspace(0, 1.0, nu) - Y_grid = np.linspace(0, 1.0, nv) - Y, X = np.meshgrid(Y_grid, X_grid) - - points_uv = np.stack([X.ravel(), Y.ravel()], 1) - Z = np.apply_along_axis(fn, 1, points_uv) - points = np.stack([X.ravel(), Y.ravel(), Z], 1) - - return points.reshape( (nu, nv, 3) ) - - - class TestSurface: @@ -140,7 +196,7 @@ def function(x): fig = plt.figure() ax = fig.gca(projection='3d') - poles = make_function_grid(function, 4, 5) + poles = bs.make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles) @@ -153,8 +209,8 @@ def test_evaluate(self): from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt - #self.plot_extrude() - #self.plot_function() + # self.plot_extrude() + # self.plot_function() # TODO: test rational surfaces, e.g. sphere @@ -174,7 +230,7 @@ def function(x): fig = plt.figure() ax = fig.gca(projection='3d') - poles = make_function_grid(function, 4, 5) + poles = bs.make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) @@ -205,7 +261,7 @@ def function(x): def make_point_grid(self): nu, nv = 5,6 - grid = make_function_grid(TestPointGrid.function, 5, 6).reshape(nu*nv, 3) + grid = bs.make_function_grid(TestPointGrid.function, 5, 6).reshape(nu*nv, 3) surf = bs.GridSurface() surf.init_from_seq(grid.T) return surf @@ -224,13 +280,15 @@ def check_surface(self, surf, xy_mat, xy_shift, z_mat): UV = np.vstack([U_grid.ravel(), V_grid.ravel()]) XY = xy_mat.dot(UV).T + xy_shift - Z_grid = surf.z_eval_xy_array(XY.T) + Z_grid = surf.z_eval_xy_array(XY) eps = 0.0 hx = 1.0 / surf.shape[0] hy = 1.0 / surf.shape[1] tol = 0.5* ( hx*hx + 2*hx*hy + hy*hy) - for xy, z_approx in zip(UV.T, Z_grid): - z_func = z_mat[0]*self.function(xy) + z_mat[1] + + uvz = np.concatenate( (UV.T, Z_grid[:, None]), axis = 1) + for u, v, z_approx in uvz: + z_func = z_mat[0]*self.function([u,v]) + z_mat[1] eps = max(eps, math.fabs( z_approx - z_func)) assert math.fabs( z_approx - z_func) < tol print("Max norm: ", eps, "Tol: ", tol) From f8759049950264d9321192349ab921d4def4eeb8 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 17 Sep 2017 15:35:31 +0200 Subject: [PATCH 18/67] Fix find_knots_interval --- src/bspline.py | 32 +++----------------------------- tests/test_bspline.py | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index b71079c..91b98f7 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -160,7 +160,7 @@ def pack_knots(self): def find_knot_interval(self, t): """ Find the first non-empty knot interval containing the value 't'. - i.e. knots[i] <= t <= knots[i+1], where knots[i] < knots[i+1] + i.e. knots[i] <= t < knots[i+1], where knots[i] < knots[i+1] Returns I = i - degree, which is the index of the first basis function nonzero in 't'. @@ -168,35 +168,9 @@ def find_knot_interval(self, t): :return: I """ assert self.knots[0] <= t <= self.knots[-1] + idx = np.searchsorted(self.knots, [t], side='right')[0] - 1 - self.degree + return min(idx, self.n_intervals - 1) # deals with t == self.knots[-1] - """ - This function try to find index for given t_param in knot_vec that - is covered by all (3) base functions. - :param self.knots: - :param t: - :return: - """ - - # get range without multiplicities - mn = self.knots_idx_range[0] - mx = self.knots_idx_range[1] - diff = mx - mn - - while diff > 1: - # estimate for equidistant knots - t_01 = (t - self.knots[mn]) / self.domain_size - est = int(t_01 * diff + mn) - if t > self.knots[est]: - if mn == est : - break - mn = est - else: - if mx == est: - mn = mx - break - mx = est - diff = mx - mn - return min(mn, self.knots_idx_range[1] - 1) - self.degree def _basis(self, deg, idx, t): """ diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 226b0ef..9d05aa1 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -11,14 +11,22 @@ def test_find_knot_interval(self): eq_basis = bs.SplineBasis.make_equidistant(2, 100) assert eq_basis.find_knot_interval(0.0) == 0 assert eq_basis.find_knot_interval(0.001) == 0 - assert eq_basis.find_knot_interval(0.01) == 0 + assert eq_basis.find_knot_interval(0.01) == 1 assert eq_basis.find_knot_interval(0.011) == 1 assert eq_basis.find_knot_interval(0.5001) == 50 assert eq_basis.find_knot_interval(1.0 - 0.011) == 98 - assert eq_basis.find_knot_interval(1.0 - 0.01) == 98 + assert eq_basis.find_knot_interval(1.0 - 0.01) == 99 assert eq_basis.find_knot_interval(1.0 - 0.001) == 99 assert eq_basis.find_knot_interval(1.0) == 99 + knots = np.array([0, 0, 0, 0.1880192, 0.24545785, 0.51219762, 0.82239001, 1., 1. , 1.]) + basis = bs.SplineBasis(2, knots) + for interval in range(2, 7): + xx = np.linspace(knots[interval], knots[interval+1], 10) + for j, x in enumerate(xx[:-1]): + i_found = basis.find_knot_interval(x) + assert i_found == interval - 2, "i_found: {} i: {} j: {} x: {} ".format(i_found, interval-2, j, x) + def plot_basis(self, degree): eq_basis = bs.SplineBasis.make_equidistant(degree, 4) n_points = 401 @@ -37,6 +45,18 @@ def test_eval(self): #self.plot_basis(2) #self.plot_basis(3) + knots = np.array([0, 0, 0, 0.1880192, 0.24545785, 0.51219762, 0.82239001, 1., 1. , 1.]) + basis = bs.SplineBasis(2, knots) + n_points = 100 + x_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + + for i_base in range(basis.size): + y_coord = [ basis.eval(i_base, x) for x in x_coord ] + plt.plot(x_coord, y_coord) + + plt.show() + + eq_basis = bs.SplineBasis.make_equidistant(0, 2) assert eq_basis.eval(0, 0.0) == 1.0 assert eq_basis.eval(1, 0.0) == 0.0 From bd7f131133803d7e711619d96614471b9a288b5d Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 17 Sep 2017 15:35:44 +0200 Subject: [PATCH 19/67] curve approx --- src/bspline_approx.py | 17 ++++++++++++++++- tests/test_bs_approx.py | 32 +++++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index bafde5a..b030326 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -9,6 +9,7 @@ #import numpy.linalg import scipy.sparse import scipy.sparse.linalg +import scipy.interpolate """ @@ -94,7 +95,7 @@ def line_3d(vtxs): -def from_grid(grid_surface, nuv, **kwargs): +def surface_from_grid(grid_surface, nuv, **kwargs): """ Make a Z_Surface of degree 2 as an approximation of the GridSurface. :param grid_surface: grid surface to approximate @@ -105,6 +106,20 @@ def from_grid(grid_surface, nuv, **kwargs): return approx.get_approximation() +def curve_from_grid(points, **kwargs): + """ + Make a Curve (of degree 3) as an approximation of a sequence of points. + :param points - N x D array, D is dimension + :param nt Prescribed number of poles of the resulting spline. + :return: Curve object. + """ + deg = kwargs.get('degree', 3) + tol = kwargs.get('tol', 0.01) + tck = scipy.interpolate.splprep(points.T, k=deg, s=tol)[0] + knots, poles, degree = tck + basis = bs.SplineBasis(degree, knots) + curve = bs.Curve(basis, np.array(poles).T) + return curve diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 2139527..8600eb9 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -12,12 +12,13 @@ def function_sin_cos(x): class TestSurfaceApprox: + # todo: numerical test - def test_approx_grid(self): + def plot_approx_grid(self): points = bs.make_function_grid(function_sin_cos, 20, 30) gs = bs.GridSurface() gs.init_from_seq(points.reshape(-1, 3).T) - z_surf = bs_approx.from_grid(gs, (3,4) ) + z_surf = bs_approx.surface_from_grid(gs, (3,4) ) fig = plt.figure() ax = fig.gca(projection='3d') @@ -26,13 +27,13 @@ def test_approx_grid(self): plt.show() - def test_approx_transformed_grid(self): + def plot_approx_transformed_grid(self): points = bs.make_function_grid(function_sin_cos, 20, 30) mat = np.array( [ [2.0, 1.0, 0.0 ], [1.0, 2.0, 0.0 ], [0.0, 0.0, 0.5 ]]) points = np.dot(points, mat.T) + np.array([10, 20, -5.0]) gs = bs.GridSurface() gs.init_from_seq(points.reshape(-1, 3).T) - z_surf = bs_approx.from_grid(gs, (3,4) ) + z_surf = bs_approx.surface_from_grid(gs, (3,4) ) fig = plt.figure() ax = fig.gca(projection='3d') @@ -41,6 +42,23 @@ def test_approx_transformed_grid(self): plt.show() - # todo: transform grid points to uV in Gridsurface - # todo: keep quad in z_surf (OK) - # check that conversion to full keeps the transformation \ No newline at end of file + def test_surface_approx(self): + #self.plot_approx_grid() + #self.plot_approx_transformed_grid() + pass + + +class TestCurveApprox: + + def test_approx_2d(self): + x_vec = np.linspace(1.1, 3.0, 100) + y_vec = np.array([ np.sin(10*x) for x in x_vec ]) + points = np.stack( (x_vec, y_vec), axis=1) + curve = bs_approx.curve_from_grid(points) + + bs_plot.plot_curve_2d(curve, 1000) + bs_plot.plot_curve_poles_2d(curve) + + + plt.plot(x_vec, y_vec, color='green') + plt.show() \ No newline at end of file From 666b5a51a1fe07b60d916754a171c815ad18e5fd Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 18 Sep 2017 15:47:00 +0200 Subject: [PATCH 20/67] Fixes: packed_knots, simple spline extrapolation, aabb - aabb for curve - fix pock_knoots - allow evaluation of the basis out of the spline domain - allow four point Z_surface quad with linear transform - fix Z-surface transform of Z coords --- src/bspline.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 91b98f7..100889a 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -132,7 +132,7 @@ def __init__(self, degree, knots): for i in range(self.degree): assert knots[i] == knots[i+1] assert knots[-i-1] == knots[-i-2] - self.knots = knots + self.knots = np.array(knots) self.size = len(self.knots) - self.degree -1 self.knots_idx_range = [self.degree, len(self.knots) - self.degree - 1] @@ -154,6 +154,7 @@ def pack_knots(self): else: packed_knots.append( (last, mult) ) last, mult = q, 1 + packed_knots.append((last,mult)) return packed_knots @@ -167,9 +168,11 @@ def find_knot_interval(self, t): :param t: float, must be within knots limits. :return: I """ - assert self.knots[0] <= t <= self.knots[-1] - idx = np.searchsorted(self.knots, [t], side='right')[0] - 1 - self.degree - return min(idx, self.n_intervals - 1) # deals with t == self.knots[-1] + idx = np.searchsorted(self.knots[self.degree: -self.degree -1], [t], side='right')[0] - 1 + if idx < 0 or idx > self.n_intervals - 1: + print("Warning: evaluation out of spline domain; t: {} min: {} max: {}".format(t, self.knots[0], self.knots[-1])) + + return max(min(idx, self.n_intervals - 1), 0) # deals with t == self.knots[-1] def _basis(self, deg, idx, t): @@ -386,6 +389,8 @@ def eval_array(self, t_points): """ return np.array( [ self.eval(t) for t in t_points] ) + def aabb(self): + return (np.amin(self.poles, axis=0), np.amax(self.poles, axis=0)) class Surface: @@ -462,6 +467,7 @@ def eval_array(self, uv_points): :param uv_points: numpy array N x [u, v] :return: Numpy array N x D, D is dimension of the curve. """ + assert uv_points.shape[1] == 2 return np.array( [ self.eval(u, v) for u, v in uv_points] ) @@ -490,10 +496,8 @@ def __init__(self, xy_quad, z_surface): self.v_basis = z_surface.v_basis self.dim = 3 - self.z_scale = 1.0 - self.z_shift = 0.0 - - if len(xy_quad) == 3: + if len(xy_quad) == 3 or \ + np.allclose(xy_quad[3], xy_quad[0] + xy_quad[2] - xy_quad[1]): # linear case self.xy_shift = xy_quad[1] v_vec = xy_quad[0] - xy_quad[1] @@ -541,12 +545,16 @@ def transform(self, xy_mat, z_mat): """ assert xy_mat.shape == (2, 3) assert z_mat.shape == (2, ) + + + self.mat_uv_to_xy = xy_mat[0:2,0:2].dot( self.mat_uv_to_xy ) self.xy_shift = xy_mat[0:2,0:2].dot( self.xy_shift ) + xy_mat[0:2, 2] self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) - self.z_scale *= z_mat[0] - self.z_shift = z_mat[0] * self.z_shift + z_mat[1] + # apply z-transfrom directly to the poles + self.z_surface.poles *= z_mat[0] + self.z_surface.poles += z_mat[1] @@ -596,6 +604,7 @@ def eval(self, u, v): def eval_array(self, uv_points): + assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) xy_points = self.uv_to_xy(uv_points) return np.concatenate( (xy_points, z_points), axis = 1) @@ -608,6 +617,7 @@ def eval_xy_array(self, xy_points): return np.concatenate( (xy_points, z_points), axis = 1) def z_eval_array(self, uv_points): + assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) return z_points.reshape(-1) @@ -623,8 +633,8 @@ class InvalidGridExc(Exception): class GridSurface: """ Surface given as bilinear interpolation of a regular grid of points. - TODO: This can be viewed as a degree 1 Z-function B-spline. Make it a common class for any degree?? """ + # TODO: calling transform lose mapping between original point grid and unit square, approximation can not be performed step_tolerance = 1e-10 def __init__(self): @@ -707,7 +717,7 @@ def init_from_seq(self, point_seq): raise InvalidGridExc("Irregular grid in Y direction, point %d"%i) #self.uv_to_xy(np.array([[0, 1], [0, 0], [1, 0], [1, 1]]).T) - self.quad = np.stack([vtx_01, vtx_00, vtx_10, vtx_11], axis = 0) + self.quad = quad = np.stack([vtx_01, vtx_00, vtx_10, vtx_11], axis = 0) # Envelope quad - polygon, oriented counter clockwise. self.grid_z = point_seq[2, :].reshape(nv, nu) @@ -725,11 +735,18 @@ def init_from_seq(self, point_seq): u_basis = SplineBasis.make_equidistant(1, nu-1) v_basis = SplineBasis.make_equidistant(1, nv-1) poles_z = np.transpose( point_seq[2, :].reshape(nv, nu, 1), axes = [1, 0, 2] ) - self.z_surface = Z_Surface(self.quad[0:3], Surface((u_basis, v_basis), poles_z) ) + self.z_surface = Z_Surface(quad[0:3], Surface((u_basis, v_basis), poles_z) ) # related bilinear Z-surface, all evaluations just call this object. + self.check_map() + def check_map(self): + # check that xy_to_uv works fine + uv_quad = self.xy_to_uv(self.quad) + print( uv_quad ) + assert np.allclose( uv_quad, np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) - + def transform(self, xy_mat, z_mat): + self.z_surface.transform(xy_mat, z_mat) def xy_to_uv(self, xy_points): From e8aa4a4a2fa3a1cebd727491cdfb30adb41b6e1f Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 18 Sep 2017 15:47:45 +0200 Subject: [PATCH 21/67] fix plane and line approximations - try to fit end points of a curve exactely --- src/bspline_approx.py | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index b030326..e01dfb4 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -35,7 +35,8 @@ def plane_surface(vtxs): def bilinear_surface(vtxs): """ - Returns B-spline surface of a bilinear surface given by 4 corner points. + Returns B-spline surface of a bilinear surface given by 4 corner points: + uv coords: We retun also list of UV coordinates of the given points. :param vtxs: List of tuples (X,Y,Z) :return: ( Surface, vtxs_uv ) @@ -51,7 +52,7 @@ def mid(*idx): [mid(0,1), mid(0,1,2,3), mid(2,3)], [vtxs[1], mid(1,2), vtxs[2]] ] - knots = [(0.0, 3), (1.0, 3)] + knots = 3 * [0.0] + 3 * [1.0] basis = bs.SplineBasis(2, knots) surface = bs.Surface((basis, basis), poles) vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] @@ -60,40 +61,22 @@ def mid(*idx): -def _line_data(vtxs, overhang=0.0): +def line(vtxs): ''' - :param vtxs: List of tuples (X,Y) or (X,Y,Z) - :return: + Return B-spline approximation of a line from two points + :param vtxs: [ X0, X1 ], Xn are point coordinates in arbitrary dimension D + :return: Curve2D ''' assert len(vtxs) == 2 vtxs = np.array(vtxs) mid = np.mean(vtxs, axis=0) poles = [ vtxs[0], mid, vtxs[1] ] - knots = [(0.0+overhang, 3), (1.0-overhang, 3)] + knots = 3*[0.0] + 3*[1.0] basis = bs.SplineBasis(2, knots) - return (basis, poles) + return bs.Curve(basis, poles) -def line_2d(vtxs): - """ - Return B-spline approximation of line from two 2d points - :param vtxs: [ (X0, Y0), (X1, Y1) ] - :return: Curve2D - """ - return bs.Curve( *_line_data(vtxs)) - - - -def line_3d(vtxs): - """ - Return B-spline approximation of line from two 3d points - :param vtxs: [ (X0, Y0, Z0), (X1, Y1, Z0) ] - :return: Curve2D - """ - return bs.Curve(*_line_data(vtxs)) - - def surface_from_grid(grid_surface, nuv, **kwargs): """ @@ -115,7 +98,9 @@ def curve_from_grid(points, **kwargs): """ deg = kwargs.get('degree', 3) tol = kwargs.get('tol', 0.01) - tck = scipy.interpolate.splprep(points.T, k=deg, s=tol)[0] + weights = np.ones(points.shape[0]) + weights[0] = weights[-1] = 1000.0 + tck = scipy.interpolate.splprep(points.T, k=deg, s=tol, w = weights)[0] knots, poles, degree = tck basis = bs.SplineBasis(degree, knots) curve = bs.Curve(basis, np.array(poles).T) @@ -155,6 +140,7 @@ def approx_chol(self): :return: B-Spline patch """ + print('Transforming points to parametric space ...') start_time = time.time() points = self.grid_surf.points_xyz From 06c33db96017fa5919c7fedf9cf6a73bd28ce6be Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 18 Sep 2017 19:48:58 +0200 Subject: [PATCH 22/67] add plane approx test --- src/bspline_approx.py | 8 +++----- tests/test_bs_approx.py | 27 +++++++++++++++------------ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index e01dfb4..e469aee 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -24,12 +24,10 @@ def plane_surface(vtxs): :return: ( Surface, vtxs_uv ) """ assert len(vtxs) == 3, "n vtx: {}".format(len(vtxs)) - vtxs.append( (0,0,0) ) vtxs = np.array(vtxs) vv = vtxs[1] + vtxs[2] - vtxs[0] vtx4 = [ vtxs[0], vtxs[1], vv, vtxs[2]] - (surf, vtxs_uv) = bilinear_surface(vtx4) - return (surf, [ vtxs_uv[0], vtxs_uv[1], vtxs_uv[3] ]) + return bilinear_surface(vtx4) @@ -55,8 +53,8 @@ def mid(*idx): knots = 3 * [0.0] + 3 * [1.0] basis = bs.SplineBasis(2, knots) surface = bs.Surface((basis, basis), poles) - vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - return (surface, vtxs_uv) + #vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] + return surface diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 8600eb9..0c54d5b 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -14,18 +14,20 @@ def function_sin_cos(x): class TestSurfaceApprox: # todo: numerical test + def plot_surf(self, surf): + fig = plt.figure() + ax = fig.gca(projection='3d') + bs_plot.plot_surface_3d(surf, ax) + plt.show() + + def plot_approx_grid(self): points = bs.make_function_grid(function_sin_cos, 20, 30) gs = bs.GridSurface() gs.init_from_seq(points.reshape(-1, 3).T) z_surf = bs_approx.surface_from_grid(gs, (3,4) ) + self.plot_surf(z_surf) - fig = plt.figure() - ax = fig.gca(projection='3d') - - bs_plot.plot_surface_3d(z_surf, ax) - - plt.show() def plot_approx_transformed_grid(self): points = bs.make_function_grid(function_sin_cos, 20, 30) @@ -34,20 +36,21 @@ def plot_approx_transformed_grid(self): gs = bs.GridSurface() gs.init_from_seq(points.reshape(-1, 3).T) z_surf = bs_approx.surface_from_grid(gs, (3,4) ) + self.plot_surf(z_surf) - fig = plt.figure() - ax = fig.gca(projection='3d') - - bs_plot.plot_surface_3d(z_surf, ax) - - plt.show() + def plot_plane(self): + surf = bs_approx.plane_surface([ [0, 0, 0], [1,1,0], [0,0,1] ]) + self.plot_surf(surf) def test_surface_approx(self): #self.plot_approx_grid() #self.plot_approx_transformed_grid() + self.plot_plane() pass + + class TestCurveApprox: def test_approx_2d(self): From aec6e46fd61c8ef5b5f30e347a3ed50502142644 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 18 Sep 2017 19:50:19 +0200 Subject: [PATCH 23/67] Implementation of plane and line overhang --- src/bspline.py | 8 ++++++++ src/bspline_approx.py | 29 +++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 100889a..0907e01 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -518,6 +518,10 @@ def __init__(self, xy_quad, z_surface): else: assert False, "Three or four points must be given." + #TODO: remove this, after fixing GridSurface z_eval + self.z_scale =1.0 + self.z_shift = 1.0 + def make_full_surface(self): """ Return representation of the surface by the 3d Surface object. @@ -556,6 +560,10 @@ def transform(self, xy_mat, z_mat): self.z_surface.poles *= z_mat[0] self.z_surface.poles += z_mat[1] + #TODO: remove this, after fixing GridSurface z_eval + self.z_scale *= z_mat[0] + self.z_shift = self.z_shift*z_mat[0] + z_mat[1] + """ diff --git a/src/bspline_approx.py b/src/bspline_approx.py index e01dfb4..ec90cce 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -16,7 +16,7 @@ Approximation methods for B/splines of degree 2. """ -def plane_surface(vtxs): +def plane_surface(vtxs, overhang=0.0): """ Returns B-spline surface of a plane given by 3 points. We retun also list of UV coordinates of the given points. @@ -28,12 +28,11 @@ def plane_surface(vtxs): vtxs = np.array(vtxs) vv = vtxs[1] + vtxs[2] - vtxs[0] vtx4 = [ vtxs[0], vtxs[1], vv, vtxs[2]] - (surf, vtxs_uv) = bilinear_surface(vtx4) - return (surf, [ vtxs_uv[0], vtxs_uv[1], vtxs_uv[3] ]) + return bilinear_surface(vtx4, overhang) -def bilinear_surface(vtxs): +def bilinear_surface(vtxs, overhang=0.0): """ Returns B-spline surface of a bilinear surface given by 4 corner points: uv coords: @@ -43,6 +42,12 @@ def bilinear_surface(vtxs): """ assert len(vtxs) == 4, "n vtx: {}".format(len(vtxs)) vtxs = np.array(vtxs) + if overhang > 0.0: + inv_oh = overhang / (1.0 - 2.0 * overhang ) + dv = np.roll(vtxs, -1) - vtxs + dv *= inv_oh + vtxs += np.roll(dv, 1) - dv + def mid(*idx): return np.mean( vtxs[list(idx)], axis=0) @@ -55,13 +60,13 @@ def mid(*idx): knots = 3 * [0.0] + 3 * [1.0] basis = bs.SplineBasis(2, knots) surface = bs.Surface((basis, basis), poles) - vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] - return (surface, vtxs_uv) + #vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] + return surface -def line(vtxs): +def line(vtxs, overhang = 0.0): ''' Return B-spline approximation of a line from two points :param vtxs: [ X0, X1 ], Xn are point coordinates in arbitrary dimension D @@ -69,6 +74,11 @@ def line(vtxs): ''' assert len(vtxs) == 2 vtxs = np.array(vtxs) + if overhang > 0.0: + inv_oh = overhang / (1.0 - 2.0 * overhang) + dv = inv_oh*(vtxs[1] - vtxs[0]) + vtxs[0] -= dv + vtxs[1] += dv mid = np.mean(vtxs, axis=0) poles = [ vtxs[0], mid, vtxs[1] ] knots = 3*[0.0] + 3*[1.0] @@ -102,8 +112,11 @@ def curve_from_grid(points, **kwargs): weights[0] = weights[-1] = 1000.0 tck = scipy.interpolate.splprep(points.T, k=deg, s=tol, w = weights)[0] knots, poles, degree = tck + curve_poles=np.array(poles).T + curve_poles[0] = points[0] + curve_poles[-1] = points[-1] basis = bs.SplineBasis(degree, knots) - curve = bs.Curve(basis, np.array(poles).T) + curve = bs.Curve(basis, curve_poles) return curve From ad3af40c57bc71040ac35448e1f181d9f208102e Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Tue, 19 Sep 2017 09:33:13 +0200 Subject: [PATCH 24/67] Improve implementation of overhang. - original range of the line/plane is preserved. --- src/bspline_approx.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 0f83153..188217a 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -42,10 +42,9 @@ def bilinear_surface(vtxs, overhang=0.0): assert len(vtxs) == 4, "n vtx: {}".format(len(vtxs)) vtxs = np.array(vtxs) if overhang > 0.0: - inv_oh = overhang / (1.0 - 2.0 * overhang ) - dv = np.roll(vtxs, -1) - vtxs - dv *= inv_oh - vtxs += np.roll(dv, 1) - dv + dv = np.roll(vtxs, -1, axis=0) - vtxs + dv *= overhang + vtxs += np.roll(dv, 1, axis=0) - dv def mid(*idx): return np.mean( vtxs[list(idx)], axis=0) @@ -56,7 +55,7 @@ def mid(*idx): [mid(0,1), mid(0,1,2,3), mid(2,3)], [vtxs[1], mid(1,2), vtxs[2]] ] - knots = 3 * [0.0] + 3 * [1.0] + knots = 3 * [0.0 - overhang] + 3 * [1.0 + overhang] basis = bs.SplineBasis(2, knots) surface = bs.Surface((basis, basis), poles) #vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] @@ -74,13 +73,12 @@ def line(vtxs, overhang = 0.0): assert len(vtxs) == 2 vtxs = np.array(vtxs) if overhang > 0.0: - inv_oh = overhang / (1.0 - 2.0 * overhang) - dv = inv_oh*(vtxs[1] - vtxs[0]) + dv = overhang*(vtxs[1] - vtxs[0]) vtxs[0] -= dv vtxs[1] += dv mid = np.mean(vtxs, axis=0) poles = [ vtxs[0], mid, vtxs[1] ] - knots = 3*[0.0] + 3*[1.0] + knots = 3*[0.0 - overhang] + 3*[1.0 + overhang] basis = bs.SplineBasis(2, knots) return bs.Curve(basis, poles) From 1525d147f925f0b277964ce527946ad155f0645a Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 11:21:05 +0200 Subject: [PATCH 25/67] Improve bspline - cleanup of bspline, GridSurface (WIP) - added tests for bspline - dev notes fro bs_approx --- src/bspline.py | 611 +++++++++++++++++++++++++--------------- src/bspline_approx.py | 19 ++ src/bspline_plot.py | 13 +- tests/test_bs_approx.py | 8 +- tests/test_bspline.py | 107 ++++--- 5 files changed, 483 insertions(+), 275 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 0907e01..4219ad8 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -1,20 +1,16 @@ """ -Module with classes representing various B-spline and NURMS curves and surfaces. +Module with classes representing various B-spline and NURBS curves and surfaces. These classes provide just basic functionality: - storing the data - evaluation of XYZ for UV -In future: - evaluation and xy<->uv functions accepting np.arrays, -- serialization and deserialization using JSONdata - must make it an installable module -- use de Boor algorithm for evaluation of curves and surfaces - evaluation of derivatives +In future: +- use de Boor algorithm for evaluation of curves and surfaces +- serialization and deserialization using JSONdata - must make it an installable module - implement degree increasing and knot insertion """ -""" -This module tries to approximate 2.5D array of terrain points -using B-Spline surface. -""" import matplotlib.pyplot as plt import numpy as np @@ -25,55 +21,55 @@ __author__ = 'Jan Brezina , Jiri Hnidek , Jiri Kopal ' -class ParamError(Exception): - pass - -def check_matrix(mat, shape, values, idx=[]): - ''' - Check shape and type of scalar, vector or matrix. - :param mat: Scalar, vector, or vector of vectors (i.e. matrix). Vector may be list or other iterable. - :param shape: List of dimensions: [] for scalar, [ n ] for vector, [n_rows, n_cols] for matrix. - If a value in this list is None, the dimension can be arbitrary. The shape list is set fo actual dimensions - of the matrix. - :param values: Type or tuple of allowed types of elements of the matrix. E.g. ( int, float ) - :param idx: Internal. Used to pass actual index in the matrix for possible error messages. - :return: - ''' - try: - if len(shape) == 0: - if not isinstance(mat, values): - raise ParamError("Element at index {} of type {}, expected instance of {}.".format(idx, type(mat), values)) - else: - if shape[0] is None: - shape[0] = len(mat) - l=None - if not hasattr(mat, '__len__'): - l=0 - elif len(mat) != shape[0]: - l=len(mat) - if not l is None: - raise ParamError("Wrong len {} of element {}, should be {}.".format(l, idx, shape[0])) - for i, item in enumerate(mat): - sub_shape = shape[1:] - check_matrix(item, sub_shape, values, idx = [i] + idx) - shape[1:] = sub_shape - return shape - except ParamError: - raise - except Exception as e: - raise ParamError(e) - - -def check_knots(deg, knots, N): - total_multiplicity = 0 - for knot, mult in knots: - # This condition must hold if we assume only (0,1) interval of curve or surface parameters. - #assert float(knot) >= 0.0 and float(knot) <= 1.0 - total_multiplicity += mult - assert total_multiplicity == deg + N + 1 - - -scalar_types = (int, float, np.int64) +# class ParamError(Exception): +# pass +# +# def check_matrix(mat, shape, values, idx=[]): +# ''' +# Check shape and type of scalar, vector or matrix. +# :param mat: Scalar, vector, or vector of vectors (i.e. matrix). Vector may be list or other iterable. +# :param shape: List of dimensions: [] for scalar, [ n ] for vector, [n_rows, n_cols] for matrix. +# If a value in this list is None, the dimension can be arbitrary. The shape list is set fo actual dimensions +# of the matrix. +# :param values: Type or tuple of allowed types of elements of the matrix. E.g. ( int, float ) +# :param idx: Internal. Used to pass actual index in the matrix for possible error messages. +# :return: +# ''' +# try: +# if len(shape) == 0: +# if not isinstance(mat, values): +# raise ParamError("Element at index {} of type {}, expected instance of {}.".format(idx, type(mat), values)) +# else: +# if shape[0] is None: +# shape[0] = len(mat) +# l=None +# if not hasattr(mat, '__len__'): +# l=0 +# elif len(mat) != shape[0]: +# l=len(mat) +# if not l is None: +# raise ParamError("Wrong len {} of element {}, should be {}.".format(l, idx, shape[0])) +# for i, item in enumerate(mat): +# sub_shape = shape[1:] +# check_matrix(item, sub_shape, values, idx = [i] + idx) +# shape[1:] = sub_shape +# return shape +# except ParamError: +# raise +# except Exception as e: +# raise ParamError(e) +# +# +# def check_knots(deg, knots, N): +# total_multiplicity = 0 +# for knot, mult in knots: +# # This condition must hold if we assume only (0,1) interval of curve or surface parameters. +# #assert float(knot) >= 0.0 and float(knot) <= 1.0 +# total_multiplicity += mult +# assert total_multiplicity == deg + N + 1 +# +# +# scalar_types = (int, float, np.int64) @@ -100,9 +96,9 @@ def make_equidistant(cls, degree, n_intervals, knot_range=[0.0, 1.0]): Returns spline basis for an eqidistant knot vector having 'n_intervals' subintervals. :param degree: degree of the spline basis - :param n_intervals: length of vector + :param n_intervals: Number of subintervals. :param knot_range: support of the spline, min and max valid 't' - :return: np array of knots + :return: SplineBasis object. """ n = n_intervals + 2 * degree + 1 knots = np.array((knot_range[0],) * n) @@ -115,37 +111,57 @@ def make_equidistant(cls, degree, n_intervals, knot_range=[0.0, 1.0]): @classmethod def make_from_packed_knots(cls, degree, knots): + """ + Construct basis from the vector of packed knots. + :param degree: Degree of the basis. + :param knots: List of knots with their multiplicities, [ (knot, mult), ..] + :return: SplineBasis object. + """ full_knots = [ q for q, mult in knots for i in range(mult) ] return cls(degree, full_knots) def __init__(self, degree, knots): """ - Constructor of the basis. + Constructor of the spline basis. :param degree: Degree of Bezier polynomials >=0. :param knots: Numpy array of the knots including multiplicities. """ assert degree >=0 self.degree = degree - # check free ends (and full degree along the whole curve) + # check free ends for i in range(self.degree): assert knots[i] == knots[i+1] assert knots[-i-1] == knots[-i-2] self.knots = np.array(knots) self.size = len(self.knots) - self.degree -1 + # Number of basis functions. + + self.knots_idx_range = [self.degree, len(self.knots) - self.degree - 1] + # Range of knot indices corrsponding to the basis domain. + self.domain = self.knots[self.knots_idx_range] + # Support domain of the spline. + self.domain_size = self.domain[1] - self.domain[0] + # Size of the domain. + self.n_intervals = self.size - self.degree - # Number of basis functions. + # Number of subintervals ( assuming multiplicities only on ends. ) + # Set optimized functions for specific degrees. if self.degree == 2: - self.eval_base_vector = self._eval_base_vector_deg_2 - self.eval_diff_base_vector = self._eval_diff_base_vector_deg_2 + self.eval_base_vector = self._eval_vector_deg_2 + self.eval_diff_base_vector = self._eval_diff_vector_deg_2 + def pack_knots(self): + """ + :return: Packed knot vector, [ (knot, multiplicity), .. ] + """ last, mult = self.knots[0], 0 packed_knots = [] for q in self.knots: @@ -169,10 +185,8 @@ def find_knot_interval(self, t): :return: I """ idx = np.searchsorted(self.knots[self.degree: -self.degree -1], [t], side='right')[0] - 1 - if idx < 0 or idx > self.n_intervals - 1: - print("Warning: evaluation out of spline domain; t: {} min: {} max: {}".format(t, self.knots[0], self.knots[-1])) - - return max(min(idx, self.n_intervals - 1), 0) # deals with t == self.knots[-1] + assert 0 <= idx <= self.n_intervals, "Evaluation out of spline domain; t: {} min: {} max: {}".format(t, self.knots[0], self.knots[-1]) + return min(idx, self.n_intervals - 1) # deals with t == self.knots[-1] def _basis(self, deg, idx, t): @@ -250,7 +264,6 @@ def eval_diff(self, i_base, t): return diff - def make_linear_poles(self): """ Return poles of basis functions to get a f(x) = x. @@ -263,33 +276,51 @@ def make_linear_poles(self): return poles - def eval_base_vector(self, i_base, t): + def eval_vector(self, i_base, t): + """ + This function compute base function of B-Spline curve on given subinterval. + :param i_int: Interval in which 't' belongs. Three nonzero basis functions on this interval are evaluated. + :param t: Where to evaluate. + :return: Numpy array of three values. + """ values = [] for ib in range(i_base, i_base + self.degree + 1): values.append( self.eval(ib, t)) return values - def eval_diff_base_vector(self, i_base, t): + def eval_diff_vector(self, i_base, t): + """ + This function compute derivative of base function of B-Spline curve on given subinterval. + :param i_int: Interval in which 't' belongs. Derivatives of the 3 nonzero basis functions on this interval are evaluated. + :param t: Where to evaluate. + :return: Numpy array of three values. + """ values = [] for ib in range(i_base, i_base + self.degree + 1): values.append( self.eval_diff(ib, t)) return values - def _eval_base_vector_deg_2(self, i_base, t): + """ + Specializations. + TODO: + - Try usage of scipy evaluation, compare speed with optimized eval_vector and diff_eval_vector. + - Generalize optimized evaluation of eval_vector (If scipy is not faster), De Boor algortihm, Hoschek 4.3.3. + - Optimize eval and eval_diff - without recursion, based on combinatorGeneralize optimized evaluation of eval_vector (If scipy is not faster) + """ + def _eval_vector_deg_2(self, i_int, t): """ - This function compute normalized blending function aka base function of B-Spline curve or surface. - :param knot_vec: - :param t_param: - :param order: (0: function value, 1: derivative function value) - :param sparse: - :return: + This function compute base function of B-Spline curve on given subinterval. + :param i_int: Interval in which 't' belongs. Three nonzero basis functions on this interval are evaluated. + :param t: Where to evaluate. + :return: Numpy array of three values. + Note: Keep code redundancy with 'diff' as optimization. """ basis_values = np.zeros(3) - tk1, tk2, tk3, tk4 = self.knots[i_base + 1 : i_base + 5] + tk1, tk2, tk3, tk4 = self.knots[i_int + 1 : i_int + 5] d31 = tk3 - tk1 d32 = tk3 - tk2 @@ -310,11 +341,18 @@ def _eval_base_vector_deg_2(self, i_base, t): return basis_values - def _eval_diff_base_vector_deg_2(self, i_base, t): + def _eval_diff_vector_deg_2(self, i_int, t): + """ + This function compute derivative of base function of B-Spline curve on given subinterval. + :param i_int: Interval in which 't' belongs. Derivatives of the 3 nonzero basis functions on this interval are evaluated. + :param t: Where to evaluate. + :return: Numpy array of three values. + Note: Keep code redundancy with 'diff' as optimization. + """ basis_values = np.zeros(3) - tk1, tk2, tk3, tk4 = self.knots[i_base + 1: i_base + 5] + tk1, tk2, tk3, tk4 = self.knots[i_int + 1: i_int + 5] d31 = tk3 - tk1 d32 = tk3 - tk2 @@ -336,39 +374,60 @@ def _eval_diff_base_vector_deg_2(self, i_base, t): class Curve: + """ + Defines a D-dim B-spline curve. + """ @classmethod def make_raw(cls, poles, knots, rational=False, degree=2): """ Construct a B-spline curve. - :param poles: List of poles (control points) ( X, Y, Z ) or weighted points (X,Y,Z, w). X,Y,Z,w are floats. - Weighted points are used only for rational B-splines (i.e. nurbs) - :param knots: List of tuples (knot, multiplicity), where knot is float, t-parameter on the curve of the knot - and multiplicity is positive int. Total number of knots, i.e. sum of their multiplicities, must be - degree + N + 1, where N is number of poles. + :param poles: Numpy array N x (D+r) of poles (control points). N is number of poles, D is dimension of the curve, 'r' is 1 for rational curves. + For rational case, poles[:, D] are weights of the control points. + :param knots, degree: See SplineBasis. :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. :param degree: Non-negative int """ basis = SplineBasis(degree, knots) return cls(basis, poles, rational) - """ - Defines a 3D (or (dim -D) curve as B-spline. We shall work only with B-splines of degree 2. - Corresponds to "B-spline Curve - <3D curve record 7>" from BREP format description. - """ def __init__(self, basis, poles, rational = False): + """ + Construct a B-spline curve. + :param poles: Numpy array N x (D+r) of poles (control points). N is number of poles, D is dimension of the curve, 'r' is 1 for rational curves. + For rational case, poles[:, D] are weights of the control points. + :param basis: SplineBasis object. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. + """ + self.basis = basis + # Spline basis. + + self.poles = np.array(poles, dtype=float) # N x D + assert self.poles.shape[0] == self.basis.size + # Spline poles. + self.dim = len(poles[0]) - rational - check_matrix(poles, [self.basis.size, self.dim + rational], scalar_types ) + # Dimension of the curve. + + self.rational = rational + # Indicator of rational B-spline (NURBS). - self.poles=np.array(poles) # N x D - self.rational=rational if rational: + # precomputations self._weights = poles[:, self.dim] self._poles = (poles[:, 0:self.dim].T * self._weights ).T def eval(self, t): + """ + Evaluate a B-spline curve for paramater 't'. + :param t: Evaluation point. + :return: D-dimensional pnumpy array. D - is dimension given by dimension of poles. + TODO: + - use basis.eval_vector + - test evaluation for rational curves + """ it = self.basis.find_knot_interval(t) dt = self.basis.degree + 1 @@ -390,12 +449,16 @@ def eval_array(self, t_points): return np.array( [ self.eval(t) for t in t_points] ) def aabb(self): + """ + Return Axes Aligned Bounding Box of the poles, which should be also bounding box of the curve itself. + :return: ( min_corner, max_corner); Box corners are numpy arryas of dimension D. + """ return (np.amin(self.poles, axis=0), np.amax(self.poles, axis=0)) class Surface: """ - Defines a B-spline surface. + Defines D-dim B-spline surface. """ @classmethod @@ -417,30 +480,41 @@ def make_raw(cls, poles, knots, rational=False, degree=(2,2)): def __init__(self, basis, poles, rational=False): """ - Construct a B-spline in 3d space. - :param poles: Matrix (list of lists) of Nu times Nv poles (control points). - Single pole is a points ( X, Y, Z ) or weighted point (X,Y,Z, w). X,Y,Z,w are floats. - Weighted points are used only for rational B-splines (i.e. nurbs) - :param knots: Tuple (u_knots, v_knots). Both u_knots and v_knots are lists of tuples - (knot, multiplicity), where knot is float, t-parameter on the curve of the knot - and multiplicity is positive int. For both U and V knot vector the total number of knots, - i.e. sum of their multiplicities, must be degree + N + 1, where N is number of poles. - :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. BREP format have two independent flags - for U and V parametr, but only choices 0,0 and 1,1 have sense. - :param degree: (u_degree, v_degree) Both positive ints. + Construct a B-spline surface. + :param poles: Numpy array Nu x Nv x (D+r) of poles (control points). + Nu and Nv are sizes of u_basis, v_basis respectively. + D is dimension of the surface, 'r' is 1 for rational surfaces. + For rational case, poles[:, :, D] are weights of the control points. + :param basis: (u_basis, v_basis) SplineBasis objects for U and V parameter axis. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. """ - self.u_basis, self.v_basis = basis - self.rational = rational + # Surface basis for U and V axis. + self.dim = len(poles[0][0]) - rational - check_matrix(poles, [self.u_basis.size, self.v_basis.size, self.dim + rational], scalar_types ) - self.poles=np.array(poles) + # Surface dimension, D. + + self.poles=np.array(poles, dtype=float) + # Surface poles matrix: Nu x Nv x (D+r) assert self.poles.shape == (self.u_basis.size, self.v_basis.size, self.dim + rational) + + self.rational = rational + # Rational surface indicator. if rational: + # precomputations self._weights = poles[:, :, self.dim] self._poles = (poles[:,:,0:self.dim].T * self._weights.T ).T def eval(self, u, v): + """ + Evaluate a B-spline surface for paramaters u,v. + :param u, v: Evaluation point. + :return: D-dimensional numpy array. D - is dimension given by dimension of poles. + TODO: + - use basis.eval_vector + - test evaluation for rational curves + """ + iu = self.u_basis.find_knot_interval(u) iv = self.v_basis.find_knot_interval(v) du = self.u_basis.degree + 1 @@ -463,9 +537,9 @@ def eval(self, u, v): def eval_array(self, uv_points): """ - Evaluate in array of t-points. + Evaluate in array of uv-points. :param uv_points: numpy array N x [u, v] - :return: Numpy array N x D, D is dimension of the curve. + :return: Numpy array N x D; D is dimension of the curve. """ assert uv_points.shape[1] == 2 return np.array( [ self.eval(u, v) for u, v in uv_points] ) @@ -474,53 +548,59 @@ def eval_array(self, uv_points): class Z_Surface: """ - Simplified B-spline surface that use just linear or bilinear transform between XY and UV. - - TODO: - - We need conversion to full 3D surface for the BREP output - - Optimization: simplified Bspline evaluation just for the singel coordinate + Simplified B-spline surface that use just linear or bilinear transform between XY and UV. """ def __init__(self, xy_quad, z_surface): """ - Construct a surface given by the 1d surface for the Z coordinate and XY quadrilateral - for the bilinear UV -> XY mapping. + Construct a surface given by the 1d surface for the Z coordinate and XY quadrilateral + for the bilinear UV <-> XY mapping. :param xy_quad: np array N x 2 Four or three points, determining bilinear or linear mapping, respectively. Four points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0), (1,1) Three points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0) - :param z_surface: !D Surface object. + Linear case is also detected for the four points. + :param z_surface: 1D Surface object. """ assert z_surface.dim == 1 + self.dim = 3 + # Fixed surface dimension. + self.z_surface = z_surface + # Underlaying 1d surface object for Z coord evaluation. + self.u_basis = z_surface.u_basis self.v_basis = z_surface.v_basis - self.dim = 3 + # Basis for UV directions. + + # Build envelope quadrilateral polygon in XY plane. + self.quad = np.array(xy_quad, dtype=float) + assert self.quad.shape[0] in [3, 4], "Three or four points must be given." + assert self.quad.shape[1] == 2 + + v11 = self.quad[0] + self.quad[2] - self.quad[1] + if self.quad.shape[0] == 3: + self.quad = np.concatenate( (self.quad, v11[None, :]), axis = 0) - if len(xy_quad) == 3 or \ - np.allclose(xy_quad[3], xy_quad[0] + xy_quad[2] - xy_quad[1]): + if np.allclose(self.quad[3], v11): # linear case - self.xy_shift = xy_quad[1] - v_vec = xy_quad[0] - xy_quad[1] - u_vec = xy_quad[2] - xy_quad[1] - self.mat_uv_to_xy = np.column_stack((u_vec, v_vec)) - self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) + self._xy_shift = self.quad[1] + v_vec = self.quad[0] - self.quad[1] + u_vec = self.quad[2] - self.quad[1] + self._mat_uv_to_xy = np.column_stack((u_vec, v_vec)) + self._mat_xy_to_uv = la.inv(self._mat_uv_to_xy) self.xy_to_uv = self._linear_xy_to_uv self.uv_to_xy = self._linear_uv_to_xy - elif len(xy_quad) == 4: + else: # bilinear case - self.quad = xy_quad - self.xy_to_uv = self._bilinear_xy_to_uv self.uv_to_xy = self._bilinear_uv_to_xy - else: - assert False, "Three or four points must be given." #TODO: remove this, after fixing GridSurface z_eval - self.z_scale =1.0 - self.z_shift = 1.0 + #self.z_scale =1.0 + #self.z_shift = 1.0 def make_full_surface(self): """ @@ -540,9 +620,9 @@ def make_full_surface(self): return Surface(basis, poles) - def transform(self, xy_mat, z_mat): + def transform(self, xy_mat, z_mat=np.array( [1.0, 0.0] ) ): """ - Transform the surface by arbitrary XY linear transform and Z linear transform. + Transform the Z-surface by arbitrary XY linear transform and Z linear transform. :param xy_mat: np array, 2 rows 3 cols, last column is xy shift :param z_shift: [ z_scale, z_shift] :return: None @@ -550,19 +630,17 @@ def transform(self, xy_mat, z_mat): assert xy_mat.shape == (2, 3) assert z_mat.shape == (2, ) - - - self.mat_uv_to_xy = xy_mat[0:2,0:2].dot( self.mat_uv_to_xy ) - self.xy_shift = xy_mat[0:2,0:2].dot( self.xy_shift ) + xy_mat[0:2, 2] - self.mat_xy_to_uv = la.inv(self.mat_uv_to_xy) + self._mat_uv_to_xy = xy_mat[0:2,0:2].dot( self._mat_uv_to_xy ) + self._xy_shift = xy_mat[0:2,0:2].dot( self._xy_shift ) + xy_mat[0:2, 2] + self._mat_xy_to_uv = la.inv(self._mat_uv_to_xy) # apply z-transfrom directly to the poles self.z_surface.poles *= z_mat[0] self.z_surface.poles += z_mat[1] #TODO: remove this, after fixing GridSurface z_eval - self.z_scale *= z_mat[0] - self.z_shift = self.z_shift*z_mat[0] + z_mat[1] + #self.z_scale *= z_mat[0] + #self.z_shift = self.z_shift*z_mat[0] + z_mat[1] @@ -575,7 +653,7 @@ def uv_to_xy(self, uv_points): """ def _linear_uv_to_xy(self, uv_points): assert uv_points.shape[1] == 2, "Size: {}".format(uv_points.shape) - return ( np.dot(uv_points, self.mat_uv_to_xy.T) + self.xy_shift) + return ( np.dot(uv_points, self._mat_uv_to_xy.T) + self._xy_shift) def _bilinear_uv_to_xy(self, uv_points): @@ -596,7 +674,7 @@ def xy_to_uv(self, xy_points): def _linear_xy_to_uv(self, xy_points): # assert xy_points.shape[0] == 2 assert xy_points.shape[1] == 2 - return np.dot((xy_points - self.xy_shift), self.mat_xy_to_uv.T) + return np.dot((xy_points - self._xy_shift), self._mat_xy_to_uv.T) def _bilinear_xy_to_uv(self, xy_points): @@ -605,6 +683,11 @@ def _bilinear_xy_to_uv(self, xy_points): def eval(self, u, v): + """ + Evaluate a B-spline surface for paramaters u,v. + :param u, v: Evaluation point. + :return: D-dimensional numpy array. D - is dimension given by dimension of poles. + """ z = self.z_surface.eval(u, v) uv_points = np.array([[u, v]]) x, y = self.uv_to_xy( uv_points )[0] @@ -612,149 +695,215 @@ def eval(self, u, v): def eval_array(self, uv_points): + """ + Evaluate a B-spline surface in array of UV points. + :param uv_points: numpy array N x [u, v] + :return: array N x D; D - is dimension given by dimension of poles. + """ assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) xy_points = self.uv_to_xy(uv_points) return np.concatenate( (xy_points, z_points), axis = 1) - def eval_xy_array(self, xy_points): + """ + Evaluate a B-spline surface in array of XY points. + :param xy_points: numpy array N x [x, y] + :return: array N x D; D - is dimension given by dimension of poles. + """ uv_points = self.xy_to_uv(xy_points) z_points = self.z_surface.eval_array(uv_points) return np.concatenate( (xy_points, z_points), axis = 1) + def z_eval_array(self, uv_points): + """ + Evaluate just Z coordinate for array of UV points. + :param uv_points: numpy array N x [u, v] + :return: array N x D; D - is dimension given by dimension of poles. + """ assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) return z_points.reshape(-1) + def z_eval_xy_array(self, xy_points): + """ + Evaluate just Z coordinate for array of XY points. + :param uv_points: numpy array N x [x, y] + :return: array N x D; D - is dimension given by dimension of poles. + """ uv_points = self.xy_to_uv(xy_points) z_points = self.z_surface.eval_array(uv_points) return z_points.reshape(-1) -class InvalidGridExc(Exception): + + + +class GridNotInShapeExc(Exception): pass +class IrregularGridExc(Exception): + pass -class GridSurface: - """ - Surface given as bilinear interpolation of a regular grid of points. - """ - # TODO: calling transform lose mapping between original point grid and unit square, approximation can not be performed - step_tolerance = 1e-10 - def __init__(self): - """ - Initialize point grid from numpy array. - :param grid: NxMx3 numpy array of NxM grid of #D coordinates - """ - self.grid=None - self.mat_xy_to_uv=None - self.mat_uv_to_xy=None - self.shift=None - self.shape = (0,0) - self.uv_step = (0,0) +class GridSurface: + step_tolerance = 1e-5 + # relative step_tolerance of + @staticmethod def load(self, filename): """ Load the grid surface from file :param filename: - :return: + :return: GridSurface object. """ point_seq = np.loadtxt(filename) assert min(point_seq.shape) > 1 - self.init_from_seq(point_seq.T) + GridSurface(point_seq.T) + """ + Can load and check grid of XYZ points and construct a + Z-surface of degree 1 for them. + """ + # TODO: calling transform lose mapping between original point grid and unit square, approximation can not be performed + - def init_from_seq(self, point_seq): + def __init__(self, point_seq, tolerance = 1e-6): """ - Get 2d transform matrix 2 rows 3 cols to map a grid of XY points to unit square - :param point_seq: numpy array N x 2 - :return: + Construct the GridSurface from sequence of points. + :param point_seq: N x 3 numpy array; organized as Nu x Nv grid. + Nu, Nv are detected automaticaly, both must be greater then 1. + :param step_tolerance: Tolerance between XY position given by envelope quad and actual point position. + Relative to the length of maximal side of the envelope quad. """ + self.tolerance = tolerance + + assert point_seq.shape[1] == 3 + n_points = point_seq.shape[0] + point_seq_xy = point_seq[:, 0:2] - assert point_seq.shape[0] == 3 - n_points = point_seq.shape[1] - point_seq_xy = point_seq[0:2,:] + self.quad = None + # Envelope quad - polygon, oriented counter clockwise. - vtx_00 = point_seq_xy[0:2, 0] - vtx_du = point_seq_xy[0:2, 1] + self.shape = None + # Number of points along axis: Nu x Nv - u_step = vtx_du - vtx_00 - for i in range(2, n_points): - step = point_seq_xy[:,i] - point_seq_xy[:,i-1] - if la.norm(u_step - step) > self.step_tolerance: - break + self._get_grid_corners(point_seq_xy) + self._check_grid_regularity(point_seq_xy) - vtx_dv = point_seq_xy[:,i] - v_step = vtx_dv - vtx_00 + self.z_scale = 1.0 + self.z_shift = 0.0 - nu = i - nv = int(n_points / nu) - if not n_points == nu*nv: - raise InvalidGridExc("Not a M*N grid.") + # self._grid_z = point_seq[2, :].reshape(self.nv, self.nu) + # grid of original Z values, format as matrix - # check total range of the grid - vtx_10 = point_seq_xy[:, nu-1] - vtx_01 = point_seq_xy[:, -nu] - vtx_11 = point_seq_xy[:, -1] - u_range_0 = vtx_10 - vtx_00 - u_range_1 = vtx_11 - vtx_01 - v_range_0 = vtx_01 - vtx_00 - v_range_1 = vtx_11 - vtx_10 - if not la.norm(u_range_0 - u_range_1) < self.step_tolerance or \ - not la.norm(v_range_0 - v_range_1) < self.step_tolerance: - raise InvalidGridExc("Grid XY envelope is not a parallelogram.") + self._uv_step = (1.0 / float(self.shape[0] - 1), 1.0 / float(self.shape[1] - 1)) + # Grid step in u, v direction respectively. - u_step = u_range_0 / (nu-1) - v_step = v_range_0 / (nv-1) + self._make_z_surface( point_seq) - # check regularity of the grid - for i in range(nu*nv): - pred_x = i - 1 - pred_y = i - nu - if i%nu == 0: - pred_x= -1 - if pred_x > 0 and not la.norm(point_seq_xy[:, i] - point_seq_xy[:, pred_x] - u_step) < 2*self.step_tolerance: - raise InvalidGridExc("Irregular grid in X direction, point %d"%i) - if pred_y > 0 and not la.norm(point_seq_xy[:, i] - point_seq_xy[:, pred_y] - v_step) < 2*self.step_tolerance: - raise InvalidGridExc("Irregular grid in Y direction, point %d"%i) - - #self.uv_to_xy(np.array([[0, 1], [0, 0], [1, 0], [1, 1]]).T) - self.quad = quad = np.stack([vtx_01, vtx_00, vtx_10, vtx_11], axis = 0) - # Envelope quad - polygon, oriented counter clockwise. + self.points_xyz = point_seq + # Original sequance of XYZ points - self.grid_z = point_seq[2, :].reshape(nv, nu) - # grid of original Z values, format as matrix + self._check_map() + + def _get_grid_corners(self, xy_points): + n_points = len(xy_points) + vtx_00 = xy_points[0, 0:2] + vtx_dv = xy_points[1, 0:2] + + # detect grid shape + v_step = vtx_dv - vtx_00 + step_tolerance = self.tolerance * la.norm(v_step, np.inf) + for i in range(2, n_points): + step = xy_points[i,:] - xy_points[i - 1, :] + if la.norm(v_step - step, np.inf) > step_tolerance: + break + else: + raise GridNotInShapeExc("End of the first row not detected.") + nv = i + nu = int(n_points / nv) + if not n_points == nu * nv: + raise GridNotInShapeExc("Not a Nu x Nv grid.") self.shape = (nu, nv) - # Grid shape. - self.uv_step = (1.0 / float(nu - 1), 1.0 / float(nv - 1)) - # Grid step in u, v direction respectively. + # make envelope quad + vtx_01 = xy_points[nv - 1, :] + vtx_10 = xy_points[-nv, :] + vtx_11 = xy_points[-1, :] + self.quad = np.array( [ vtx_01, vtx_00, vtx_10, vtx_11 ], dtype = float ) - self.points_xyz = point_seq.T - # Original sequance of XYZ points + # check that quad is parallelogram. + diff = np.roll(self.quad, -1, axis = 0) - self.quad + if not la.norm(diff[0] + diff[2]) < self.tolerance * la.norm(diff[0]) or \ + not la.norm(diff[1] + diff[3]) < self.step_tolerance * la.norm(diff[1]): + raise GridNotInShapeExc("Grid XY envelope is not a parallelogram.") + + self._point_tol = np.max( la.norm(diff, axis = 1) ) + self._u_step = diff[1] / (nu-1) # v10 - v00 + self._v_step = diff[2] / (nv-1) # v11 - v10 + + + def _check_grid_regularity(self, points_xy): + # check regularity of the grid + nu, nv = self.shape + for i in range(nu * nv): + pred_y = i - 1 + pred_x = i - nv + if i%nv == 0: + pred_y = -1 + if pred_x > 0 and not la.norm(points_xy[i, :] - points_xy[pred_x, :] - self._u_step) < self._point_tol: + raise IrregularGridExc("Irregular grid in X direction, point %d"%i) + if pred_y > 0 and not la.norm(points_xy[i, :] - points_xy[pred_y, :] - self._v_step) < self._point_tol: + raise IrregularGridExc("Irregular grid in Y direction, point %d"%i) + + def _make_z_surface(self, points): + nu, nv = self.shape + u_basis = SplineBasis.make_equidistant(1, nu - 1) + v_basis = SplineBasis.make_equidistant(1, nv - 1) + + + + poles_z = points[:, 2].reshape(nu, nv, 1) + self.z_surface = Z_Surface(self.quad[0:3], Surface((u_basis, v_basis), poles_z) ) + uv_points = self.z_surface.xy_to_uv( points[:, 0:2] ) + grid_uv = uv_points.reshape(nu, nv, 2) + self.grid_uvz = np.concatenate((grid_uv, poles_z), axis=2) - u_basis = SplineBasis.make_equidistant(1, nu-1) - v_basis = SplineBasis.make_equidistant(1, nv-1) - poles_z = np.transpose( point_seq[2, :].reshape(nv, nu, 1), axes = [1, 0, 2] ) - self.z_surface = Z_Surface(quad[0:3], Surface((u_basis, v_basis), poles_z) ) # related bilinear Z-surface, all evaluations just call this object. - self.check_map() - def check_map(self): + + def _check_map(self): # check that xy_to_uv works fine uv_quad = self.xy_to_uv(self.quad) print( uv_quad ) assert np.allclose( uv_quad, np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) + + + def transform(self, xy_mat, z_mat): - self.z_surface.transform(xy_mat, z_mat) + """ + Transform the GridSurface by arbitrary XY linear transform and Z linear transform. + :param xy_mat: np array, 2 rows 3 cols, last column is xy shift + :param z_shift: [ z_scale, z_shift] + :return: None + Note: + """ + # just XY transfrom of underlaying z_surface + self.z_surface.transform(xy_mat) + + # transform quad + self.quad = self.z_surface.uv_to_xy( np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) + + # transform z_scale + self.z_scale = 1.0 + self.z_shift = 0.0 def xy_to_uv(self, xy_points): @@ -799,16 +948,16 @@ def z_eval_array(self, uv_points): result = np.zeros(uv_points.shape[0]) for i, uv in enumerate(uv_points): - iuv = np.floor(uv / self.uv_step) + iuv = np.floor(uv / self._uv_step) iu = max(0, min(self.shape[0] - 2, int(iuv[0]))) iv = max(0, min(self.shape[1] - 2, int(iuv[1]))) iuv = np.array([iu, iv]) - uv_loc = uv / self.uv_step - iuv + uv_loc = uv / self._uv_step - iuv u_loc = np.array([1 - uv_loc[0], uv_loc[0]]) v_loc = np.array([1 - uv_loc[1], uv_loc[1]]) - Z_mat = self.grid_z[iv: (iv + 2), iu: (iu + 2)] - result[i] = self.z_surface.z_scale*(v_loc.dot(Z_mat).dot(u_loc)) + self.z_surface.z_shift + Z_mat = self.grid_uvz[iu: (iu + 2), iv: (iv + 2), 2] + result[i] = self.z_scale*(v_loc.dot(Z_mat).dot(u_loc)) + self.z_shift return result diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 188217a..2b01a8c 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -102,6 +102,15 @@ def curve_from_grid(points, **kwargs): :param points - N x D array, D is dimension :param nt Prescribed number of poles of the resulting spline. :return: Curve object. + + TODO: + - Measure efficiency. Estimate how good we can be. Do it our self if we can do at leas 10 times better. + - Find out which method is used. Hoschek (4.4.1) refers to several methods how to determine parametrization of + the curve, i.e. Find parameters t_i to the given approximation points P_i. + - Further on it is not clear what is the mening of the 's' parameter and how one cna influence tolerance and smoothness. + - Some sort of adaptivity is used. + + """ deg = kwargs.get('degree', 3) tol = kwargs.get('tol', 0.01) @@ -122,6 +131,16 @@ def curve_from_grid(points, **kwargs): class _SurfaceApprox: + """ + TODO: + - Check efficiency of scipy methods, compare it to our approach assuming theoretical number of operations. + - Optimize construction of A regularization matrix. + - Compute BtB directly during single assembly pass, local 9x9 matricies as in A matrix. + - In contradiction to some literature (Hoschek) solution of the LS system is fast as long as the basis is local ( + this is true for B-splines). + - Extensions to fitting X and Y as well - general Surface + + """ diff --git a/src/bspline_plot.py b/src/bspline_plot.py index 6c843d8..2542444 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -22,7 +22,12 @@ def plot_curve_2d(curve, n_points=100, **kwargs): coords = [curve.eval(t) for t in t_coord] x_coord, y_coord = zip(*coords) - return plt.plot(x_coord, y_coord, **kwargs) + + img = plt.plot(x_coord, y_coord, **kwargs) + if kwargs.get('poles', False): + plot_curve_poles_2d(curve) + + return img def plot_curve_poles_2d(curve, **kwargs): @@ -70,7 +75,11 @@ def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): Z = Z.reshape(U.shape) # Plot the surface. - return fig_ax.plot_surface(X, Y, Z, **kwargs) + img = fig_ax.plot_surface(X, Y, Z, **kwargs) + if kwargs.get('poles', False): + plot_curve_poles_2d(surface) + return img + def plot_surface_poles_3d(surface, fig_ax, **kwargs): diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 0c54d5b..0ff7016 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -39,13 +39,13 @@ def plot_approx_transformed_grid(self): self.plot_surf(z_surf) def plot_plane(self): - surf = bs_approx.plane_surface([ [0, 0, 0], [1,1,0], [0,0,1] ]) + surf = bs_approx.plane_surface([ [0.0, 0, 0], [1.0, 0, 0], [0.0, 0, 1] ], overhang=0.1) self.plot_surf(surf) def test_surface_approx(self): #self.plot_approx_grid() #self.plot_approx_transformed_grid() - self.plot_plane() + #self.plot_plane() pass @@ -63,5 +63,5 @@ def test_approx_2d(self): bs_plot.plot_curve_poles_2d(curve) - plt.plot(x_vec, y_vec, color='green') - plt.show() \ No newline at end of file + #plt.plot(x_vec, y_vec, color='green') + #plt.show() \ No newline at end of file diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 9d05aa1..a766bfe 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -3,11 +3,18 @@ import numpy as np import math import bspline_plot as bs_plot -import matplotlib.pyplot as plt +from mpl_toolkits.mplot3d import Axes3D +import matplotlib.pyplot as plt class TestSplineBasis: + def test_find_knot_interval(self): + """ + test methods: + - make_equidistant + - find_knot_interval + """ eq_basis = bs.SplineBasis.make_equidistant(2, 100) assert eq_basis.find_knot_interval(0.0) == 0 assert eq_basis.find_knot_interval(0.001) == 0 @@ -27,8 +34,22 @@ def test_find_knot_interval(self): i_found = basis.find_knot_interval(x) assert i_found == interval - 2, "i_found: {} i: {} j: {} x: {} ".format(i_found, interval-2, j, x) - def plot_basis(self, degree): - eq_basis = bs.SplineBasis.make_equidistant(degree, 4) + + def test_packed_knots(self): + """ + Test: + - make_from_packed_knots + - pack_knots + :return: + """ + packed = [(-0.1, 3), (0,1), (1,1), (1.1, 3)] + basis = bs.SplineBasis.make_from_packed_knots(2, packed) + assert packed == basis.pack_knots() + + + + + def plot_basis(self, eq_basis): n_points = 401 x_coord = np.linspace(eq_basis.domain[0], eq_basis.domain[1], n_points) @@ -40,22 +61,11 @@ def plot_basis(self, degree): def test_eval(self): - #self.plot_basis(0) - #self.plot_basis(1) - #self.plot_basis(2) - #self.plot_basis(3) + #self.plot_basis(bs.SplineBasis.make_equidistant(0, 4)) knots = np.array([0, 0, 0, 0.1880192, 0.24545785, 0.51219762, 0.82239001, 1., 1. , 1.]) basis = bs.SplineBasis(2, knots) - n_points = 100 - x_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) - - for i_base in range(basis.size): - y_coord = [ basis.eval(i_base, x) for x in x_coord ] - plt.plot(x_coord, y_coord) - - plt.show() - + # self.plot_basis(basis) eq_basis = bs.SplineBasis.make_equidistant(0, 2) assert eq_basis.eval(0, 0.0) == 1.0 @@ -75,6 +85,23 @@ def test_eval(self): assert eq_basis.eval(1, 0.125) == 0.5 assert eq_basis.eval(2, 1.0) == 0.0 + # check summation to one: + for deg in range(0, 10): + basis = bs.SplineBasis.make_equidistant(deg, 2) + for x in np.linspace(basis.domain[0], basis.domain[1], 10): + s = sum([ basis.eval(i, x) for i in range(basis.size) ]) + assert np.isclose(s, 1.0) + + def fn_supp(self): + basis = bs.SplineBasis.make_equidistant(2, 4) + for i in range(basis.size): + supp = basis.fn_supp(i) + for x in np.linspace(supp[0] - 0.1, supp[0], 10): + assert basis.eval(i, x) == 0.0 + for x in np.linspace(supp[0] + 0.001, supp[1] - 0.001, 10): + assert basis.eval(i, x) > 0.0 + for x in np.linspace(supp[1], supp[1] + 0.1): + assert basis.eval(i, x) == 0.0 def test_linear_poles(self): eq_basis = bs.SplineBasis.make_equidistant(2, 4) @@ -86,10 +113,8 @@ def test_linear_poles(self): x = np.dot(b_vals, poles) assert np.abs( x - t ) < 1e-15 - - def check_eval_vec(self, basis, i, t): - vec = basis.eval_base_vector(i, t) + vec = basis.eval_vector(i, t) for j in range(basis.degree + 1): assert vec[j] == basis.eval(i + j, t) @@ -100,7 +125,7 @@ def plot_basis_vec(self, basis): y_coords = np.zeros( (basis.size, x_coord.shape[0]) ) for i, x in enumerate(x_coord): idx = basis.find_knot_interval(x) - y_coords[idx : idx + basis.degree + 1, i] = basis.eval_base_vector(idx, x) + y_coords[idx : idx + basis.degree + 1, i] = basis.eval_vector(idx, x) for i_base in range(basis.size): plt.plot(x_coord, y_coords[i_base, :]) @@ -108,7 +133,7 @@ def plot_basis_vec(self, basis): plt.show() - def test_eval_base_vec(self): + def test_eval_vec(self): basis = bs.SplineBasis.make_equidistant(2, 4) # self.plot_basis_vec(basis) self.check_eval_vec(basis, 0, 0.1) @@ -117,7 +142,6 @@ def test_eval_base_vec(self): self.check_eval_vec(basis, 3, 0.8) self.check_eval_vec(basis, 3, 1.0) - basis = bs.SplineBasis.make_equidistant(3, 4) # self.plot_basis_vec(basis) self.check_eval_vec(basis, 0, 0.1) @@ -129,7 +153,7 @@ def test_eval_base_vec(self): def check_diff_vec(self, basis, i, t): - vec = basis.eval_diff_base_vector(i, t) + vec = basis.eval_diff_vector(i, t) for j in range(basis.degree + 1): assert np.abs(vec[j] - basis.eval_diff(i + j, t)) < 1e-15 @@ -140,7 +164,7 @@ def plot_basis_diff(self, basis): y_coords = np.zeros( (basis.size, x_coord.shape[0]) ) for i, x in enumerate(x_coord): idx = basis.find_knot_interval(x) - y_coords[idx : idx + basis.degree + 1, i] = basis.eval_diff_base_vector(idx, x) + y_coords[idx : idx + basis.degree + 1, i] = basis.eval_diff_vector(idx, x) for i_base in range(basis.size): plt.plot(x_coord, y_coords[i_base, :]) @@ -166,6 +190,7 @@ def test_eval_diff_base_vec(self): self.check_diff_vec(basis, 3, 1.0) + class TestCurve: def plot_4p(self): @@ -174,15 +199,24 @@ def plot_4p(self): basis = bs.SplineBasis.make_equidistant(degree, 2) curve = bs.Curve(basis, poles) - bs_plot.plot_curve_2d(curve) - bs_plot.plot_curve_poles_2d(curve) + bs_plot.plot_curve_2d(curve, poles=True) + b00, b11 = curve.aabb() + b01 = [b00[0], b11[1]] + b10 = [b11[0], b00[1]] + bb = np.array([b00, b10, b11, b01, b00]) + + plt.plot( bb[:, 0], bb[:, 1], color='green') plt.show() def test_evaluate(self): # self.plot_4p() + # TODO: make numerical tests with explicitely computed values + # TODO: test rational curves, e.g. circle + pass - # TODO: test rational curves, e.g. circle + + @@ -204,8 +238,7 @@ def plot_extrude(self): u_basis = bs.SplineBasis.make_equidistant(2, 1) v_basis = bs.SplineBasis.make_equidistant(2, 2) surface_extrude = bs.Surface( (u_basis, v_basis), poles) - bs_plot.plot_surface_3d(surface_extrude, ax) - bs_plot.plot_surface_poles_3d(surface_extrude, ax) + bs_plot.plot_surface_3d(surface_extrude, ax, poles = True) plt.show() def plot_function(self): @@ -226,14 +259,10 @@ def function(x): plt.show() def test_evaluate(self): - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.pyplot as plt - # self.plot_extrude() # self.plot_function() - - # TODO: test rational surfaces, e.g. sphere + pass @@ -282,8 +311,7 @@ def function(x): def make_point_grid(self): nu, nv = 5,6 grid = bs.make_function_grid(TestPointGrid.function, 5, 6).reshape(nu*nv, 3) - surf = bs.GridSurface() - surf.init_from_seq(grid.T) + surf = bs.GridSurface(grid) return surf def check_surface(self, surf, xy_mat, xy_shift, z_mat): @@ -308,9 +336,12 @@ def check_surface(self, surf, xy_mat, xy_shift, z_mat): uvz = np.concatenate( (UV.T, Z_grid[:, None]), axis = 1) for u, v, z_approx in uvz: - z_func = z_mat[0]*self.function([u,v]) + z_mat[1] + z_func = z_mat[0]*TestPointGrid.function([u,v]) + z_mat[1] eps = max(eps, math.fabs( z_approx - z_func)) - assert math.fabs( z_approx - z_func) < tol + assert np.isclose(z_approx, z_func, atol = tol) + + x, y, z = surf.eval_array(np.array([[u,v]]))[0] + assert np.isclose(z_approx, z, atol=tol) print("Max norm: ", eps, "Tol: ", tol) def test_grid_surface(self): From 6fef8208f7b106d3fe40b952b481b5cbef97da19 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 14:15:53 +0200 Subject: [PATCH 26/67] Fix bspline and its tests. --- src/bspline.py | 11 +++++--- src/bspline_plot.py | 45 ++++++++++++++++++++++++++++++-- tests/test_bs_approx.py | 9 +++++-- tests/test_bspline.py | 57 +++++++++++++++++++++++++++++------------ 4 files changed, 98 insertions(+), 24 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 4219ad8..e6a5abc 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -902,8 +902,8 @@ def transform(self, xy_mat, z_mat): self.quad = self.z_surface.uv_to_xy( np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) # transform z_scale - self.z_scale = 1.0 - self.z_shift = 0.0 + self.z_scale *= z_mat[0] + self.z_shift = z_mat[0]*self.z_shift + z_mat[1] def xy_to_uv(self, xy_points): @@ -923,7 +923,10 @@ def uv_to_xy(self, uv_points): def eval_array(self, uv_points): - return self.z_surface.eval_array(uv_points) + xyz = self.z_surface.eval_array(uv_points) + xyz[:, 2] *= self.z_scale + xyz[:, 2] += self.z_shift + return xyz def z_eval_xy_array(self, xy_points): @@ -957,7 +960,7 @@ def z_eval_array(self, uv_points): u_loc = np.array([1 - uv_loc[0], uv_loc[0]]) v_loc = np.array([1 - uv_loc[1], uv_loc[1]]) Z_mat = self.grid_uvz[iu: (iu + 2), iv: (iv + 2), 2] - result[i] = self.z_scale*(v_loc.dot(Z_mat).dot(u_loc)) + self.z_shift + result[i] = self.z_scale*(u_loc.dot(Z_mat).dot(v_loc)) + self.z_shift return result diff --git a/src/bspline_plot.py b/src/bspline_plot.py index 2542444..e24b7cf 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -23,8 +23,9 @@ def plot_curve_2d(curve, n_points=100, **kwargs): coords = [curve.eval(t) for t in t_coord] x_coord, y_coord = zip(*coords) + poles = kwargs.pop('poles', False) img = plt.plot(x_coord, y_coord, **kwargs) - if kwargs.get('poles', False): + if poles: plot_curve_poles_2d(curve) return img @@ -41,6 +42,7 @@ def plot_curve_poles_2d(curve, **kwargs): return plt.plot(x_poles, y_poles, 'bo', color='red', **kwargs) + def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): """ Plot a surface in 3d. @@ -75,13 +77,52 @@ def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): Z = Z.reshape(U.shape) # Plot the surface. + poles = kwargs.pop('poles', False) + img = fig_ax.plot_surface(X, Y, Z, **kwargs) - if kwargs.get('poles', False): + if poles: plot_curve_poles_2d(surface) return img +def plot_grid_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): + """ + Plot a surface in 3d. + Usage: + from mpl_toolkits.mplot3d import Axes3D + import matplotlib.pyplot as plt + fig = plt.figure() + ax = fig.gca(projection='3d') + plot_surface_3d(surface, ax) + plt.show() + + :param surface: Parametric surface in 3d. + :param fig_ax: Axes object: + :param n_points: (nu, nv), nu*nv - number of evaluation point + :param kwargs: surface_plot additional options + :return: The plot object. + """ + + u_coord = np.linspace(0, 1.0, n_points[0]) + v_coord = np.linspace(0, 1.0, n_points[1]) + + U, V = np.meshgrid(u_coord, v_coord) + points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) + + xyz = surface.eval_array(points) + X, Y, Z = xyz.T + + X = X.reshape(U.shape) + Y = Y.reshape(U.shape) + Z = Z.reshape(U.shape) + + #Z = surface.z_eval_array(points).reshape(U.shape) + # Plot the surface. + img = fig_ax.plot_surface(U, V, Z, **kwargs) + return img + + def plot_surface_poles_3d(surface, fig_ax, **kwargs): """ Plot poles of the B-spline curve. diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 0ff7016..eab5418 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -53,7 +53,7 @@ def test_surface_approx(self): class TestCurveApprox: - def test_approx_2d(self): + def plot_approx_2d(self): x_vec = np.linspace(1.1, 3.0, 100) y_vec = np.array([ np.sin(10*x) for x in x_vec ]) points = np.stack( (x_vec, y_vec), axis=1) @@ -62,6 +62,11 @@ def test_approx_2d(self): bs_plot.plot_curve_2d(curve, 1000) bs_plot.plot_curve_poles_2d(curve) + plt.show() #plt.plot(x_vec, y_vec, color='green') - #plt.show() \ No newline at end of file + #plt.show() + + def test_approx_2d(self): + # self.plot_approx_2d() + pass \ No newline at end of file diff --git a/tests/test_bspline.py b/tests/test_bspline.py index a766bfe..e18c17d 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -314,6 +314,25 @@ def make_point_grid(self): surf = bs.GridSurface(grid) return surf + + def plot_check_surface(self, XYZ_grid_eval, XYZ_surf_eval, XYZ_func_eval): + fig = plt.figure() + ax = fig.gca(projection='3d') + ax.plot_surface(XYZ_grid_eval[:, :, 0], XYZ_grid_eval[:, :, 1], XYZ_grid_eval[:, :, 2], color='blue') + ax.plot_surface(XYZ_surf_eval[:, :, 0], XYZ_surf_eval[:, :, 1], XYZ_surf_eval[:, :, 2], color='red') + ax.plot_surface(XYZ_func_eval[:, :, 0], XYZ_func_eval[:, :, 1], XYZ_func_eval[:, :, 2], color='green') + plt.show() + + def grid_cmp(self, a, b, tol): + a_z = a[:, :, 2].ravel() + b_z = b[:, :, 2].ravel() + eps = 0.0 + for i, (za, zb) in enumerate(zip(a_z, b_z)): + diff = np.abs( za - zb) + eps = max(eps, diff) + assert diff < tol, " |a({}) - b({})| > tol({}), idx: {}".format(za, zb, tol, i) + print("Max norm: ", eps, "Tol: ", tol) + def check_surface(self, surf, xy_mat, xy_shift, z_mat): """ TODO: Make this a general function - evaluate a surface on a grid, use it also in other tests @@ -324,25 +343,27 @@ def check_surface(self, surf, xy_mat, xy_shift, z_mat): # surface on unit square U = np.linspace(0.0, 1.0, nu) V = np.linspace(0.0, 1.0, nv) - U_grid, V_grid = np.meshgrid(U,V) + V_grid, U_grid = np.meshgrid(V,U) + + UV = np.stack( [U_grid.ravel(), V_grid.ravel()], axis = 1 ) + XY = xy_mat.dot(UV.T).T + xy_shift + Z = surf.z_eval_xy_array(XY) + XYZ_grid_eval = np.concatenate( (XY, Z[:, None]) , axis = 1).reshape(nu, nv, 3) + + XYZ_surf_eval = surf.eval_array(UV).reshape(nu, nv, 3) + + z_func_eval = np.array([ z_mat[0]*TestPointGrid.function([u,v]) + z_mat[1] for u, v in UV ]) + XYZ_func_eval = np.concatenate( (XY, z_func_eval[:, None]), axis =1 ).reshape(nu, nv, 3) + + #self.plot_check_surface(XYZ_grid_eval, XYZ_surf_eval, XYZ_func_eval) - UV = np.vstack([U_grid.ravel(), V_grid.ravel()]) - XY = xy_mat.dot(UV).T + xy_shift - Z_grid = surf.z_eval_xy_array(XY) eps = 0.0 hx = 1.0 / surf.shape[0] hy = 1.0 / surf.shape[1] - tol = 0.5* ( hx*hx + 2*hx*hy + hy*hy) + tol = 2.0 * 0.5* ( hx*hx + 2*hx*hy + hy*hy) - uvz = np.concatenate( (UV.T, Z_grid[:, None]), axis = 1) - for u, v, z_approx in uvz: - z_func = z_mat[0]*TestPointGrid.function([u,v]) + z_mat[1] - eps = max(eps, math.fabs( z_approx - z_func)) - assert np.isclose(z_approx, z_func, atol = tol) - - x, y, z = surf.eval_array(np.array([[u,v]]))[0] - assert np.isclose(z_approx, z, atol=tol) - print("Max norm: ", eps, "Tol: ", tol) + self.grid_cmp(XYZ_func_eval, XYZ_grid_eval, tol) + self.grid_cmp(XYZ_func_eval, XYZ_surf_eval, tol) def test_grid_surface(self): xy_mat = np.array([ [1.0, 0.0], [0.0, 1.0] ]) @@ -350,7 +371,11 @@ def test_grid_surface(self): z_shift = np.array([1.0, 0.0]) surface = self.make_point_grid() - self.check_surface(surface, xy_mat, xy_shift, z_shift) + # fig = plt.figure() + # ax = fig.gca(projection='3d') + # bs_plot.plot_grid_surface_3d(surface, ax) + # plt.show() + # self.check_surface(surface, xy_mat, xy_shift, z_shift) # transformed surface xy_mat = np.array([ [3.0, -3.0], [2.0, 2.0] ]) / math.sqrt(2) @@ -358,7 +383,7 @@ def test_grid_surface(self): z_shift = np.array([1.0, 1.3]) surface = self.make_point_grid() - surface.z_surface.transform(np.concatenate((xy_mat, xy_shift.T), axis=1), z_shift) + surface.transform(np.concatenate((xy_mat, xy_shift.T), axis=1), z_shift) self.check_surface(surface, xy_mat, xy_shift, z_shift) From 45de1cebb51447061deb41d0f52077905219383a Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 20:35:57 +0200 Subject: [PATCH 27/67] Add check method to SplineBase - add check method for rounding values cloase to boundary of the domain - few more fixes in GridSurface --- src/bspline.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index e6a5abc..ceb5259 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -174,6 +174,27 @@ def pack_knots(self): return packed_knots + def check(self, t, rtol = 1e-10): + """ + Check that 't' is inside basis domain. Fix small perturbations. + :param t: + :return: + """ + if self.domain[0] <= t <= self.domain[1]: + return t + else: + tol = (np.abs(t) + 1e-4)*rtol + assert self.domain[0] - tol <= t <= self.domain[1] + tol, \ + "Evaluate spline, t={}, out of domain: {}.".format(t, self.domain) + if t < self.domain[0]: + return self.domain[0] + else: + return self.domain[1] + + + + self.domain[0] + def find_knot_interval(self, t): """ Find the first non-empty knot interval containing the value 't'. @@ -189,6 +210,7 @@ def find_knot_interval(self, t): return min(idx, self.n_intervals - 1) # deals with t == self.knots[-1] + def _basis(self, deg, idx, t): """ Recursive evaluation of basis function of given degree and index. @@ -514,7 +536,8 @@ def eval(self, u, v): - use basis.eval_vector - test evaluation for rational curves """ - + u = self.u_basis.check(u) + v = self.v_basis.check(v) iu = self.u_basis.find_knot_interval(u) iv = self.v_basis.find_knot_interval(v) du = self.u_basis.degree + 1 @@ -797,18 +820,13 @@ def __init__(self, point_seq, tolerance = 1e-6): self.z_scale = 1.0 self.z_shift = 0.0 - # self._grid_z = point_seq[2, :].reshape(self.nv, self.nu) - # grid of original Z values, format as matrix - - self._uv_step = (1.0 / float(self.shape[0] - 1), 1.0 / float(self.shape[1] - 1)) # Grid step in u, v direction respectively. - self._make_z_surface( point_seq) - - self.points_xyz = point_seq - # Original sequance of XYZ points + self.grid_uvz = None + # numpy array nu x nv x 3 with original XYZ values transformed to unit square. + self._make_z_surface( point_seq) self._check_map() @@ -881,7 +899,7 @@ def _make_z_surface(self, points): def _check_map(self): # check that xy_to_uv works fine uv_quad = self.xy_to_uv(self.quad) - print( uv_quad ) + #print( "Check quad: ", uv_quad ) assert np.allclose( uv_quad, np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) From 17daabac538c207012961f1746005325bc3c878d Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 20:37:47 +0200 Subject: [PATCH 28/67] Fixes and new tests for approximation - surface approximation modify size of approximation in order to have well posed problem (more points then unknowns) - fix several bugs - add numerical tests --- src/bspline_approx.py | 91 ++++++++++++++++++++-------- tests/test_bs_approx.py | 127 +++++++++++++++++++++++++++++++++++----- tests/test_bspline.py | 5 +- 3 files changed, 180 insertions(+), 43 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 2b01a8c..479abf4 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -144,11 +144,11 @@ class _SurfaceApprox: - def __init__(self, grid_surface, nuv, **kwargs): + def __init__(self, grid_surface, nuv=None, **kwargs): + self.degree = np.array((2, 2)) self.grid_surf = grid_surface - self.u_basis = bs.SplineBasis.make_equidistant(2, nuv[0]) - self.v_basis = bs.SplineBasis.make_equidistant(2, nuv[1]) - self.regularization_weight = kwargs.get('reg_weight', 1.0) + self.nuv = nuv + self.regularization_weight = kwargs.get('reg_weight', 0.001) def get_approximation(self): if not hasattr(self, 'z_surf'): @@ -172,28 +172,53 @@ def approx_chol(self): print('Transforming points to parametric space ...') start_time = time.time() - points = self.grid_surf.points_xyz - points_uv = self.grid_surf.xy_to_uv( points[:, 0:2] ) + points_uvz = self.grid_surf.grid_uvz.reshape(-1, 3) + points_uv = points_uvz[:, 0:2] # remove points far from unit square eps = 1.0e-15 cut_min = np.array( [ -eps, -eps ]) cut_max = np.array( [ 1+eps, 1+eps ]) - in_idx = np.all(np.logical_and(cut_min < points_uv, points_uv <= cut_max), axis=1) points_uv = points_uv[in_idx] - points_z = points[in_idx, 2][:,None] + self.points_z = points_uvz[in_idx, 2][:,None] + print("Number of points out of the grid domain: {}".format(len(points_uv) - np.sum(in_idx))) # snap to unit square points_uv = np.maximum(points_uv, np.array([0.0, 0.0])) - points_uv = np.minimum(points_uv, np.array([1.0, 1.0])) - + self.points_uv = np.minimum(points_uv, np.array([1.0, 1.0])) + + # determine number of knots + assert len(self.points_uv) == len(self.points_z) + n_points = len(self.points_uv) + + grid_shape = np.asarray(self.grid_surf.shape, dtype=int) + if self.nuv == None: + nuv = np.floor_divide( grid_shape / 3 ) + else: + nuv = np.floor(np.array(self.nuv)) + nuv = np.minimum( nuv, grid_shape - self.degree - 2) + size_u, size_v = nuv + self.degree + + # try to make number of unknowns less then number of remaining points + # +1 to improve determination + if (size_u + 1) * (size_v + 1) > n_points: + sv = np.floor(np.sqrt( n_points * size_v / size_u )) + su = np.floor(sv * size_u / size_v) + nuv = np.array( [su, sv] ) - self.degree + nuv = nuv.astype(int) + if nuv[0] < 1 or nuv[1] < 1: + raise Exception("Two few points, {}, to make approximation, degree: {}".format(n_points, self.degree)) + + print("Using {} x {} B-spline approximation.".format(nuv[0], nuv[1])) + self.u_basis = bs.SplineBasis.make_equidistant(2, nuv[0]) + self.v_basis = bs.SplineBasis.make_equidistant(2, nuv[1]) - self.grid_uvz = np.concatenate((points_uv, points_z), axis=1) end_time = time.time() print('Computed in {0} seconds.'.format(end_time - start_time)) + # Own computation of approximation print('Creating B matrix ...') start_time = time.time() @@ -215,35 +240,45 @@ def approx_chol(self): print('Computed in {0} seconds.'.format(end_time - start_time)) + print('Computing A and B svds approximation ...') + start_time = time.time() + bb_norm = scipy.sparse.linalg.svds(bb_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, maxiter=300, return_singular_vectors=False) a_norm = scipy.sparse.linalg.svds(a_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, maxiter=300, return_singular_vectors=False) c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat - g_vec = self.grid_uvz[:, 2] + g_vec = self.points_z b_vec = b_mat.transpose() * g_vec + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) print('Computing Z coordinates ...') start_time = time.time() z_vec = scipy.sparse.linalg.spsolve(c_mat, b_vec) + assert not np.isnan(np.sum(z_vec)), "Singular matrix for approximation." + print(type(z_vec)) end_time = time.time() print('Computed in {0} seconds.'.format(end_time - start_time)) - # print('Computing differences ...') - # start_time = time.time() - # diff = (numpy.matrix(b_mat * z_vec).transpose() - g_vec).tolist() - # diff = [item[0] for item in diff] - # end_time = time.time() - # print('Computed in {0} seconds.'.format(end_time - start_time)) + print('Computing differences ...') + start_time = time.time() + diff = np.abs(b_mat * z_vec - g_vec) + max_diff = np.max(diff) + print("Approximation error (max norm): {}".format(max_diff) ) + end_time = time.time() + print('Computed in {0} seconds.'.format(end_time - start_time)) # Construct Z-Surface - poles_z = z_vec.reshape(self.u_basis.size, self.v_basis.size, 1) - surface_z = bs.Surface((self.u_basis, self.v_basis), poles_z) - z_surf = bs.Z_Surface(self.grid_surf.quad, surface_z) + poles_z = z_vec.reshape(self.v_basis.size, self.u_basis.size).T + poles_z *= self.grid_surf.z_scale + poles_z += self.grid_surf.z_shift + surface_z = bs.Surface((self.u_basis, self.v_basis), poles_z[:,:,None]) + z_surf = bs.Z_Surface(self.grid_surf.quad[0:3], surface_z) return z_surf @@ -261,7 +296,7 @@ def build_ls_matrix(self): """ u_n_basf = self.u_basis.size v_n_basf = self.v_basis.size - n_points = self.grid_uvz.shape[0] + n_points = self.points_uv.shape[0] n_uv_loc_nz = (self.u_basis.degree + 1) * (self.v_basis.degree + 1) row = np.zeros(n_points * n_uv_loc_nz) @@ -273,16 +308,19 @@ def build_ls_matrix(self): interval = np.empty((n_points, 2)) for idx in range(0, n_points): - u, v = self.grid_uvz[idx, 0:2] + u, v = self.points_uv[idx, 0:2] iu = self.u_basis.find_knot_interval(u) - iv = self.u_basis.find_knot_interval(v) + iv = self.v_basis.find_knot_interval(v) u_base_vec = self.u_basis.eval_base_vector(iu, u) - v_base_vec = self.u_basis.eval_base_vector(iv, v) + v_base_vec = self.v_basis.eval_base_vector(iv, v) # Hard-coded Kronecker product (problem based) for n in range(0, 3): data[nnz_b + 3 * n:nnz_b + 3 * (n + 1)] = v_base_vec[n] * u_base_vec for m in range(0, 3): - col[nnz_b + (3 * n) + m] = (iv + n) * u_n_basf + iu + m + col_item = (iv + n) * u_n_basf + iu + m + print(idx, iu, iv, n, m, col_item) + + col[nnz_b + (3 * n) + m] = col_item row[nnz_b:nnz_b + 9] = idx nnz_b += 9 @@ -308,6 +346,7 @@ def _basis_in_q_points(self, basis): for j in range(nq_points): up = us + uil * self._q_points[j] q_point[n] = up + # TODO: no need to find interval, idx = i idx = basis.find_knot_interval(up) u_base_vec = basis.eval_base_vector(idx, up) u_base_vec_diff = basis.eval_diff_base_vector(idx, up) diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index eab5418..536c1b2 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -11,6 +11,68 @@ def function_sin_cos(x): return math.sin(x[0] * 4) * math.cos(x[1] * 4) +def gen_uv_grid(nu, nv): + # surface on unit square + U = np.linspace(0.0, 1.0, nu) + V = np.linspace(0.0, 1.0, nv) + V_grid, U_grid = np.meshgrid(V, U) + + return np.stack([U_grid.ravel(), V_grid.ravel()], axis=1) + +def eval_func_on_grid(func, xy_mat, xy_shift, z_mat, shape=(50, 50)): + nu, nv = shape + UV = gen_uv_grid(nu, nv) + XY = xy_mat.dot(UV.T).T + xy_shift + z_func_eval = np.array([z_mat[0] * func([u, v]) + z_mat[1] for u, v in UV]) + return np.concatenate((XY, z_func_eval[:, None]), axis=1).reshape(nu, nv, 3) + + +def eval_z_surface_on_grid(surface, xy_mat, xy_shift, shape=(50, 50)): + nu, nv = shape + UV = gen_uv_grid(nu, nv) + XY = xy_mat.dot(UV.T).T + xy_shift + Z = surface.z_eval_xy_array(XY) + return np.concatenate((XY, Z[:, None]), axis=1).reshape(nu, nv, 3) + + +def eval_surface_on_grid(surface, shape=(50, 50)): + nu, nv = shape + UV = gen_uv_grid(nu, nv) + return surface.eval_array(UV).reshape(nu, nv, 3) + +def plot_cmp(a_grid, b_grid): + fig = plt.figure() + ax = fig.gca(projection='3d') + ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], a_grid[:, :, 2], color='green') + ax.plot_surface(b_grid[:, :, 0], b_grid[:, :, 1], b_grid[:, :, 2], color='red') + plt.show() + + diff = b_grid - a_grid + fig = plt.figure() + ax = fig.gca(projection='3d') + ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 0], color='red') + ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 1], color='green') + ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 2], color='blue') + plt.show() + +def grid_cmp( a, b, tol): + a_z = a[:, :, 2].ravel() + b_z = b[:, :, 2].ravel() + eps = 0.0 + n_err=0 + for i, (za, zb) in enumerate(zip(a_z, b_z)): + diff = np.abs( za - zb) + eps = max(eps, diff) + if diff > tol: + n_err +=1 + if n_err < 10: + print(" {} =|a({}) - b({})| > tol({}), idx: {}".format(diff, za, zb, tol, i) ) + elif n_err == 10: + print("... skipping") + print("Max norm: ", eps, "Tol: ", tol) + if eps > tol: + plot_cmp(a, b) + class TestSurfaceApprox: # todo: numerical test @@ -21,31 +83,64 @@ def plot_surf(self, surf): plt.show() - def plot_approx_grid(self): - points = bs.make_function_grid(function_sin_cos, 20, 30) - gs = bs.GridSurface() - gs.init_from_seq(points.reshape(-1, 3).T) - z_surf = bs_approx.surface_from_grid(gs, (3,4) ) - self.plot_surf(z_surf) + def test_approx_func(self): + xy_mat = np.array( [ [1.0, -1.0, 10 ], [1.0, 1.0, 20 ]]) # rotate left pi/4 and blow up 1.44 + z_mat = np.array( [2.0, -10] ) + + xyz_func = eval_func_on_grid(function_sin_cos, xy_mat[:2,:2], xy_mat[:, 2], z_mat) + + print("Compare: Func - GridSurface.transform") + points = bs.make_function_grid(function_sin_cos, 40, 30) + gs = bs.GridSurface(points.reshape(-1, 3)) + gs.transform(xy_mat, z_mat) + xyz_grid = eval_z_surface_on_grid(gs, xy_mat[:2,:2], xy_mat[:, 2]) + #grid_cmp(xyz_func, xyz_grid, 0.02) + + print("Compare: Func - GridSurface.transform.z_surf") + xyz_grid = eval_z_surface_on_grid(gs.z_surface, xy_mat[:2, :2], xy_mat[:, 2]) + xyz_grid[:,:,2] *= z_mat[0] + xyz_grid[:, :, 2] += z_mat[1] + #grid_cmp(xyz_func, xyz_grid, 0.02) + + print("Compare: Func - GridSurface.transform.Z_surf_approx") + z_surf = bs_approx.surface_from_grid(gs, (7, 5)) + #z_surf.transform(xy_mat, z_mat) + xyz_grid = eval_z_surface_on_grid(z_surf, xy_mat[:2,:2], xy_mat[:, 2]) + grid_cmp(xyz_func, xyz_grid, 0.02) + + print("Compare: Func - GridSurface.transform.Z_surf_approx.full_surface") + xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) + #grid_cmp(xyz_func, xyz_grid, 0.01) + + + + + + + #points = bs.make_function_grid(function_sin_cos, 20, 30) + #points = np.dot(points, mat.T) + np.array([10, 20, -5.0]) + #gs = bs.GridSurface(points.reshape(-1, 3)) + #z_surf = bs_approx.surface_from_grid(gs, (3,4) ) + #self.plot_surf(z_surf) + + + + + + def plot_approx_transformed_grid(self): - points = bs.make_function_grid(function_sin_cos, 20, 30) - mat = np.array( [ [2.0, 1.0, 0.0 ], [1.0, 2.0, 0.0 ], [0.0, 0.0, 0.5 ]]) - points = np.dot(points, mat.T) + np.array([10, 20, -5.0]) - gs = bs.GridSurface() - gs.init_from_seq(points.reshape(-1, 3).T) - z_surf = bs_approx.surface_from_grid(gs, (3,4) ) - self.plot_surf(z_surf) + pass def plot_plane(self): surf = bs_approx.plane_surface([ [0.0, 0, 0], [1.0, 0, 0], [0.0, 0, 1] ], overhang=0.1) self.plot_surf(surf) def test_surface_approx(self): - #self.plot_approx_grid() - #self.plot_approx_transformed_grid() - #self.plot_plane() + # self.plot_approx_grid() + # self.plot_approx_transformed_grid() + # self.plot_plane() pass diff --git a/tests/test_bspline.py b/tests/test_bspline.py index e18c17d..b488d73 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -360,7 +360,7 @@ def check_surface(self, surf, xy_mat, xy_shift, z_mat): eps = 0.0 hx = 1.0 / surf.shape[0] hy = 1.0 / surf.shape[1] - tol = 2.0 * 0.5* ( hx*hx + 2*hx*hy + hy*hy) + tol = 0.5* ( hx*hx + 2*hx*hy + hy*hy) self.grid_cmp(XYZ_func_eval, XYZ_grid_eval, tol) self.grid_cmp(XYZ_func_eval, XYZ_surf_eval, tol) @@ -381,9 +381,12 @@ def test_grid_surface(self): xy_mat = np.array([ [3.0, -3.0], [2.0, 2.0] ]) / math.sqrt(2) xy_shift = np.array([[-2.0, 5.0 ]]) z_shift = np.array([1.0, 1.3]) + new_quad = np.array([ [0, 1.0], [0,0], [1, 0], [1, 1]]) + new_quad = new_quad.dot(xy_mat[:2,:2].T) + xy_shift surface = self.make_point_grid() surface.transform(np.concatenate((xy_mat, xy_shift.T), axis=1), z_shift) + assert np.all(surface.quad == new_quad), "surf: {} ref: {}".format(surface.quad, new_quad) self.check_surface(surface, xy_mat, xy_shift, z_shift) From 51d907b0f3bc384b6a2213f25972dee4f03d434e Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 21:50:29 +0200 Subject: [PATCH 29/67] Fix computing differences --- src/bspline_approx.py | 10 ++++------ tests/test_bs_approx.py | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 479abf4..7b60048 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -234,7 +234,7 @@ def approx_chol(self): print('Computing B^T B matrix ...') start_time = time.time() - bb_mat = b_mat.transpose() * b_mat + bb_mat = b_mat.transpose().dot(b_mat) end_time = time.time() print('Computed in {0} seconds.'.format(end_time - start_time)) @@ -249,8 +249,8 @@ def approx_chol(self): maxiter=300, return_singular_vectors=False) c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat - g_vec = self.points_z - b_vec = b_mat.transpose() * g_vec + g_vec = self.points_z[:, 0] + b_vec = b_mat.transpose().dot( g_vec ) end_time = time.time() print('Computed in {0} seconds.'.format(end_time - start_time)) @@ -267,7 +267,7 @@ def approx_chol(self): print('Computing differences ...') start_time = time.time() - diff = np.abs(b_mat * z_vec - g_vec) + diff = b_mat.dot(z_vec) - g_vec max_diff = np.max(diff) print("Approximation error (max norm): {}".format(max_diff) ) end_time = time.time() @@ -318,8 +318,6 @@ def build_ls_matrix(self): data[nnz_b + 3 * n:nnz_b + 3 * (n + 1)] = v_base_vec[n] * u_base_vec for m in range(0, 3): col_item = (iv + n) * u_n_basf + iu + m - print(idx, iu, iv, n, m, col_item) - col[nnz_b + (3 * n) + m] = col_item row[nnz_b:nnz_b + 9] = idx nnz_b += 9 diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 536c1b2..42340d0 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -90,27 +90,27 @@ def test_approx_func(self): xyz_func = eval_func_on_grid(function_sin_cos, xy_mat[:2,:2], xy_mat[:, 2], z_mat) print("Compare: Func - GridSurface.transform") - points = bs.make_function_grid(function_sin_cos, 40, 30) + points = bs.make_function_grid(function_sin_cos, 30, 40) gs = bs.GridSurface(points.reshape(-1, 3)) gs.transform(xy_mat, z_mat) xyz_grid = eval_z_surface_on_grid(gs, xy_mat[:2,:2], xy_mat[:, 2]) - #grid_cmp(xyz_func, xyz_grid, 0.02) + grid_cmp(xyz_func, xyz_grid, 0.02) print("Compare: Func - GridSurface.transform.z_surf") xyz_grid = eval_z_surface_on_grid(gs.z_surface, xy_mat[:2, :2], xy_mat[:, 2]) xyz_grid[:,:,2] *= z_mat[0] xyz_grid[:, :, 2] += z_mat[1] - #grid_cmp(xyz_func, xyz_grid, 0.02) + grid_cmp(xyz_func, xyz_grid, 0.02) print("Compare: Func - GridSurface.transform.Z_surf_approx") - z_surf = bs_approx.surface_from_grid(gs, (7, 5)) + z_surf = bs_approx.surface_from_grid(gs, (16, 24)) #z_surf.transform(xy_mat, z_mat) xyz_grid = eval_z_surface_on_grid(z_surf, xy_mat[:2,:2], xy_mat[:, 2]) grid_cmp(xyz_func, xyz_grid, 0.02) print("Compare: Func - GridSurface.transform.Z_surf_approx.full_surface") xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) - #grid_cmp(xyz_func, xyz_grid, 0.01) + grid_cmp(xyz_func, xyz_grid, 0.01) From a776944035972816297de3b5d7b41885d1fe3f28 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Sep 2017 22:31:35 +0200 Subject: [PATCH 30/67] Add brep writer --- src/brep_writer.py | 1062 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1062 insertions(+) create mode 100644 src/brep_writer.py diff --git a/src/brep_writer.py b/src/brep_writer.py new file mode 100644 index 0000000..1edadaa --- /dev/null +++ b/src/brep_writer.py @@ -0,0 +1,1062 @@ +import enum +import itertools +import numpy as np +import bspline as bs + + +''' +TODO: +- For Solid make auto conversion from Faces similar to Face from Edges +- For Solid make test that Shells are closed. +- Implement closed test for shells (similar to wire) +- Improve test of slosed (Wire, Shell) to check also orientation of (Edges, Faces). ?? May be both if holes are allowed. +- Rename attributes and methods to more_words_are_separated_by_underscore. +- rename _writeformat to _brep_output +- remove (back) groups parameter from _brepo_output, make checks of IDs at main level (only in DEBUG mode) +- document public methods +''' + + +class ParamError(Exception): + pass + +def check_matrix(mat, shape, values, idx=[]): + ''' + Check shape and type of scalar, vector or matrix. + :param mat: Scalar, vector, or vector of vectors (i.e. matrix). Vector may be list or other iterable. + :param shape: List of dimensions: [] for scalar, [ n ] for vector, [n_rows, n_cols] for matrix. + If a value in this list is None, the dimension can be arbitrary. The shape list is set fo actual dimensions + of the matrix. + :param values: Type or tuple of allowed types of elements of the matrix. E.g. ( int, float ) + :param idx: Internal. Used to pass actual index in the matrix for possible error messages. + :return: + ''' + try: + + if len(shape) == 0: + if not isinstance(mat, values): + raise ParamError("Element at index {} of type {}, expected instance of {}.".format(idx, type(mat), values)) + else: + + if shape[0] is None: + shape[0] = len(mat) + l=None + if not hasattr(mat, '__len__'): + l=0 + elif len(mat) != shape[0]: + l=len(mat) + if not l is None: + raise ParamError("Wrong len {} of element {}, should be {}.".format(l, idx, shape[0])) + for i, item in enumerate(mat): + sub_shape = shape[1:] + check_matrix(item, sub_shape, values, idx = [i] + idx) + shape[1:] = sub_shape + return shape + except ParamError: + raise + except Exception as e: + raise ParamError(e) + + +class Location: + """ + Location defines an affine transformation in 3D space. Corresponds to the in the BREP file. + BREP format allows to use different transformations for individual shapes. + Location are numberd from 1. Zero index means identity location. + """ + def __init__(self, matrix=None): + """ + Constructor for elementary afine transformation. + :param matrix: Transformation matrix 3x4. First three columns forms the linear transformation matrix. + Last column is the translation vector. + matrix==None means identity location (ID=0). + """ + if matrix is None: + self.matrix = None + self.id = 0 + return + + # checks + check_matrix(matrix, [3, 4], (int, float)) + self.matrix=matrix + + def _dfs(self, groups): + """ + Deep first search that assign numbers to shapes + :param groups: dict(locations=[], curves_2d=[], curves_3d=[], surfaces=[], shapes=[]) + :return: None + """ + if not hasattr(self, 'id'): + id=len(groups['locations'])+1 + self.id = id + groups['locations'].append(self) + + def _brep_output(self, stream, groups): + stream.write("1\n") + for row in self.matrix: + for number in row: + stream.write(" {}".format(number)) + stream.write("\n") + +class ComposedLocation(Location): + """ + Defines an affine transformation as a composition of othr transformations. Corresponds to the in the BREP file. + BREP format allows to use different transformations for individual shapes. + """ + def __init__(self, location_powers=[]): + """ + + :param location_powers: List of pairs (location, power) where location is instance of Location and power is float. + """ + locs, pows = zip(*location_powers) + l = len(locs) + check_matrix(locs, [ l ], (Location, ComposedLocation) ) + check_matrix(pows, [ l ], int) + + self.location_powers=location_powers + + + def _dfs(self, groups): + + for location,power in self.location_powers: + location._dfs(groups) + Location._dfs(self, groups) + + def _brep_output(self, stream, groups): + stream.write("2 ") + for loc, pow in self.location_powers: + stream.write("{} {} ".format(loc.id, pow)) + stream.write("0\n") + +def check_knots(deg, knots, N): + total_multiplicity = 0 + for knot, mult in knots: + # This condition must hold if we assume only (0,1) interval of curve or surface parameters. + #assert float(knot) >= 0.0 and float(knot) <= 1.0 + total_multiplicity += mult + assert total_multiplicity == deg + N + 1 + + +# TODO: perform explicit conversion to np.float64 in order to avoid problems on different arch +# should be unified in bspline as well, convert to np.arrays as soon as posible +scalar_types = (int, float, np.int64, np.float64) + + +def curve_from_bs( curve): + """ + Make BREP writer Curve (2d or 3d) from bspline curve. + :param curve: bs.Curve object + :return: + """ + dim = curve.dim + if dim == 2: + curve_dim = Curve2D + elif dim == 3: + curve_dim = Curve3D + else: + assert False + return curve_dim(curve.poles, curve.basis.pack_knots(), curve.rational, curve.basis.degree) + +class Curve3D: + """ + Defines a 3D curve as B-spline. We shall work only with B-splines of degree 2. + Corresponds to "B-spline Curve - <3D curve record 7>" from BREP format description. + """ + def __init__(self, poles, knots, rational=False, degree=2): + """ + Construct a B-spline in 3d space. + :param poles: List of poles (control points) ( X, Y, Z ) or weighted points (X,Y,Z, w). X,Y,Z,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: List of tuples (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. Total number of knots, i.e. sum of their multiplicities, must be + degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. + :param degree: Positive int. + """ + + if rational: + check_matrix(poles, [None, 4], scalar_types ) + else: + check_matrix(poles, [None, 3], scalar_types) + N = len(poles) + check_knots(degree, knots, N) + + self.poles=poles + self.knots=knots + self.rational=rational + self.degree=degree + + def _dfs(self, groups): + if not hasattr(self, 'id'): + id = len(groups['curves_3d']) + 1 + self.id = id + groups['curves_3d'].append(self) + + + def _brep_output(self, stream, groups): + # writes b-spline curve + stream.write("7 {} 0 {} {} {} ".format(int(self.rational), self.degree, len(self.poles), len(self.knots))) + for pole in self.poles: + for value in pole: + stream.write(" {}".format(value)) + stream.write(" ") + for knot in self.knots: + for value in knot: + stream.write(" {}".format(value)) + stream.write(" ") + stream.write("\n") + +class Curve2D: + """ + Defines a 2D curve as B-spline. We shall work only with B-splines of degree 2. + Corresponds to "B-spline Curve - <2D curve record 7>" from BREP format description. + """ + def __init__(self, poles, knots, rational=False, degree=2): + """ + Construct a B-spline in 2d space. + :param poles: List of points ( X, Y ) or weighted points (X,Y, w). X,Y,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: List of tuples (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. Total number of knots, i.e. sum of their multiplicities, must be + degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. + :param degree: Positive int. + """ + + N = len(poles) + if rational: + check_matrix(poles, [N, 3], scalar_types ) + else: + check_matrix(poles, [N, 2], scalar_types) + check_knots(degree, knots, N) + + self.poles=poles + self.knots=knots + self.rational=rational + self.degree=degree + + def _dfs(self, groups): + if not hasattr(self, 'id'): + id = len(groups['curves_2d']) + 1 + self.id = id + groups['curves_2d'].append(self) + + def _brep_output(self, stream, groups): + # writes b-spline curve + stream.write("7 {} 0 {} {} {} ".format(int(self.rational), self.degree, len(self.poles), len(self.knots))) + for pole in self.poles: + for value in pole: + stream.write(" {}".format(value)) + stream.write(" ") + for knot in self.knots: + for value in knot: + stream.write(" {}".format(value)) + stream.write(" ") + stream.write("\n") + + + +def surface_from_bs(surf): + """ + Make BREP writer Surface from bspline surface. + :param surf: bs.Surface object + :return: + """ + return Surface(surf.poles, (surf.u_basis.pack_knots(), surf.v_basis.pack_knots()), + surf.rational, (surf.u_basis.degree, surf.v_basis.degree) ) + + +class Surface: + """ + Defines a B-spline surface in 3d space. We shall work only with B-splines of degree 2. + Corresponds to "B-spline Surface - < surface record 9 >" from BREP format description. + """ + def __init__(self, poles, knots, rational=False, degree=(2,2)): + """ + Construct a B-spline in 3d space. + :param poles: Matrix (list of lists) of Nu times Nv poles (control points). + Single pole is a points ( X, Y, Z ) or weighted point (X,Y,Z, w). X,Y,Z,w are floats. + Weighted points are used only for rational B-splines (i.e. nurbs) + :param knots: Tuple (u_knots, v_knots). Both u_knots and v_knots are lists of tuples + (knot, multiplicity), where knot is float, t-parameter on the curve of the knot + and multiplicity is positive int. For both U and V knot vector the total number of knots, + i.e. sum of their multiplicities, must be degree + N + 1, where N is number of poles. + :param rational: True for rational B-spline, i.e. NURB. Use weighted poles. BREP format have two independent flags + for U and V parametr, but only choices 0,0 and 1,1 have sense. + :param degree: (u_degree, v_degree) Both positive ints. + """ + + if rational: + check_matrix(poles, [None, None, 4], scalar_types ) + else: + check_matrix(poles, [None, None, 3], scalar_types) + + assert len(poles) > 0 + assert len(poles[0]) > 0 + self.Nu = len(poles) + self.Nv = len(poles[0]) + for row in poles: + assert len(row) == self.Nv + + assert (not rational and len(poles[0][0]) == 3) or (rational and len(poles[0][0]) == 4) + + (u_knots, v_knots) = knots + check_knots(degree[0], u_knots, self.Nu) + check_knots(degree[1], v_knots, self.Nv) + + self.poles=poles + self.knots=knots + self.rational=rational + self.degree=degree + + def _dfs(self, groups): + if not hasattr(self, 'id'): + id = len(groups['surfaces']) + 1 + self.id = id + groups['surfaces'].append(self) + + def _brep_output(self, stream, groups): + #writes b-spline surface + stream.write("9 {} {} 0 0 ".format(int(self.rational),int(self.rational))) #prints B-spline surface u or v rational flag - both same + for i in self.degree: #prints <_> + stream.write(" {}".format(i)) + (u_knots, v_knots) = self.knots + stream.write(" {} {} {} {} ".format(self.Nu, self.Nv, len(u_knots), len(v_knots))) + #prints <_> <_> <_> +# stream.write(" {}".format(self.poles)) #TODO: tohle smaz, koukam na format poles a chci: B-spline surface weight poles + for pole in self.poles: #TODO: check, takovy pokus o poles + for vector in pole: + for value in vector: + stream.write(" {}".format(value)) + stream.write(" ") + stream.write(" ") + for knot in u_knots: #prints B-spline surface u multiplicity knots + for value in knot: + stream.write(" {}".format(value)) + stream.write(" ") + for knot in v_knots: #prints B-spline surface v multiplicity knots + for value in knot: + stream.write(" {}".format(value)) + stream.write(" ") + stream.write("\n") + +class Approx: + """ + Approximation methods for B/splines of degree 2. + + """ + @classmethod + def plane(cls, vtxs): + """ + Returns B-spline surface of a plane given by 3 points. + We retun also list of UV coordinates of the given points. + :param vtxs: List of tuples (X,Y,Z) + :return: ( Surface, vtxs_uv ) + """ + assert len(vtxs) == 3, "n vtx: {}".format(len(vtxs)) + vtxs.append( (0,0,0) ) + vtxs = np.array(vtxs) + vv = vtxs[1] + vtxs[2] - vtxs[0] + vtx4 = [ vtxs[0], vtxs[1], vv, vtxs[2]] + (surf, vtxs_uv) = cls.bilinear(vtx4) + return (surf, [ vtxs_uv[0], vtxs_uv[1], vtxs_uv[3] ]) + + @classmethod + def bilinear(cls, vtxs): + """ + Returns B-spline surface of a bilinear surface given by 4 corner points. + We retun also list of UV coordinates of the given points. + :param vtxs: List of tuples (X,Y,Z) + :return: ( Surface, vtxs_uv ) + """ + assert len(vtxs) == 4, "n vtx: {}".format(len(vtxs)) + vtxs = np.array(vtxs) + def mid(*idx): + return np.mean( vtxs[list(idx)], axis=0) + + # v - direction v0 -> v2 + # u - direction v0 -> v1 + poles = [ [vtxs[0], mid(0, 3), vtxs[3]], + [mid(0,1), mid(0,1,2,3), mid(2,3)], + [vtxs[1], mid(1,2), vtxs[2]] + ] + knots = [(0.0, 3), (1.0, 3)] + surface = Surface(poles, (knots, knots)) + vtxs_uv = [ (0, 0), (1, 0), (1, 1), (0, 1) ] + return (surface, vtxs_uv) + + + + + @classmethod + def _line(cls, vtxs, overhang=0.0): + ''' + :param vtxs: List of tuples (X,Y) or (X,Y,Z) + :return: + ''' + assert len(vtxs) == 2 + vtxs = np.array(vtxs) + mid = np.mean(vtxs, axis=0) + poles = [ vtxs[0], mid, vtxs[1] ] + knots = [(0.0+overhang, 3), (1.0-overhang, 3)] + return (poles, knots) + + @classmethod + def line_2d(cls, vtxs): + """ + Return B-spline approximation of line from two 2d points + :param vtxs: [ (X0, Y0), (X1, Y1) ] + :return: Curve2D + """ + return Curve2D( *cls._line(vtxs) ) + + @classmethod + def line_3d(cls, vtxs): + """ + Return B-spline approximation of line from two 3d points + :param vtxs: [ (X0, Y0, Z0), (X1, Y1, Z0) ] + :return: Curve2D + """ + return Curve3D(*cls._line(vtxs)) + + +class Orient(enum.IntEnum): + Forward=1 + Reversed=2 + Internal=3 + External=4 + +#op=Orient.Forward +#om=Orient.Reversed +#oi=Orient.Internal +#oe=Orient.External + +class ShapeRef: + """ + Auxiliary data class to store an object with its orientation + and possibly location. Meaning of location in this context is not clear yet. + Identity location (0) in all BREPs produced by OCC. + All methods accept the tuple (shape, orient, location) and + construct the ShapeRef object automatically. + """ + + orient_chars = ['+', '-', 'i', 'e'] + + def __init__(self, shape, orient=Orient.Forward, location=Location()): + """ + :param shape: referenced shape + :param orient: orientation of the shape, value is enum Orient + :param location: A Location object. Default is None = identity location. + """ + if not issubclass(type(shape), Shape): + raise ParamError("Expected Shape, get: {}.".format(shape)) + assert isinstance(orient, Orient) + assert issubclass(type(location), Location) + + self.shape=shape + self.orientation=orient + self.location=location + + def _writeformat(self, stream, groups): + + stream.write("{}{} {} ".format(self.orient_chars[self.orientation-1], self.shape.id, self.location.id)) + + def __repr__(self): + return "{}{} ".format(self.orient_chars[self.orientation-1], self.shape.id) + + +class ShapeFlag(dict): + """ + Auxiliary data class representing the shape flag word of BREP shapes. + All methods set the flags automatically, but it can be overwritten. + + Free - Seems to indicate a top level shapes. + Modified - ?? + Checked - for format version 2 may indicate that shape topology is already checked + Orientable - ?? + Closed - used to indicate closed Wires and Shells + Infinite - ?? may indicate shapes extending to infinite, not our case + Convex - ?? may indicate convexity of the shape, not clear how this is combined with geometry + """ + flag_names = ['free', 'modified', 'checked', 'orientable', 'closed', 'infinite', 'convex'] + + def __init__(self, *args): + for k, f in zip(self.flag_names, args): + assert f in [0, 1] + self[k]=f + + def set(self, key, value=1): + if value: + value =1 + else: + value =0 + self[key] = value + + def unset(self, key): + self[key] = 0 + + def _brep_output(self, stream): + for k in self.flag_names: + stream.write(str(self[k])) + +class Shape: + def __init__(self, childs): + """ + Construct base Shape object. + Examples: + Wire([ edge_1, edge_2.m(), edge_3]) # recommended + Wire(edge_1, ShapeRef(edge_2, Orient.Reversed, some_location), edge_3) + ... not recommended since it is bad idea to reference same shape with different Locations. + + :param childs: List of ShapeRefs or child objects. + """ + + # self.subtypes - List of allowed types of childs. + assert hasattr(self, 'sub_types'), self + + # convert list of shape reference tuples to ShapeRef objects + # automaticaly wrap naked shapes into tuple. + self.childs=[] + for child in childs: + self.append(child) # append convert to ShapeRef + + # Thes flags are usualy produced by OCC for all other shapes safe vertices. + self.flags=ShapeFlag(0,1,0,1,0,0,0) + + # self.shpname: Shape name, defined in childs + assert hasattr(self, 'shpname'), self + + + """ + Methods to simplify ceration of oriented references. + """ + def p(self): + return ShapeRef(self, Orient.Forward) + + def m(self): + return ShapeRef(self, Orient.Reversed) + + def i(self): + return ShapeRef(self, Orient.Internal) + + def e(self): + return ShapeRef(self, Orient.External) + + def subshapes(self): + # Return list of subshapes stored in child ShapeRefs. + return [chld.shape for chld in self.childs] + + def append(self, shape_ref): + """ + Append a reference to shild + :param shape_ref: Either ShapeRef or child shape. + :return: None + """ + if type(shape_ref) != ShapeRef: + shape_ref=ShapeRef(shape_ref) + if not isinstance(shape_ref.shape, tuple(self.sub_types)): + raise ParamError("Wrong child type: {}, allowed: {}".format(type(shape_ref.shape), self.sub_types)) + self.childs.append(shape_ref) + + #def _convert_to_shaperefs(self, childs): + + + def set_flags(self, flags): + """ + Set flags given as tuple. + :param flags: Tuple of 7 flags. + :return: + """ + self.flags = ShapeFlag(*flags) + + + def is_closed(self): + return self.flags['closed'] + + + def _dfs(self, groups): + """ + Deep first search that assign numbers to shapes + :param groups: dict(locations=[], curves_2d=[], curves_3d=[], surfaces=[], shapes=[]) + :return: None + """ + if hasattr(self, 'id'): + return + for sub_ref in self.childs: + sub_ref.location._dfs(groups) + sub_ref.shape._dfs(groups) + groups['shapes'].append(self) + self.id = len(groups['shapes']) + + def _brep_output(self, stream, groups): + stream.write("{}\n".format(self.shpname)) + self._subrecordoutput(stream) + self.flags._brep_output(stream) + stream.write("\n") +# stream.write("{}".format(self.childs)) + for child in self.childs: + child._writeformat(stream, groups) + stream.write("*\n") + #subshape, tj. childs + + def _subrecordoutput(self, stream): + stream.write("\n") + + def __repr__(self): + if not hasattr(self, 'id'): + self.index_all() + if len(self.childs)==0: + return "" + repr = "" + repr+=self.shpname + " " + str(self.id) + " : " + for child in self.childs: + repr+=child.__repr__() + repr+="\n" + for child in self.childs: + repr+=child.shape.__repr__() + repr+="\n" + return repr + + + def index_all(self, location=Location()): + # print("Index") + # print(compound.__class__.__name__) #prints class name + + groups = dict(locations=[], curves_2d=[], curves_3d=[], surfaces=[], shapes=[]) + self._dfs(groups) # pridej jako parametr dictionary listu jednotlivych grup. v listech primo objekty + location._dfs(groups) + # print(groups) + return groups + + +""" +Shapes with no special parameters, only flags and subshapes. +Writer can be generic implemented in bas class Shape. +""" + +class Compound(Shape): + def __init__(self, shapes=[]): + self.sub_types = [CompoundSolid, Solid, Shell, Wire, Face, Edge, Vertex] + self.shpname = 'Co' + super().__init__(shapes) + #flags: free, modified, IGNORED, orientable, closed, infinite, convex + self.set_flags( (1, 1, 0, 0, 0, 0, 0) ) # free, modified + + def set_free_shapes(self): + """ + Set 'free' attributes to all shapes of the compound. + :return: + """ + for shape in self.subshapes(): + shape.flags.set('free', True) + + +class CompoundSolid(Shape): + def __init__(self, solids=[]): + self.sub_types = [Solid] + self.shpname = 'Cs' + super().__init__(solids) + + +class Solid(Shape): + def __init__(self, shells=[]): + self.sub_types = [Shell] + self.shpname='So' + super().__init__(shells) + self.set_flags((0, 1, 0, 0, 0, 0, 0)) # modified + +class Shell(Shape): + def __init__(self, faces=[]): + self.sub_types = [Face] + self.shpname='Sh' + super().__init__(faces) + self.set_flags((0, 1, 0, 1, 0, 0, 0)) # modified, orientable + + +class Wire(Shape): + def __init__(self, edges=[]): + self.sub_types = [Edge] + self.shpname='Wi' + super().__init__(edges) + self.set_flags((0, 1, 0, 1, 0, 0, 0)) # modified, orientable + self._set_closed() + + def _set_closed(self): + ''' + Return true for the even parity of vertices. + :return: REtrun true if wire is closed. + ''' + vtx_set = {} + for edge in self.subshapes(): + for vtx in edge.subshapes(): + vtx_set[vtx] = 0 + vtx.n_edges += 1 + closed = True + for vtx in vtx_set.keys(): + if vtx.n_edges % 2 != 0: + closed = False + vtx.n_edges = 0 + self.flags.set('closed', closed) + + +""" +Shapes with special parameters. +Specific writers are necessary. +""" + +class Face(Shape): + """ + Face class. + Like vertex and edge have some additional parameters in the BREP format. + """ + + def __init__(self, wires, surface=None, location=Location(), tolerance=1.0e-3): + """ + :param wires: List of wires, or list of edges, or list of ShapeRef tuples of Edges to construct a Wire. + :param surface: Representation of the face, surface on which face lies. + :param location: Location of the surface. + :param tolerance: Tolerance of the representation. + """ + self.sub_types = [Wire, Edge] + self.tol=tolerance + self.restriction_flag =0 + self.shpname = 'Fa' + + if type(wires) != list: + wires = [ wires ] + assert(len(wires) > 0) + super().__init__(wires) + + # auto convert list of edges into wire + shape_type = type(self.childs[0].shape) + for shape in self.subshapes(): + assert type(shape) == shape_type + if shape_type == Edge: + wire = Wire(self.childs) + self.childs = [] + self.append(wire) + + # check that wires are closed + for wire in self.subshapes(): + if not wire.is_closed(): + raise Exception("Trying to make face from non-closed wire.") + + if surface is None: + self.repr=[] + else: + assert type(surface) == Surface + self.repr=[(surface, location)] + + + + def _dfs(self, groups): + Shape._dfs(self,groups) + if not self.repr: + self.implicit_surface() + assert len(self.repr) == 1 + for repr, loc in self.repr: + repr._dfs(groups) + loc._dfs(groups) + + # update geometry representation of edges (add 2D curves) + for wire in self.subshapes(): + for edge in wire.subshapes(): + edge._dfs(groups) + + + def implicit_surface(self): + """ + Construct a surface if surface is None. Works only for + 3 and 4 vertices (plane or bilinear surface) + Should be called in _dfs just after all child shapes are passed. + :return: None + """ + edges = {} + vtxs = [] + for wire in self.subshapes(): + for edge in wire.childs: + edges[edge.shape.id] = edge.shape + e_vtxs = edge.shape.subshapes() + if edge.orientation == Orient.Reversed: + e_vtxs.reverse() + for vtx in e_vtxs: + vtxs.append( (vtx.id, vtx.point) ) + vtxs = vtxs[1:] + vtxs[:1] + odd_vtx = vtxs[1::2] + even_vtx = vtxs[0::2] + assert odd_vtx == even_vtx, "odd: {} even: {}".format(odd_vtx, even_vtx) + vtxs = odd_vtx + if len(vtxs) == 3: + constructor = Approx.plane + elif len(vtxs) == 4: + constructor = Approx.bilinear + else: + raise Exception("Too many vertices {} for implicit surface construction.".format(len(vtxs))) + (ids, points) = zip(*vtxs) + (surface, vtxs_uv) = constructor(list(points)) + self.repr = [(surface, Location())] + + # set representation of edges + assert len(ids) == len(vtxs_uv) + id_to_uv = dict(zip(ids, vtxs_uv)) + for edge in edges.values(): + e_vtxs = edge.subshapes() + v0_uv = id_to_uv[e_vtxs[0].id] + v1_uv = id_to_uv[e_vtxs[1].id] + edge.attach_to_plane( surface, v0_uv, v1_uv ) + + # TODO: Possibly more general attachment of edges to 2D curves for general surfaces, but it depends + # on organisation of intersection curves. + + def _subrecordoutput(self, stream): + assert len(self.repr) == 1 + surf,loc = self.repr[0] + stream.write("{} {} {} {}\n\n".format(self.restriction_flag,self.tol,surf.id,loc.id)) + + +class Edge(Shape): + """ + Edge class. Special edge flags have unclear meaning. + Allow setting representations of the edge, this is crucial for good mash generation. + """ + + class Repr(enum.IntEnum): + Curve3d = 1 + Curve2d = 2 + #Continuous2d=3 + + + def __init__(self, vertices, tolerance=1.0e-3): + """ + :param vertices: List of shape reference tuples, see ShapeRef class. + :param tolerance: Tolerance of the representation. + """ + self.sub_types = [Vertex] + self.shpname = 'Ed' + self.tol = tolerance + self.repr = [] + self.edge_flags=(1,1,0) # this is usual value + + assert(len(vertices) == 2) + + super().__init__(vertices) + # Overwrite vertex orientation + self.childs[0].orientation = Orient.Forward + self.childs[1].orientation = Orient.Reversed + + def set_edge_flags(self, same_parameter, same_range, degenerated): + """ + Edge flags with unclear meaning. + :param same_parameter: + :param same_range: + :param degenerated: + :return: + """ + self.edge_flags=(same_parameter,same_range, degenerated) + + def points(self): + ''' + :return: List of coordinates of the edge vertices. + ''' + return [ vtx.point for vtx in self.subshapes()] + + def attach_to_3d_curve(self, t_range, curve, location=Location()): + """ + Add vertex representation on a 3D curve. + :param t_range: Tuple (t_min, t_max). + :param curve: 3D curve object (Curve3d) + :param location: Location object. Default is None = identity location. + :return: None + """ + assert type(curve) == Curve3D + self.repr.append( (self.Repr.Curve3d, t_range, curve, location) ) + + def attach_to_2d_curve(self, t_range, curve, surface, location=Location()): + """ + Add vertex representation on a 2D curve. + :param t_range: Tuple (t_min, t_max). + :param curve: 2D curve object (Curve2d) + :param surface: Surface on which the curve lies. + :param location: Location object. Default is None = identity location. + :return: None + """ + assert type(surface) == Surface + assert type(curve) == Curve2D + self.repr.append( (self.Repr.Curve2d, t_range, curve, surface, location) ) + + def attach_to_plane(self, surface, v0, v1): + """ + Construct and attach 2D line in UV space of the 'surface' + :param surface: A Surface object. + :param v0: UV coordinate of the first edge point + :param v1: UV coordinate of the second edge point + :return: + """ + assert type(surface) == Surface + self.attach_to_2d_curve((0.0, 1.0), Approx.line_2d([v0, v1]), surface) + + def implicit_curve(self): + """ + Construct a line 3d curve if there is no 3D representation. + Should be called in _dfs. + :return: + """ + vtx_points = self.points() + self.attach_to_3d_curve((0.0,1.0), Approx.line_3d( vtx_points )) + + #def attach_continuity(self): + + def _dfs(self, groups): + Shape._dfs(self,groups) + if not self.repr: + self.implicit_curve() + assert len(self.repr) > 0 + for i,repr in enumerate(self.repr): + if repr[0]==self.Repr.Curve2d: + repr[2]._dfs(groups) #curve + repr[3]._dfs(groups) #surface + repr[4]._dfs(groups) #location + elif repr[0]==self.Repr.Curve3d: + repr[2]._dfs(groups) #curve + repr[3]._dfs(groups) #location + + def _subrecordoutput(self, stream): #prints edge data #TODO: tisknu nekolik data representation + assert len(self.repr) > 0 + stream.write(" {} {} {} {}\n".format(self.tol,self.edge_flags[0],self.edge_flags[1],self.edge_flags[2])) + for i,repr in enumerate(self.repr): + if repr[0] == self.Repr.Curve2d: + curve_type, t_range, curve, surface, location = repr + stream.write("2 {} {} {} {} {}\n".format(curve.id, surface.id, location.id,t_range[0],t_range[1] )) #TODO: 2 <_> <_> + elif repr[0] == self.Repr.Curve3d: + curve_type, t_range, curve, location = repr + stream.write("1 {} {} {} {}\n".format(curve.id, location.id, t_range[0], t_range[1])) #TODO: 3 + stream.write("0\n") + +class Vertex(Shape): + """ + Vertex class. + Allow setting representations of the vertex but seems it is not used in BREPs produced by OCC. + """ + + class Repr(enum.IntEnum): + Curve3d = 1 + Curve2d = 2 + Surface = 3 + + def __init__(self, point, tolerance=1.0e-3): + """ + :param point: 3d point (X,Y,Z) + :param tolerance: Tolerance of the representation. + """ + check_matrix(point, [3], scalar_types) + + # These flags are produced by OCC for vertices. + self.flags = ShapeFlag(0, 1, 0, 1, 1, 0, 1) + # Coordinates in the 3D space. [X, Y, Z] + self.point=np.array(point) + # tolerance of representations. + self.tolerance=tolerance + # List of geometrical representations of the vertex. Possibly not necessary for meshing. + self.repr=[] + # Number of edges in which vertex is used. Used internally to check closed wires. + self.n_edges = 0 + self.shpname = 'Ve' + self.sub_types=[] + + super().__init__(childs=[]) + + def attach_to_3d_curve(self, t, curve, location=Location()): + """ + Add vertex representation on a 3D curve. + :param t: Parameter of the point on the curve. + :param curve: 3D curve object (Curve3d) + :param location: Location object. Default is None = identity location. + :return: None + """ + self.repr.append( (self.Repr.Curve3d, t, curve, location) ) + + def attach_to_2d_curve(self, t, curve, surface, location=Location()): + """ + Add vertex representation on a 2D curve on a surface. + :param t: Parameter of the point on the curve. + :param curve: 2D curve object (Curve2d) + :param surface: Surface on which the curve lies. + :param location: Location object. Default is None = identity location. + :return: None + """ + self.repr.append( (self.Repr.Curve2d, t, curve, surface, location) ) + + def attach_to_surface(self, u, v, surface, location=Location()): + """ + Add vertex representation on a 3D curve. + :param u,v: Parameters u,v of the point on the surface. + :param surface: Surface object. + :param location: Location object. Default is None = identity location. + :return: None + """ + self.repr.append( (self.Repr.Surface, u,v, surface, location) ) + + def _dfs(self, groups): + Shape._dfs(self,groups) + for repr in self.repr: + if repr[0]==self.Repr.Curve2d: + repr[2]._dfs(groups) #curve + repr[3]._dfs(groups) #surface + repr[4]._dfs(groups) #location + elif repr[0]==self.Repr.Curve3d: + repr[2]._dfs(groups) #curve + repr[3]._dfs(groups) #location + + + def _subrecordoutput(self, stream): #prints vertex data + stream.write("{}\n".format(self.tolerance)) + for i in self.point: + stream.write("{} ".format(i)) + stream.write("\n0 0\n\n") #no added + + + + +def write_model(stream, compound, location): + + groups = compound.index_all(location=location) + + # fix shape IDs + n_shapes = len(groups['shapes']) + 1 + for shape in groups['shapes']: + shape.id = n_shapes - shape.id + + stream.write("DBRep_DrawableShape\n\n") + stream.write("CASCADE Topology V1, (c) Matra-Datavision\n") + stream.write("Locations {}\n".format(len(groups['locations']))) + for loc in groups['locations']: + loc._brep_output(stream, groups) + + stream.write("Curve2ds {}\n".format(len(groups['curves_2d']))) + for curve in groups['curves_2d']: + curve._brep_output(stream, groups) + + stream.write("Curves {}\n".format(len(groups['curves_3d']))) + for curve in groups['curves_3d']: + curve._brep_output(stream, groups) + + stream.write("Polygon3D 0\n") + + stream.write("PolygonOnTriangulations 0\n") + + stream.write("Surfaces {}\n".format(len(groups['surfaces']))) + for surface in groups['surfaces']: + surface._brep_output(stream, groups) + + stream.write("Triangulations 0\n") + + stream.write("\nTShapes {}\n".format(len(groups['shapes']))) + for shape in groups['shapes']: + #print("# {} id: {} childs: {}\n".format(shape.shpname, shape.id, + # [ch.id for ch in shape.subshapes()])) + shape._brep_output(stream, groups) + stream.write("\n+1 0") + #stream.write("0\n") + + + From 25ef5838a4c243716424ff3c367d19dd2eafb6bf Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 21 Sep 2017 17:02:56 +0200 Subject: [PATCH 31/67] Fix grid surf load, add surface.aabb, test for brep writer --- src/brep_writer.py | 3 +- src/bspline.py | 76 +++--------------- src/bspline_approx.py | 2 + tests/test_brep_writer.py | 157 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 68 deletions(-) create mode 100644 tests/test_brep_writer.py diff --git a/src/brep_writer.py b/src/brep_writer.py index 1edadaa..3888434 100644 --- a/src/brep_writer.py +++ b/src/brep_writer.py @@ -1,7 +1,6 @@ import enum -import itertools import numpy as np -import bspline as bs + ''' diff --git a/src/bspline.py b/src/bspline.py index ceb5259..c562083 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -20,69 +20,6 @@ __author__ = 'Jan Brezina , Jiri Hnidek , Jiri Kopal ' - -# class ParamError(Exception): -# pass -# -# def check_matrix(mat, shape, values, idx=[]): -# ''' -# Check shape and type of scalar, vector or matrix. -# :param mat: Scalar, vector, or vector of vectors (i.e. matrix). Vector may be list or other iterable. -# :param shape: List of dimensions: [] for scalar, [ n ] for vector, [n_rows, n_cols] for matrix. -# If a value in this list is None, the dimension can be arbitrary. The shape list is set fo actual dimensions -# of the matrix. -# :param values: Type or tuple of allowed types of elements of the matrix. E.g. ( int, float ) -# :param idx: Internal. Used to pass actual index in the matrix for possible error messages. -# :return: -# ''' -# try: -# if len(shape) == 0: -# if not isinstance(mat, values): -# raise ParamError("Element at index {} of type {}, expected instance of {}.".format(idx, type(mat), values)) -# else: -# if shape[0] is None: -# shape[0] = len(mat) -# l=None -# if not hasattr(mat, '__len__'): -# l=0 -# elif len(mat) != shape[0]: -# l=len(mat) -# if not l is None: -# raise ParamError("Wrong len {} of element {}, should be {}.".format(l, idx, shape[0])) -# for i, item in enumerate(mat): -# sub_shape = shape[1:] -# check_matrix(item, sub_shape, values, idx = [i] + idx) -# shape[1:] = sub_shape -# return shape -# except ParamError: -# raise -# except Exception as e: -# raise ParamError(e) -# -# -# def check_knots(deg, knots, N): -# total_multiplicity = 0 -# for knot, mult in knots: -# # This condition must hold if we assume only (0,1) interval of curve or surface parameters. -# #assert float(knot) >= 0.0 and float(knot) <= 1.0 -# total_multiplicity += mult -# assert total_multiplicity == deg + N + 1 -# -# -# scalar_types = (int, float, np.int64) - - - - - - - - - - - - - class SplineBasis: """ @@ -496,7 +433,7 @@ def make_raw(cls, poles, knots, rational=False, degree=(2,2)): :param degree: Non-negative int """ u_basis = SplineBasis(degree[0], knots[0]) - v_basis = SplineBasis(degree[0], knots[0]) + v_basis = SplineBasis(degree[1], knots[1]) return cls( (u_basis, v_basis), poles, rational) @@ -567,6 +504,13 @@ def eval_array(self, uv_points): assert uv_points.shape[1] == 2 return np.array( [ self.eval(u, v) for u, v in uv_points] ) + def aabb(self): + """ + Return Axes Aligned Bounding Box of the poles, which should be also bounding box of the curve itself. + :return: ( min_corner, max_corner); Box corners are numpy arryas of dimension D. + TODO: test + """ + return (np.amin(self.poles, axis=(0,1)), np.amax(self.poles, axis=(0,1))) class Z_Surface: @@ -777,7 +721,7 @@ class GridSurface: @staticmethod - def load(self, filename): + def load(filename): """ Load the grid surface from file :param filename: @@ -785,7 +729,7 @@ def load(self, filename): """ point_seq = np.loadtxt(filename) assert min(point_seq.shape) > 1 - GridSurface(point_seq.T) + return GridSurface(point_seq) """ Can load and check grid of XYZ points and construct a diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 7b60048..0cf6f5f 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -20,6 +20,8 @@ def plane_surface(vtxs, overhang=0.0): """ Returns B-spline surface of a plane given by 3 points. We retun also list of UV coordinates of the given points. + U direction v0 -> v1 + V direction v0 -> v2 :param vtxs: List of tuples (X,Y,Z) :return: ( Surface, vtxs_uv ) """ diff --git a/tests/test_brep_writer.py b/tests/test_brep_writer.py new file mode 100644 index 0000000..598435f --- /dev/null +++ b/tests/test_brep_writer.py @@ -0,0 +1,157 @@ +import pytest +import unittest +import brep_writer as bw +import sys + + + +class TestConstructors(unittest.TestCase): + def test_Location(self): + print( "test locations") + la = bw.Location([[1, 0, 0, 4], [0, 1, 0, 8], [0, 0, 1, 12]]) + lb = bw.Location([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) + lc = bw.ComposedLocation([ (la, 2), (lb, 1) ]) + ld = bw.ComposedLocation([ (lb, 2), (lc, 3) ]) + with self.assertRaises(bw.ParamError): + bw.Location([1,2,3]) + with self.assertRaises(bw.ParamError): + bw.Location([[1], [2], [3]]) + with self.assertRaises(bw.ParamError): + a = 1 + b = 'a' + lb = bw.Location([[a, b, a, b], [a, b, a, b], [a, b, a, b]]) + + def test_Shape(self): + + # check sub types + with self.assertRaises(bw.ParamError): + bw.Wire(['a', 'b']) + v=bw.Vertex([1,2,3]) + with self.assertRaises(bw.ParamError): + bw.Wire([v, v]) + + + def test_Vertex(self): + with self.assertRaises(bw.ParamError): + bw.Vertex(['a','b','c']) + + +class TestPlanarGeomeries(unittest.TestCase): + + def _cube(self): + # 0, 0; top, bottom + v1=bw.Vertex((0.0, 0.0, 1.0)) + v2=bw.Vertex((0.0, 0.0, 0.0)) + + v3=bw.Vertex((0.0, 1.0, 1.0)) + v4=bw.Vertex((0.0, 1.0, 0.0)) + + v5=bw.Vertex((1.0, 0.0, 1.0)) + v6=bw.Vertex((1.0, 0.0, 0.0)) + + # vertical edges + e1=bw.Edge([v1,v2]) + e2=bw.Edge([v3,v4]) + e3=bw.Edge([v5,v6]) + + # face v12 - v34 + # top + e4=bw.Edge([v1,v3]) + # bottom + e5=bw.Edge([v2,v4]) + f1 = bw.Face([e1.m(), e4, e2, e5.m()]) + + # face v34 - v56 + # top + e6=bw.Edge([v3, v5]) + # bottom + e7=bw.Edge([v4, v6]) + f2 = bw.Face([e2.m(), e6, e3, e7.m()]) + + # face v56 - v12 + # top + e8=bw.Edge([v5, v1]) + # bottom + e9=bw.Edge([v6, v2]) + f3 = bw.Face([e3.m(), e8, e1, e9.m()]) + + # top cup + f4 = bw.Face([e4, e6, e8]) + # bot cup + w5=bw.Wire([e5, e7, e9]) + f5 = bw.Face([w5.m()]) + + shell = bw.Shell([ f1, f2, f3, f4, f5.m() ]) + return shell + + def _permuted_cube(self): + # 0, 0; top, bottom + v1=bw.Vertex((0.0, 0.0, 1.0)) + v2=bw.Vertex((0.0, 0.0, 0.0)) + + v3=bw.Vertex((0.0, 1.0, 1.0)) + v4=bw.Vertex((0.0, 1.0, 0.0)) + + v5=bw.Vertex((1.0, 0.0, 1.0)) + v6=bw.Vertex((1.0, 0.0, 0.0)) + + # face v12 - v34 + # top + e4=bw.Edge([v1,v3]) + # top + e6=bw.Edge([v3, v5]) + # top + e8=bw.Edge([v5, v1]) + + # top cup + f4 = bw.Face([e4, e6, e8]) + + # bottom + e5=bw.Edge([v2,v4]) + # face v34 - v56 + # bottom + e7=bw.Edge([v4, v6]) + # face v56 - v12 + # bottom + e9=bw.Edge([v6, v2]) + + # bot cup + w5=bw.Wire([e5, e7, e9]) + f5 = bw.Face([w5.m()]) + + + # vertical edges + e1=bw.Edge([v1,v2]) + e2=bw.Edge([v3,v4]) + e3=bw.Edge([v5,v6]) + + f1 = bw.Face([e1.m(), e4, e2, e5.m()]) + + f2 = bw.Face([e2.m(), e6, e3, e7.m()]) + + f3 = bw.Face([e3.m(), e8, e1, e9.m()]) + + shell = bw.Shell([ f4, f5.m(), f1, f2, f3 ]) + return shell + + def test_cube(self): + + shell = self._cube() + #shell = self._permuted_cube() + + s1=bw.Solid([ shell ]) + + c1=bw.Compound([s1]) + + loc1=bw.Location([[0,0,1,0],[1,0,0,0],[0,1,0,0]]) + loc2=bw.Location([[0,0,1,0],[1,0,0,0],[0,1,0,0]]) #dej tam tu druhou z prikladu + cloc=bw.ComposedLocation([(loc1,1),(loc2,1)]) + + with open("_out_test_prism.brep", "w") as f: + bw.write_model(f, c1, cloc) + #bw.write_model(sys.stdout, c1, cloc) + print(c1) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From 7e2900cf3b657d6ccbc247755b281d922c2e47c3 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 21 Sep 2017 18:41:57 +0200 Subject: [PATCH 32/67] Implement aabb and grid center. --- src/bspline.py | 16 +++++++++++++-- tests/test_bspline.py | 46 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index c562083..b2377a3 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -412,7 +412,7 @@ def aabb(self): Return Axes Aligned Bounding Box of the poles, which should be also bounding box of the curve itself. :return: ( min_corner, max_corner); Box corners are numpy arryas of dimension D. """ - return (np.amin(self.poles, axis=0), np.amax(self.poles, axis=0)) + return np.array( [np.amin(self.poles, axis=0), np.amax(self.poles, axis=0)] ) class Surface: @@ -510,7 +510,7 @@ def aabb(self): :return: ( min_corner, max_corner); Box corners are numpy arryas of dimension D. TODO: test """ - return (np.amin(self.poles, axis=(0,1)), np.amax(self.poles, axis=(0,1))) + return np.array( [np.amin(self.poles, axis=(0,1)), np.amax(self.poles, axis=(0,1))] ) class Z_Surface: @@ -706,6 +706,13 @@ def z_eval_xy_array(self, xy_points): return z_points.reshape(-1) + def aabb(self): + xyz_box = np.empty( (2, 3) ) + xyz_box[0, 0:2] = np.amin(self.quad, axis=0) + xyz_box[1, 0:2] = np.amax(self.quad, axis=0) + xyz_box[:, 2] = self.z_surface.aabb()[:,0] + return xyz_box + class GridNotInShapeExc(Exception): @@ -926,8 +933,13 @@ def z_eval_array(self, uv_points): return result + def center(self): + return self.eval_array( np.array([ [0.5, 0.5] ]))[0] + def aabb(self): + return self.z_surface.aabb() + def make_function_grid(fn, nu, nv): """ diff --git a/tests/test_bspline.py b/tests/test_bspline.py index b488d73..762f8cc 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -215,7 +215,12 @@ def test_evaluate(self): pass - + def test_aabb(self): + poles = [ [0., 0.], [1.0, 0.5], [2., -2.], [3., 1.] ] + basis = bs.SplineBasis.make_equidistant(2, 2) + curve = bs.Curve(basis, poles) + box = curve.aabb() + assert np.allclose( box, np.array([ [0,-2], [3, 1]]) ) @@ -242,13 +247,13 @@ def plot_extrude(self): plt.show() def plot_function(self): + fig = plt.figure() + ax = fig.gca(projection='3d') + # function surface def function(x): return math.sin(x[0]) * math.cos(x[1]) - fig = plt.figure() - ax = fig.gca(projection='3d') - poles = bs.make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) @@ -265,6 +270,19 @@ def test_evaluate(self): pass + def test_aabb(self): + # function surface + def function(x): + return x[0] * (x[1] + 1.0) + 3.0 + + poles = bs.make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), np.array(poles)) + box = surface_func.aabb() + assert np.allclose( box, np.array([ [0,0, 3], [1, 1, 5]]) ) + + class TestZ_Surface: @@ -299,6 +317,20 @@ def test_eval_uv(self): #self.plot_function_uv() pass + def test_aabb(self): + # function surface + def function(x): + return x[0] * (x[1] + 1.0) + 3.0 + + poles = bs.make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) + + quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) + z_surf = bs.Z_Surface(quad, surface_func) + box = z_surf.aabb() + assert np.allclose(box, np.array([[0, 0, 3], [1.1, 1.1, 5]])) class TestPointGrid: @@ -389,4 +421,8 @@ def test_grid_surface(self): assert np.all(surface.quad == new_quad), "surf: {} ref: {}".format(surface.quad, new_quad) self.check_surface(surface, xy_mat, xy_shift, z_shift) - + surf_center = surface.center() + ref_center = np.array( [-2.0, math.sqrt(2.0)+5.0, (math.sin(0.5)*math.cos(0.5) + 1.3) ] ) + #print(surf_center) + #print(ref_center) + assert np.allclose( ref_center, surf_center, rtol=0.01) From 3d0d1de523cff14f0191415e0ec249901a95e3f1 Mon Sep 17 00:00:00 2001 From: jiri Date: Wed, 27 Sep 2017 13:15:44 +0200 Subject: [PATCH 33/67] construction of the matrix A was optimized --- src/bspline_approx.py | 54 ++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 0cf6f5f..777ba65 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -336,7 +336,7 @@ def _basis_in_q_points(self, basis): nq_points = len(self._q_points) point_val = np.zeros((3, n_int * nq_points)) d_point_val = np.zeros((3, n_int * nq_points)) - point_idx = np.zeros((n_int * nq_points, 1)) + point_idx = np.zeros((n_int, 1)) q_point = np.zeros((n_int * nq_points, 1)) n = 0 @@ -352,8 +352,8 @@ def _basis_in_q_points(self, basis): u_base_vec_diff = basis.eval_diff_base_vector(idx, up) point_val[:, n] = u_base_vec d_point_val[:, n] = u_base_vec_diff - point_idx[n] = idx n += 1 + point_idx[i] = idx return point_val, d_point_val, point_idx, q_point def build_sparse_reg_matrix(self): @@ -385,38 +385,39 @@ def build_sparse_reg_matrix(self): self._weights = [1.0 / 6, 5.0 / 6, 5.0 / 6, 1.0 / 6] nq_points = len(self._q_points) - # TODO: rename: u_vals, u_diffs, u_idxs, u_points - u_point_val, ud_point_val, u_point_idx, q_u_point = self._basis_in_q_points(self.u_basis) - v_point_val, vd_point_val, v_point_idx, q_v_point = self._basis_in_q_points(self.v_basis) + # DONE: rename: u_vals, u_diffs, u_idxs, u_points + u_val, ud_val, u_idx, q_u_point = self._basis_in_q_points(self.u_basis) + v_val, vd_val, v_idx, q_v_point = self._basis_in_q_points(self.v_basis) # Matrix construction - # TODO: Assembly local dense blocks 9*9 and then put these nonzeroes into sparse matirx + # DONE: Assembly local dense blocks 9*9 and then put these nonzeroes into sparse matirx # TODO: use numpy opperations to make assembly of local blocks readable, eliminate loops colv = np.zeros(n_uv_loc_nz) - # TODO:rename data and data2 to something meqningful - data = np.zeros(n_uv_loc_nz) - data2 = np.zeros(n_uv_loc_nz) - row_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) - col_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) - data_m = np.zeros((v_n_inter * u_n_inter * nq_points * nq_points * n_uv_loc_nz * n_uv_loc_nz)) + # DONE:rename data and data2 to something meqningful + v_diff_u = np.zeros(n_uv_loc_nz) + v_u_diff = np.zeros(n_uv_loc_nz) + row_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) + col_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) # * nq_points * nq_points + data_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) nnz_a = 0 for i in range(v_n_inter): - for k in range(nq_points): - v_point = v_point_val[:, i * nq_points + k] - vd_point = vd_point_val[:, i * nq_points + k] - j_idx = v_point_idx[i * nq_points + k] - for l in range(u_n_inter): + j_idx = v_idx[i] # * nq_points + k + for l in range(u_n_inter): + i_idx = u_idx[l] # * nq_points + m + for k in range(nq_points): + v_point = v_val[:, i * nq_points + k] + vd_point = vd_val[:, i * nq_points + k] for m in range(nq_points): - u_point = u_point_val[:, l * nq_points + m] - ud_point = ud_point_val[:, l * nq_points + m] - i_idx = u_point_idx[l * nq_points + m] + u_point = u_val[:, l * nq_points + m] + ud_point = ud_val[:, l * nq_points + m] + for n in range(0, 3): # Hard-coded Kronecker product: vd = numpy.kron(vd_point, u_point) - data[3 * n:3 * (n + 1)] = vd_point[n] * u_point + v_diff_u[3 * n:3 * (n + 1)] = vd_point[n] * u_point # Hard-coded Kronecker product: ud = numpy.kron(v_point, ud_point) - data2[3 * n:3 * (n + 1)] = v_point[n] * ud_point + v_u_diff[3 * n:3 * (n + 1)] = v_point[n] * ud_point # column indices for data & data2 for p in range(0, 3): colv[3 * n + p] = (j_idx + n) * u_n_basf + i_idx + p @@ -430,10 +431,11 @@ def build_sparse_reg_matrix(self): jac = 1.0 / u_n_inter / v_n_inter coef = self._weights[m] * self._weights[k] * jac for n in range(0, 9): - row_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv - col_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv[n] - data_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = coef * (data[n] * data + data2[n] * data2) - nnz_a += n_uv_loc_nz * n_uv_loc_nz + data_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = coef * (v_diff_u[n] * v_diff_u + v_u_diff[n] * v_u_diff) + for n in range(0, 9): + row_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv + col_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv[n] + nnz_a += n_uv_loc_nz * n_uv_loc_nz mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() From 18744b269943d22848e611f69013fe3994f4c5c2 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 27 Sep 2017 13:17:20 +0200 Subject: [PATCH 34/67] Remove unneeded imports. --- src/bspline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index b2377a3..3a52235 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -12,9 +12,7 @@ """ -import matplotlib.pyplot as plt import numpy as np -import math import numpy.linalg as la From 3647b7792dfb81b66fd1f3824788b1a9b58a8e4c Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 27 Sep 2017 18:38:23 +0200 Subject: [PATCH 35/67] Fix aabb for GridSurface, add missing test --- src/bspline.py | 23 +++++++++++++++-------- tests/test_bspline.py | 5 +++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 3a52235..74f4380 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -585,7 +585,7 @@ def make_full_surface(self): return Surface(basis, poles) - def transform(self, xy_mat, z_mat=np.array( [1.0, 0.0] ) ): + def transform(self, xy_mat, z_mat = np.array( [1.0, 0.0] ) ): """ Transform the Z-surface by arbitrary XY linear transform and Z linear transform. :param xy_mat: np array, 2 rows 3 cols, last column is xy shift @@ -599,6 +599,9 @@ def transform(self, xy_mat, z_mat=np.array( [1.0, 0.0] ) ): self._xy_shift = xy_mat[0:2,0:2].dot( self._xy_shift ) + xy_mat[0:2, 2] self._mat_xy_to_uv = la.inv(self._mat_uv_to_xy) + # transform quad + self.quad = np.dot(self.quad, xy_mat[0:2,0:2].T) + xy_mat[0:2, 2] + # apply z-transfrom directly to the poles self.z_surface.poles *= z_mat[0] self.z_surface.poles += z_mat[1] @@ -863,14 +866,15 @@ def transform(self, xy_mat, z_mat): Note: """ # just XY transfrom of underlaying z_surface - self.z_surface.transform(xy_mat) - - # transform quad - self.quad = self.z_surface.uv_to_xy( np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) + if xy_mat is not None: + self.z_surface.transform(xy_mat) + # transform quad + self.quad = self.z_surface.uv_to_xy( np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) # transform z_scale - self.z_scale *= z_mat[0] - self.z_shift = z_mat[0]*self.z_shift + z_mat[1] + if z_mat is not None: + self.z_scale *= z_mat[0] + self.z_shift = z_mat[0]*self.z_shift + z_mat[1] def xy_to_uv(self, xy_points): @@ -936,7 +940,10 @@ def center(self): def aabb(self): - return self.z_surface.aabb() + z_surf_box = self.z_surface.aabb() + z_surf_box[:,2] *= self.z_scale + z_surf_box[:, 2] += self.z_shift + return z_surf_box def make_function_grid(fn, nu, nv): diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 762f8cc..6feda6b 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -426,3 +426,8 @@ def test_grid_surface(self): #print(surf_center) #print(ref_center) assert np.allclose( ref_center, surf_center, rtol=0.01) + + v_min, v_max = surface.aabb() + assert np.allclose(v_min, np.array([-3.0/math.sqrt(2) -2, 0.0 + 5, 1.3])) + assert np.allclose(v_max, np.array([3.0 / math.sqrt(2) - 2, 4.0 / math.sqrt(2) + 5, math.sin(1.0) + 1.3])) + From dc055972de76c3637776e227ebac33f744ffa7fe Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 27 Sep 2017 23:17:27 +0200 Subject: [PATCH 36/67] Hints for optimization --- src/bspline_approx.py | 16 +++++++++++++++- tests/test_bs_approx.py | 5 +++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 777ba65..bac5372 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -401,7 +401,7 @@ def build_sparse_reg_matrix(self): data_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) nnz_a = 0 - + print("vnint: {} unint: {} nqp: {} prod: {}".format(v_n_inter, u_n_inter, nq_points, v_n_inter* u_n_inter* nq_points*nq_points)) for i in range(v_n_inter): j_idx = v_idx[i] # * nq_points + k for l in range(u_n_inter): @@ -413,6 +413,19 @@ def build_sparse_reg_matrix(self): u_point = u_val[:, l * nq_points + m] ud_point = ud_val[:, l * nq_points + m] + + # 9x9 dense matrix for single patch is: + # N'_i(u) * N'_k(u) * N_j(v)*N_l(v) + N_i(u) * N_k(u) * N'_j(v) * N'_l(v) + # Precomputing M'_ik(u) = N'_i(u) * N'_k(u) and M_ik(u) = N_i(u) * N_k(u) + # and similarly M'_jl(v) and M_jl(v) for u and v quad points we can compute + # whole local matrix as M_I(u_i) * M'_J(v_j) + M'_I(u_i)*M_J(v_j) = + # [ M_I, M'_I ](u_i) .dot. [ M'_J, M_J](v_j) + # sum_i,j X_I(u_i) .dot. Y_J(v_j) = + # (sum_i X_I(u_i)) .dot. (sum_j Y_J(v_j)) + # = A_I .dot. B_J + # A_I = sum_i [M_I, M'_I](u_i) + # B_J = sum_j [M'_J, M'_J](v_j) + # matrix shapes: (9,2) .dot. (2,9) for n in range(0, 3): # Hard-coded Kronecker product: vd = numpy.kron(vd_point, u_point) v_diff_u[3 * n:3 * (n + 1)] = vd_point[n] * u_point @@ -437,6 +450,7 @@ def build_sparse_reg_matrix(self): col_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv[n] nnz_a += n_uv_loc_nz * n_uv_loc_nz + print("Assembled") mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 42340d0..2db43d3 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -90,7 +90,7 @@ def test_approx_func(self): xyz_func = eval_func_on_grid(function_sin_cos, xy_mat[:2,:2], xy_mat[:, 2], z_mat) print("Compare: Func - GridSurface.transform") - points = bs.make_function_grid(function_sin_cos, 30, 40) + points = bs.make_function_grid(function_sin_cos, 200, 200) gs = bs.GridSurface(points.reshape(-1, 3)) gs.transform(xy_mat, z_mat) xyz_grid = eval_z_surface_on_grid(gs, xy_mat[:2,:2], xy_mat[:, 2]) @@ -103,7 +103,8 @@ def test_approx_func(self): grid_cmp(xyz_func, xyz_grid, 0.02) print("Compare: Func - GridSurface.transform.Z_surf_approx") - z_surf = bs_approx.surface_from_grid(gs, (16, 24)) + #z_surf = bs_approx.surface_from_grid(gs, (16, 24)) + z_surf = bs_approx.surface_from_grid(gs, (100, 100)) #z_surf.transform(xy_mat, z_mat) xyz_grid = eval_z_surface_on_grid(z_surf, xy_mat[:2,:2], xy_mat[:, 2]) grid_cmp(xyz_func, xyz_grid, 0.02) From 2509dac2ee4f947ea75b9be06ecba62e2134affa Mon Sep 17 00:00:00 2001 From: jiri Date: Wed, 4 Oct 2017 08:45:21 +0200 Subject: [PATCH 37/67] construction of the matrix A was completely rewritten due to memory and performance issues --- src/bspline_approx.py | 97 +++++++++++-------------------------------- 1 file changed, 25 insertions(+), 72 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index bac5372..c9cc70c 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -334,11 +334,11 @@ def build_ls_matrix(self): def _basis_in_q_points(self, basis): n_int = basis.n_intervals nq_points = len(self._q_points) - point_val = np.zeros((3, n_int * nq_points)) - d_point_val = np.zeros((3, n_int * nq_points)) - point_idx = np.zeros((n_int, 1)) q_point = np.zeros((n_int * nq_points, 1)) + point_val_outer = np.zeros((3, 3 * n_int)) # "3" considers degree 2 + d_point_val_outer = np.zeros((3, 3 * n_int)) # "3" considers degree 2 + #TODO: use numpy functions for quadrature points n = 0 for i in range(n_int): us = basis.knots[i + 2] @@ -346,15 +346,14 @@ def _basis_in_q_points(self, basis): for j in range(nq_points): up = us + uil * self._q_points[j] q_point[n] = up - # TODO: no need to find interval, idx = i - idx = basis.find_knot_interval(up) - u_base_vec = basis.eval_base_vector(idx, up) - u_base_vec_diff = basis.eval_diff_base_vector(idx, up) - point_val[:, n] = u_base_vec - d_point_val[:, n] = u_base_vec_diff + u_base_vec = basis.eval_base_vector(i, up) + u_base_vec_diff = basis.eval_diff_base_vector(i, up) + point_val_outer[:, 3 * i : 3 * (i + 1)] += self._weights[j] * np.outer(u_base_vec,u_base_vec) + d_point_val_outer[:, 3 * i : 3 * (i + 1)] += self._weights[j] * np.outer(u_base_vec_diff,u_base_vec_diff) n += 1 - point_idx[i] = idx - return point_val, d_point_val, point_idx, q_point + + return point_val_outer, d_point_val_outer,q_point + def build_sparse_reg_matrix(self): """ @@ -385,75 +384,29 @@ def build_sparse_reg_matrix(self): self._weights = [1.0 / 6, 5.0 / 6, 5.0 / 6, 1.0 / 6] nq_points = len(self._q_points) - # DONE: rename: u_vals, u_diffs, u_idxs, u_points - u_val, ud_val, u_idx, q_u_point = self._basis_in_q_points(self.u_basis) - v_val, vd_val, v_idx, q_v_point = self._basis_in_q_points(self.v_basis) - - # Matrix construction - # DONE: Assembly local dense blocks 9*9 and then put these nonzeroes into sparse matirx - # TODO: use numpy opperations to make assembly of local blocks readable, eliminate loops - colv = np.zeros(n_uv_loc_nz) - # DONE:rename data and data2 to something meqningful - v_diff_u = np.zeros(n_uv_loc_nz) - v_u_diff = np.zeros(n_uv_loc_nz) + u_val_outer, u_diff_val_outer, q_u_point = self._basis_in_q_points(self.u_basis) + v_val_outer, v_diff_val_outer, q_v_point = self._basis_in_q_points(self.v_basis) + row_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) - col_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) # * nq_points * nq_points + col_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) data_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) + nnz_a = 0 + linsp = np.linspace(0,self.u_basis.degree,self.u_basis.degree+1) + llinsp = np.tile(linsp,self.u_basis.degree+1) print("vnint: {} unint: {} nqp: {} prod: {}".format(v_n_inter, u_n_inter, nq_points, v_n_inter* u_n_inter* nq_points*nq_points)) for i in range(v_n_inter): - j_idx = v_idx[i] # * nq_points + k for l in range(u_n_inter): - i_idx = u_idx[l] # * nq_points + m - for k in range(nq_points): - v_point = v_val[:, i * nq_points + k] - vd_point = vd_val[:, i * nq_points + k] - for m in range(nq_points): - u_point = u_val[:, l * nq_points + m] - ud_point = ud_val[:, l * nq_points + m] - - - # 9x9 dense matrix for single patch is: - # N'_i(u) * N'_k(u) * N_j(v)*N_l(v) + N_i(u) * N_k(u) * N'_j(v) * N'_l(v) - # Precomputing M'_ik(u) = N'_i(u) * N'_k(u) and M_ik(u) = N_i(u) * N_k(u) - # and similarly M'_jl(v) and M_jl(v) for u and v quad points we can compute - # whole local matrix as M_I(u_i) * M'_J(v_j) + M'_I(u_i)*M_J(v_j) = - # [ M_I, M'_I ](u_i) .dot. [ M'_J, M_J](v_j) - # sum_i,j X_I(u_i) .dot. Y_J(v_j) = - # (sum_i X_I(u_i)) .dot. (sum_j Y_J(v_j)) - # = A_I .dot. B_J - # A_I = sum_i [M_I, M'_I](u_i) - # B_J = sum_j [M'_J, M'_J](v_j) - # matrix shapes: (9,2) .dot. (2,9) - for n in range(0, 3): - # Hard-coded Kronecker product: vd = numpy.kron(vd_point, u_point) - v_diff_u[3 * n:3 * (n + 1)] = vd_point[n] * u_point - # Hard-coded Kronecker product: ud = numpy.kron(v_point, ud_point) - v_u_diff[3 * n:3 * (n + 1)] = v_point[n] * ud_point - # column indices for data & data2 - for p in range(0, 3): - colv[3 * n + p] = (j_idx + n) * u_n_basf + i_idx + p - - # Hard-coded Outer product: - # Jacobian * weights[m] * weights[k] * (numpy.outer(ud, ud) + numpy.outer(vd, vd)) - #u_q = q_u_point[l * nq_points + m, 0] - #v_q = q_v_point[i * nq_points + k, 0] - - # jacobian for UV coordinates should be used - jac = 1.0 / u_n_inter / v_n_inter - coef = self._weights[m] * self._weights[k] * jac - for n in range(0, 9): - data_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = coef * (v_diff_u[n] * v_diff_u + v_u_diff[n] * v_u_diff) - for n in range(0, 9): - row_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv - col_m[nnz_a + 9 * n:nnz_a + 9 * (n + 1)] = colv[n] + jac = 1.0 / u_n_inter / v_n_inter + coef = jac + data_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = coef * ( np.kron(v_val_outer[:, 3 * i: 3 * (i + 1)],u_diff_val_outer[:, 3 * l: 3 * (l + 1)]).ravel() + + np.kron(v_diff_val_outer[:, 3 * i: 3 * (i + 1)],u_val_outer[:, 3 * l: 3 * (l + 1)]).ravel() ) + colv = np.repeat((i + linsp) * u_n_basf,self.u_basis.degree+1) + llinsp + col_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = np.repeat(colv,n_uv_loc_nz) + row_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = np.tile(colv,n_uv_loc_nz) nnz_a += n_uv_loc_nz * n_uv_loc_nz - print("Assembled") mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() - - return mat_a - - + return mat_a \ No newline at end of file From ca1570fc6c92ddca42d3f8011453b7a9c74cc336 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 9 Oct 2017 10:07:05 +0200 Subject: [PATCH 38/67] Enhance readability --- src/bspline.py | 7 ++++--- src/bspline_approx.py | 24 ++++++++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index b2377a3..9ad2166 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -121,8 +121,9 @@ def check(self, t, rtol = 1e-10): return t else: tol = (np.abs(t) + 1e-4)*rtol - assert self.domain[0] - tol <= t <= self.domain[1] + tol, \ - "Evaluate spline, t={}, out of domain: {}.".format(t, self.domain) + if not self.domain[0] - tol <= t <= self.domain[1] + tol: + raise IndexError("Evaluate spline, t={}, out of domain: {}.".format(t, self.domain)) + if t < self.domain[0]: return self.domain[0] else: @@ -813,7 +814,7 @@ def _get_grid_corners(self, xy_points): not la.norm(diff[1] + diff[3]) < self.step_tolerance * la.norm(diff[1]): raise GridNotInShapeExc("Grid XY envelope is not a parallelogram.") - self._point_tol = np.max( la.norm(diff, axis = 1) ) + self._point_tol = self.tolerance * np.max( la.norm(diff, axis = 1) ) self._u_step = diff[1] / (nu-1) # v10 - v00 self._v_step = diff[2] / (nv-1) # v11 - v10 diff --git a/src/bspline_approx.py b/src/bspline_approx.py index c9cc70c..2197212 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -335,8 +335,8 @@ def _basis_in_q_points(self, basis): n_int = basis.n_intervals nq_points = len(self._q_points) q_point = np.zeros((n_int * nq_points, 1)) - point_val_outer = np.zeros((3, 3 * n_int)) # "3" considers degree 2 - d_point_val_outer = np.zeros((3, 3 * n_int)) # "3" considers degree 2 + point_val_outer = np.zeros((3, 3, n_int)) # "3" considers degree 2 + d_point_val_outer = np.zeros((3, 3, n_int)) # "3" considers degree 2 #TODO: use numpy functions for quadrature points n = 0 @@ -348,8 +348,8 @@ def _basis_in_q_points(self, basis): q_point[n] = up u_base_vec = basis.eval_base_vector(i, up) u_base_vec_diff = basis.eval_diff_base_vector(i, up) - point_val_outer[:, 3 * i : 3 * (i + 1)] += self._weights[j] * np.outer(u_base_vec,u_base_vec) - d_point_val_outer[:, 3 * i : 3 * (i + 1)] += self._weights[j] * np.outer(u_base_vec_diff,u_base_vec_diff) + point_val_outer[:, :, i] += self._weights[j] * np.outer(u_base_vec,u_base_vec) + d_point_val_outer[:, :, i] += self._weights[j] * np.outer(u_base_vec_diff,u_base_vec_diff) n += 1 return point_val_outer, d_point_val_outer,q_point @@ -399,13 +399,17 @@ def build_sparse_reg_matrix(self): for i in range(v_n_inter): for l in range(u_n_inter): jac = 1.0 / u_n_inter / v_n_inter - coef = jac - data_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = coef * ( np.kron(v_val_outer[:, 3 * i: 3 * (i + 1)],u_diff_val_outer[:, 3 * l: 3 * (l + 1)]).ravel() - + np.kron(v_diff_val_outer[:, 3 * i: 3 * (i + 1)],u_val_outer[:, 3 * l: 3 * (l + 1)]).ravel() ) + idx_range = n_uv_loc_nz * n_uv_loc_nz + v_val_outer_loc = v_val_outer[:, :, i] + dv_val_outer_loc = v_diff_val_outer[:, : , i] + u_val_outer_loc = u_val_outer[:, :, i] + du_val_outer_loc = u_diff_val_outer[:, : , i] + data_m[nnz_a:nnz_a + idx_range] = jac * ( np.kron(v_val_outer_loc, du_val_outer_loc) + + np.kron(dv_val_outer_loc, u_val_outer_loc) ).ravel() colv = np.repeat((i + linsp) * u_n_basf,self.u_basis.degree+1) + llinsp - col_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = np.repeat(colv,n_uv_loc_nz) - row_m[nnz_a:nnz_a + n_uv_loc_nz * n_uv_loc_nz] = np.tile(colv,n_uv_loc_nz) - nnz_a += n_uv_loc_nz * n_uv_loc_nz + col_m[nnz_a:nnz_a + idx_range] = np.repeat(colv,n_uv_loc_nz) + row_m[nnz_a:nnz_a + idx_range] = np.tile(colv,n_uv_loc_nz) + nnz_a += idx_range print("Assembled") mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() From 3c3f7e13c982de4a9250083f1b97cf661b534fa4 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Fri, 3 Nov 2017 09:27:43 +0100 Subject: [PATCH 39/67] BSpline plotting --- src/bspline.py | 9 +- src/bspline_plot.py | 289 +++++++++++++++++++++++++------------ tests/test_bspline.py | 76 +++++----- tests/test_bspline_plot.py | 62 ++++++++ 4 files changed, 296 insertions(+), 140 deletions(-) create mode 100644 tests/test_bspline_plot.py diff --git a/src/bspline.py b/src/bspline.py index c32ce54..2620114 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -127,8 +127,6 @@ def check(self, t, rtol = 1e-10): else: return self.domain[1] - - self.domain[0] def find_knot_interval(self, t): @@ -449,10 +447,11 @@ def __init__(self, basis, poles, rational=False): self.u_basis, self.v_basis = basis # Surface basis for U and V axis. - self.dim = len(poles[0][0]) - rational + self.poles = np.array(poles, dtype=float) + self.dim = len(self.poles[0,0,:]) - rational # Surface dimension, D. - self.poles=np.array(poles, dtype=float) + # Surface poles matrix: Nu x Nv x (D+r) assert self.poles.shape == (self.u_basis.size, self.v_basis.size, self.dim + rational) @@ -461,7 +460,7 @@ def __init__(self, basis, poles, rational=False): if rational: # precomputations self._weights = poles[:, :, self.dim] - self._poles = (poles[:,:,0:self.dim].T * self._weights.T ).T + self._poles = (poles[:, :, 0:self.dim].T * self._weights.T ).T def eval(self, u, v): """ diff --git a/src/bspline_plot.py b/src/bspline_plot.py index e24b7cf..b7ca427 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -2,136 +2,237 @@ Functions to plot Bspline curves and surfaces. """ +plot_lib = "plotly" + +import plotly.offline as pl +import plotly.graph_objs as go + import matplotlib.pyplot as plt from matplotlib import cm +from mpl_toolkits.mplot3d import Axes3D + import numpy as np -def plot_curve_2d(curve, n_points=100, **kwargs): + +class PlottingPlotly: + def __init__(self): + self.i_figure = -1 + self._reinit() + + def _reinit(self): + self.i_figure += 1 + self.data_3d = [] + self.data_2d = [] + + def add_curve_2d(self, X, Y, **kwargs): + self.data_2d.append( go.Scatter(x=X, y=Y, mode = 'lines') ) + + def add_points_2d(self, X, Y, **kwargs): + marker = dict( + size=10, + color='red', + ) + self.data_2d.append( go.Scatter(x=X, y=Y, + mode = 'markers', + marker=marker) ) + + + def add_surface_3d(self, X, Y, Z, **kwargs): + hue = (120.0*(len(self.data_3d)))%360 + colorscale = [[0.0, 'hsv({}, 50%, 10%)'.format(hue)], [1.0, 'hsv({}, 50%, 90%)'.format(hue)]] + self.data_3d.append( go.Surface(x=X, y=Y, z=Z, colorscale=colorscale)) + + + def add_points_3d(self, X, Y, Z, **kwargs): + marker = dict( + size=5, + color='red', + # line=dict( + # color='rgba(217, 217, 217, 0.14)', + # width=0.5 + # ), + opacity=0.6 + ) + self.data_3d.append( go.Scatter3d( + x=X, y=Y, z=Z, + mode='markers', + marker=marker + )) + + + def show(self): + """ + Show added plots and clear the list for other plotting. + :return: + """ + if self.data_3d: + fig_3d = go.Figure(data=self.data_3d) + pl.plot(fig_3d, filename='bc_plot_3d_%d.html'%(self.i_figure)) + if self.data_2d: + fig_2d = go.Figure(data=self.data_2d) + pl.plot(fig_2d, filename='bc_plot_2d_%d.html'%(self.i_figure)) + self._reinit() + + +class PlottingMatplot: + def __init__(self): + self.fig_2d = plt.figure(1) + self.fig_3d = plt.figure(2) + self.ax_3d = self.fig_3d.gca(projection='3d') + + def add_curve_2d(self, X, Y, **kwargs): + plt.figure(1) + plt.plot(X, Y, **kwargs) + + def add_points_2d(self, X, Y, **kwargs): + plt.figure(1) + plt.plot(X, Y, 'bo', color='red', **kwargs) + + def add_surface_3d(self, X, Y, Z, **kwargs): + plt.figure(2) + self.ax_3d.plot_surface(X, Y, Z, **kwargs) + + def add_points_3d(self, X, Y, Z, **kwargs): + plt.figure(2) + return self.ax_3d.scatter(X, Y, Z, color='red', **kwargs) + + def show(self): + """ + Show added plots and clear the list for other plotting. + :return: + """ + plt.show() + + +class Plotting: """ - Plot a 2d Bspline curve. - :param curve: Curve t -> x,y - :param n_points: Number of evaluated points. - :param: kwargs: Additional parameters passed to the mtplotlib plot command. - :return: Plot object. + Debug plotting class. Several 2d and 3d plots can be added and finally displayed on common figure + calling self.show(). Matplotlib or plotly library is used as backend. """ + def __init__(self, backend = PlottingPlotly()): + self.backend = backend - basis = curve.basis - t_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + def plot_2d(self, X, Y): + """ + Add line scatter plot. Every plot use automatically different color. + :param X: x-coords of points + :param Y: y-coords of points + """ + self.backend.add_curve_2d(X,Y) - coords = [curve.eval(t) for t in t_coord] - x_coord, y_coord = zip(*coords) + def plot_surface(self, X, Y, Z): + """ + Add line scatter plot. Every plot use automatically different color. + :param X: x-coords of points + :param Y: y-coords of points + """ + self.backend.add_surface_3d(X, Y, Z) - poles = kwargs.pop('poles', False) - img = plt.plot(x_coord, y_coord, **kwargs) - if poles: - plot_curve_poles_2d(curve) + def plot_curve_2d(self, curve, n_points=100, poles=False): + """ + Add plot of a 2d Bspline curve. + :param curve: Curve t -> x,y + :param n_points: Number of evaluated points. + :param: kwargs: Additional parameters passed to the mtplotlib plot command. + """ - return img + basis = curve.basis + t_coord = np.linspace(basis.domain[0], basis.domain[1], n_points) + coords = [curve.eval(t) for t in t_coord] + x_coord, y_coord = zip(*coords) -def plot_curve_poles_2d(curve, **kwargs): - """ - Plot poles of the B-spline curve. - :param curve: Curve t -> x,y - :param: kwargs: Additional parameters passed to the mtplotlib plot command. - :return: Plot object. - """ - x_poles, y_poles = curve.poles.T[0:2, :] # remove weights - return plt.plot(x_poles, y_poles, 'bo', color='red', **kwargs) + self.backend.add_curve_2d(x_coord, y_coord) + if poles: + self.plot_curve_poles_2d(curve) + def plot_curve_poles_2d(self, curve): + """ + Plot poles of the B-spline curve. + :param curve: Curve t -> x,y + :return: Plot object. + """ + x_poles, y_poles = curve.poles.T[0:2, :] # remove weights + return self.backend.add_points_2d(x_poles, y_poles) -def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): - """ - Plot a surface in 3d. - Usage: - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.pyplot as plt - fig = plt.figure() - ax = fig.gca(projection='3d') - plot_surface_3d(surface, ax) - plt.show() - :param surface: Parametric surface in 3d. - :param fig_ax: Axes object: - :param n_points: (nu, nv), nu*nv - number of evaluation point - :param kwargs: surface_plot additional options - :return: The plot object. - """ + def plot_surface_3d(self, surface, n_points=(100, 100), poles=False): + """ + Plot a surface in 3d. + Usage: + plotting=Plotting() + plotting.plot_surface_3d(surf_1) + plotting.plot_surface_3d(surf_2) + plotting.show() - u_basis, v_basis = surface.u_basis, surface.v_basis + :param surface: Parametric surface in 3d. + :param n_points: (nu, nv), nu*nv - number of evaluation point + """ - u_coord = np.linspace(u_basis.domain[0], u_basis.domain[1], n_points[0]) - v_coord = np.linspace(v_basis.domain[0], v_basis.domain[1], n_points[1]) + u_basis, v_basis = surface.u_basis, surface.v_basis - U, V = np.meshgrid(u_coord, v_coord) - points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) + u_coord = np.linspace(u_basis.domain[0], u_basis.domain[1], n_points[0]) + v_coord = np.linspace(v_basis.domain[0], v_basis.domain[1], n_points[1]) - xyz = surface.eval_array(points) - X, Y, Z = xyz.T + U, V = np.meshgrid(u_coord, v_coord) + points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) - X = X.reshape(U.shape) - Y = Y.reshape(U.shape) - Z = Z.reshape(U.shape) + xyz = surface.eval_array(points) + X, Y, Z = xyz.T - # Plot the surface. - poles = kwargs.pop('poles', False) + X = X.reshape(U.shape) + Y = Y.reshape(U.shape) + Z = Z.reshape(U.shape) - img = fig_ax.plot_surface(X, Y, Z, **kwargs) - if poles: - plot_curve_poles_2d(surface) - return img + # Plot the surface. + self.backend.add_surface_3d(X, Y, Z) + if poles: + self.plot_surface_poles_3d(surface) -def plot_grid_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): - """ - Plot a surface in 3d. - Usage: - from mpl_toolkits.mplot3d import Axes3D - import matplotlib.pyplot as plt - fig = plt.figure() - ax = fig.gca(projection='3d') - plot_surface_3d(surface, ax) - plt.show() - :param surface: Parametric surface in 3d. - :param fig_ax: Axes object: - :param n_points: (nu, nv), nu*nv - number of evaluation point - :param kwargs: surface_plot additional options - :return: The plot object. - """ - u_coord = np.linspace(0, 1.0, n_points[0]) - v_coord = np.linspace(0, 1.0, n_points[1]) + def plot_grid_surface_3d(self, surface, n_points=(100, 100)): + """ + Plot a surface in 3d, on UV plane. - U, V = np.meshgrid(u_coord, v_coord) - points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) + :param surface: Parametric surface in 3d. + :param n_points: (nu, nv), nu*nv - number of evaluation point + """ - xyz = surface.eval_array(points) - X, Y, Z = xyz.T + u_coord = np.linspace(0, 1.0, n_points[0]) + v_coord = np.linspace(0, 1.0, n_points[1]) - X = X.reshape(U.shape) - Y = Y.reshape(U.shape) - Z = Z.reshape(U.shape) + U, V = np.meshgrid(u_coord, v_coord) + points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) - #Z = surface.z_eval_array(points).reshape(U.shape) - # Plot the surface. - img = fig_ax.plot_surface(U, V, Z, **kwargs) - return img + xyz = surface.eval_array(points) + X, Y, Z = xyz.T + Z = Z.reshape(U.shape) + + # Plot the surface. + self.backend.add_surface_3d(U, V, Z) -def plot_surface_poles_3d(surface, fig_ax, **kwargs): - """ - Plot poles of the B-spline curve. - :param curve: Curve t -> x,y - :param: kwargs: Additional parameters passed to the mtplotlib plot command. - :return: Plot object. - """ - x_poles, y_poles, z_poles = surface.poles[:, :, 0:3].reshape(-1, 3).T # remove weights and flatten nu, nv - return fig_ax.scatter(x_poles, y_poles, z_poles, color='red', **kwargs) + def plot_surface_poles_3d(self, surface, **kwargs): + """ + Plot poles of the B-spline curve. + :param curve: Curve t -> x,y + :param: kwargs: Additional parameters passed to the mtplotlib plot command. + :return: Plot object. + """ + x_poles, y_poles, z_poles = surface.poles[:, :, 0:3].reshape(-1, 3).T # remove weights and flatten nu, nv + return self.backend.add_points_3d(x_poles, y_poles, z_poles, **kwargs) + def show(self): + """ + Display added plots. Empty the queue. + :return: + """ + self.backend.show() diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 6feda6b..7215c84 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -2,11 +2,15 @@ import bspline as bs import numpy as np import math -import bspline_plot as bs_plot +import bspline_plot as bp from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt +plotting = bp.Plotting() +#plotting = bp.Plotting(bp.PlottingMatplot()) + + class TestSplineBasis: def test_find_knot_interval(self): @@ -55,9 +59,9 @@ def plot_basis(self, eq_basis): for i_base in range(eq_basis.size): y_coord = [ eq_basis.eval(i_base, x) for x in x_coord ] - plt.plot(x_coord, y_coord) + plotting.plot_2d(x_coord, y_coord) - plt.show() + plotting.show() def test_eval(self): @@ -65,7 +69,7 @@ def test_eval(self): knots = np.array([0, 0, 0, 0.1880192, 0.24545785, 0.51219762, 0.82239001, 1., 1. , 1.]) basis = bs.SplineBasis(2, knots) - # self.plot_basis(basis) + #self.plot_basis(basis) eq_basis = bs.SplineBasis.make_equidistant(0, 2) assert eq_basis.eval(0, 0.0) == 1.0 @@ -128,14 +132,14 @@ def plot_basis_vec(self, basis): y_coords[idx : idx + basis.degree + 1, i] = basis.eval_vector(idx, x) for i_base in range(basis.size): - plt.plot(x_coord, y_coords[i_base, :]) + plotting.plot_2d(x_coord, y_coords[i_base, :]) - plt.show() + plotting.show() def test_eval_vec(self): basis = bs.SplineBasis.make_equidistant(2, 4) - # self.plot_basis_vec(basis) + #self.plot_basis_vec(basis) self.check_eval_vec(basis, 0, 0.1) self.check_eval_vec(basis, 1, 0.3) self.check_eval_vec(basis, 2, 0.6) @@ -167,14 +171,14 @@ def plot_basis_diff(self, basis): y_coords[idx : idx + basis.degree + 1, i] = basis.eval_diff_vector(idx, x) for i_base in range(basis.size): - plt.plot(x_coord, y_coords[i_base, :]) + plotting.plot_2d(x_coord, y_coords[i_base, :]) - plt.show() + plotting.show() def test_eval_diff_base_vec(self): basis = bs.SplineBasis.make_equidistant(2, 4) - # self.plot_basis_diff(basis) + #self.plot_basis_diff(basis) self.check_diff_vec(basis, 0, 0.1) self.check_diff_vec(basis, 1, 0.3) self.check_diff_vec(basis, 2, 0.6) @@ -199,17 +203,17 @@ def plot_4p(self): basis = bs.SplineBasis.make_equidistant(degree, 2) curve = bs.Curve(basis, poles) - bs_plot.plot_curve_2d(curve, poles=True) + plotting.plot_curve_2d(curve, poles=True) b00, b11 = curve.aabb() b01 = [b00[0], b11[1]] b10 = [b11[0], b00[1]] bb = np.array([b00, b10, b11, b01, b00]) - plt.plot( bb[:, 0], bb[:, 1], color='green') - plt.show() + plotting.plot_2d( bb[:, 0], bb[:, 1]) + plotting.show() def test_evaluate(self): - # self.plot_4p() + #self.plot_4p() # TODO: make numerical tests with explicitely computed values # TODO: test rational curves, e.g. circle @@ -233,8 +237,6 @@ def test_aabb(self): class TestSurface: def plot_extrude(self): - fig = plt.figure() - ax = fig.gca(projection='3d') # curve extruded to surface poles_yz = [[0., 0.], [1.0, 0.5], [2., -2.], [3., 1.]] @@ -243,8 +245,8 @@ def plot_extrude(self): u_basis = bs.SplineBasis.make_equidistant(2, 1) v_basis = bs.SplineBasis.make_equidistant(2, 2) surface_extrude = bs.Surface( (u_basis, v_basis), poles) - bs_plot.plot_surface_3d(surface_extrude, ax, poles = True) - plt.show() + plotting.plot_surface_3d(surface_extrude, poles = True) + plotting.show() def plot_function(self): fig = plt.figure() @@ -258,14 +260,12 @@ def function(x): u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles) - bs_plot.plot_surface_3d(surface_func, ax) - bs_plot.plot_surface_poles_3d(surface_func, ax) - - plt.show() + plotting.plot_surface_3d(surface_func, poles = True) + plotting.show() def test_evaluate(self): - # self.plot_extrude() - # self.plot_function() + #self.plot_extrude() + #self.plot_function() # TODO: test rational surfaces, e.g. sphere pass @@ -294,27 +294,23 @@ def plot_function_uv(self): def function(x): return math.sin(x[0]*4) * math.cos(x[1]*4) - fig = plt.figure() - ax = fig.gca(projection='3d') - poles = bs.make_function_grid(function, 4, 5) u_basis = bs.SplineBasis.make_equidistant(2, 2) v_basis = bs.SplineBasis.make_equidistant(2, 3) surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) - quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) - #quad = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) + #quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) + quad = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) z_surf = bs.Z_Surface(quad, surface_func) full_surf = z_surf.make_full_surface() + z_surf.transform(np.array([[1., 0, 0], [0, 1, 0]]), np.array([2.0, 0]) ) + plotting.plot_surface_3d(z_surf) + plotting.plot_surface_3d(full_surf) - bs_plot.plot_surface_3d(z_surf, ax) - bs_plot.plot_surface_3d(full_surf, ax, color='red') - #bs_plot.plot_surface_poles_3d(surface_func, ax) - - plt.show() + plotting.show() def test_eval_uv(self): - #self.plot_function_uv() + self.plot_function_uv() pass def test_aabb(self): @@ -348,12 +344,10 @@ def make_point_grid(self): def plot_check_surface(self, XYZ_grid_eval, XYZ_surf_eval, XYZ_func_eval): - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.plot_surface(XYZ_grid_eval[:, :, 0], XYZ_grid_eval[:, :, 1], XYZ_grid_eval[:, :, 2], color='blue') - ax.plot_surface(XYZ_surf_eval[:, :, 0], XYZ_surf_eval[:, :, 1], XYZ_surf_eval[:, :, 2], color='red') - ax.plot_surface(XYZ_func_eval[:, :, 0], XYZ_func_eval[:, :, 1], XYZ_func_eval[:, :, 2], color='green') - plt.show() + plotting.plot_surface(XYZ_grid_eval[:, :, 0], XYZ_grid_eval[:, :, 1], XYZ_grid_eval[:, :, 2], color='blue') + plotting.plot_surface(XYZ_surf_eval[:, :, 0], XYZ_surf_eval[:, :, 1], XYZ_surf_eval[:, :, 2], color='red') + plotting.plot_surface(XYZ_func_eval[:, :, 0], XYZ_func_eval[:, :, 1], XYZ_func_eval[:, :, 2], color='green') + plotting.show() def grid_cmp(self, a, b, tol): a_z = a[:, :, 2].ravel() diff --git a/tests/test_bspline_plot.py b/tests/test_bspline_plot.py new file mode 100644 index 0000000..280b87e --- /dev/null +++ b/tests/test_bspline_plot.py @@ -0,0 +1,62 @@ +import pytest +import bspline as bs +import numpy as np +import math +import bspline_plot as bp + + +class TestPlottingMatplot: + plotting = bp.Plotting(bp.PlottingMatplot()) + + def plotting_2d(self, plotting): + # simple 2d graphs + n_points = 201 + eq_basis = bs.SplineBasis.make_equidistant(2, 4) + x_coord = np.linspace(eq_basis.domain[0], eq_basis.domain[1], n_points) + + for i_base in range(eq_basis.size): + y_coord = [ eq_basis.eval(i_base, x) for x in x_coord ] + plotting.plot_2d(x_coord, y_coord) + plotting.show() + + # 2d curves + degree = 2 + poles = [ [0., 0.], [1.0, 0.5], [2., -2.], [3., 1.] ] + basis = bs.SplineBasis.make_equidistant(degree, 2) + curve = bs.Curve(basis, poles) + plotting.plot_curve_2d(curve, poles=True) + + poles = [[0., 0.], [-1.0, 0.5], [-2., -2.], [3., 1.]] + basis = bs.SplineBasis.make_equidistant(degree, 2) + curve = bs.Curve(basis, poles) + plotting.plot_curve_2d(curve, poles=True) + + plotting.show() + + def test_plot_2d(self): + self.plotting_2d(bp.Plotting(bp.PlottingMatplot())) + self.plotting_2d(bp.Plotting((bp.PlottingPlotly()))) + + def plotting_3d(self, plotting): + # plotting 3d surfaces + def function(x): + return math.sin(x[0]*4) * math.cos(x[1]*4) + + poles = bs.make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) + + #quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) + quad = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) + z_surf = bs.Z_Surface(quad, surface_func) + full_surf = z_surf.make_full_surface() + z_surf.transform(np.array([[1., 0, 0], [0, 1, 0]]), np.array([2.0, 0]) ) + plotting.plot_surface_3d(z_surf) + plotting.plot_surface_3d(full_surf) + + plotting.show() + + def test_plot_3d(self): + self.plotting_3d(bp.Plotting(bp.PlottingMatplot())) + self.plotting_3d(bp.Plotting(bp.PlottingPlotly())) \ No newline at end of file From e7be6f79a181ed26ffe674559bd92843d0714b0c Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 6 Nov 2017 09:32:14 +0100 Subject: [PATCH 40/67] working version for intersections --- src/bspline_plot.py | 2 +- src/isec_surf_surf.py | 56 +++++++++++++++++++++++++++++++ tests/test_isec.py | 78 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 tests/test_isec.py diff --git a/src/bspline_plot.py b/src/bspline_plot.py index e24b7cf..89d0dbb 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -81,7 +81,7 @@ def plot_surface_3d(surface, fig_ax, n_points=(100, 100), **kwargs): img = fig_ax.plot_surface(X, Y, Z, **kwargs) if poles: - plot_curve_poles_2d(surface) + plot_surface_poles_3d(surface,fig_ax) return img diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 4ff7684..8c1056f 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -1,4 +1,60 @@ +import sys +build_path="/home/jiri/Soft/Geomop/Intersections/external/bih" +sys.path+=[build_path] +print(sys.path) + +import bih +import numpy as np +import numpy.linalg as la + + + + + class IsecSurfSurf: + + def __init__(self, surf1, surf2): + self.surf1 = surf1 + self.surf2 = surf2 + self.tree1 = self.bounding_boxes(self.surf1) + self.tree2 = self.bounding_boxes(self.surf2) + + @staticmethod + def bounding_boxes(surf): + tree = bih.BIH() + n_patch = (surf.u_basis.n_intervals - 2)*(surf.v_basis.n_intervals - 2) + patch_poles = np.zeros([9, 3, n_patch]) + i_patch = 0 + for k in range( surf.u_basis.n_intervals - 2): + for l in range(surf.v_basis.n_intervals - 2): + n_points = 0 + i_patch += 1 + for i in range(0,3): + for j in range(0,3): + patch_poles[n_points,:,i_patch] = surf.poles[k+1, l+j, :] + + boxes = [bih.AABB(patch_poles[:,:,p]) for p in range(n_patch)] + tree.add_boxes( boxes ) + tree.construct() + return tree + +# def get_intersections(tree1,tree2): +#$ for k in range( self.surf.u_basis.n_intervals - 2): + # for l in range(self.surf.v_basis.n_intervals - 2): + # self.tree1. + + + + + + + # def compute_intersection(self.tree1,self.tree2): + # + # + # intersect_box_ids = tree.find_point([0.7, 0.5, 0.5]) + # intersect_box_ids = tree.find_box(box) + + ''' Calculation and representation of intersection of two B-spline surfaces. diff --git a/tests/test_isec.py b/tests/test_isec.py new file mode 100644 index 0000000..076e337 --- /dev/null +++ b/tests/test_isec.py @@ -0,0 +1,78 @@ +import pytest +import isec_surf_surf as iss +import bspline as bs +import numpy as np +import math +import bspline_plot as bs_plot + +from mpl_toolkits.mplot3d import Axes3D +import matplotlib.pyplot as plt + +class TestSurface: + + def plot_extrude(self): + fig1 = plt.figure() + + ax1 = fig1.gca(projection='3d') + + + # curve extruded to surface + #poles_yz = [[0., 0.], [1.0, 0.5], [2., -2.], [3., 1.]] + #poles_x = [0, 1, 2] + #poles = [ [ [x] + yz for yz in poles_yz ] for x in poles_x ] + + def function(x): + return math.sin(x[0]*4) * math.cos(x[1]*4) + + def function2(x): + return math.cos(x[0]*4) * math.sin(x[1]*4) + + u_basis = bs.SplineBasis.make_equidistant(2, 10) + v_basis = bs.SplineBasis.make_equidistant(2, 15) + + poles = bs.make_function_grid(function, 12, 17) + surface_extrude = bs.Surface((u_basis, v_basis), poles) + + + bs_plot.plot_surface_3d(surface_extrude, ax1, poles = True) + # plt.show() + ### + #fig2 = plt.figure() + #ax2 = fig2.gca(projection='3d') + poles2 = bs.make_function_grid(function2, 12, 17) + surface_extrude2 = bs.Surface((u_basis, v_basis), poles2) + + bs_plot.plot_surface_3d(surface_extrude2, ax1, poles=True) + plt.show() + + return surface_extrude, surface_extrude2 + + + + def plot_function(self): + fig = plt.figure() + ax = fig.gca(projection='3d') + + # function surface + def function(x): + return math.sin(x[0]) * math.cos(x[1]) + + poles = bs.make_function_grid(function, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles) + bs_plot.plot_surface_3d(surface_func, ax) + bs_plot.plot_surface_poles_3d(surface_func, ax) + + plt.show() + + + def boudingbox(self): + SurfSurf = IsecSurfSurf.bounding_boxes() + + def test_isec(self): + + + surf1, surf2 = self.plot_extrude() + isec = iss.IsecSurfSurf(surf1, surf2) + self.boudingbox(self) \ No newline at end of file From 3ea7fee26d390a79344cf314005b0a4ddf8ce037 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 20 Nov 2017 13:55:14 +0100 Subject: [PATCH 41/67] Add checks for BW curve and surface objects. --- src/brep_writer.py | 40 +++++++++++++++++++++++++++++++++----- tests/test_bspline_plot.py | 2 +- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/brep_writer.py b/src/brep_writer.py index 3888434..7deb770 100644 --- a/src/brep_writer.py +++ b/src/brep_writer.py @@ -2,7 +2,6 @@ import numpy as np - ''' TODO: - For Solid make auto conversion from Faces similar to Face from Edges @@ -154,7 +153,9 @@ def curve_from_bs( curve): curve_dim = Curve3D else: assert False - return curve_dim(curve.poles, curve.basis.pack_knots(), curve.rational, curve.basis.degree) + c = curve_dim(curve.poles, curve.basis.pack_knots(), curve.rational, curve.basis.degree) + c._bs_curve = curve + return c class Curve3D: """ @@ -185,6 +186,13 @@ def __init__(self, poles, knots, rational=False, degree=2): self.rational=rational self.degree=degree + def _eval_check(self, t, point): + if hasattr(self, '_bs_curve'): + repr_pt = self._bs_curve.eval(t) + if not np.allclose(np.array(point), repr_pt , rtol = 1.0e-3): + raise Exception("Point: {} far from curve repr: {}".format(point, repr_pt)) + + def _dfs(self, groups): if not hasattr(self, 'id'): id = len(groups['curves_3d']) + 1 @@ -234,6 +242,12 @@ def __init__(self, poles, knots, rational=False, degree=2): self.rational=rational self.degree=degree + def _eval_check(self, t, surface, point): + if hasattr(self, '_bs_curve'): + u, v = self._bs_curve.eval(t) + surface._eval_check(u, v, point) + + def _dfs(self, groups): if not hasattr(self, 'id'): id = len(groups['curves_2d']) + 1 @@ -261,9 +275,10 @@ def surface_from_bs(surf): :param surf: bs.Surface object :return: """ - return Surface(surf.poles, (surf.u_basis.pack_knots(), surf.v_basis.pack_knots()), + s = Surface(surf.poles, (surf.u_basis.pack_knots(), surf.v_basis.pack_knots()), surf.rational, (surf.u_basis.degree, surf.v_basis.degree) ) - + s._bs_surface = surf + return s class Surface: """ @@ -308,6 +323,13 @@ def __init__(self, poles, knots, rational=False, degree=(2,2)): self.rational=rational self.degree=degree + def _eval_check(self, u, v, point): + if hasattr(self, '_bs_surface'): + repr_pt = self._bs_surface.eval(u, v) + if not np.allclose(np.array(point), repr_pt, rtol = 1.0e-3): + raise Exception("Point: {} far from curve repr: {}".format(point, repr_pt)) + + def _dfs(self, groups): if not hasattr(self, 'id'): id = len(groups['surfaces']) + 1 @@ -868,6 +890,8 @@ def attach_to_3d_curve(self, t_range, curve, location=Location()): :return: None """ assert type(curve) == Curve3D + curve._eval_check(t_range[0], self.points()[0]) + curve._eval_check(t_range[1], self.points()[1]) self.repr.append( (self.Repr.Curve3d, t_range, curve, location) ) def attach_to_2d_curve(self, t_range, curve, surface, location=Location()): @@ -881,6 +905,8 @@ def attach_to_2d_curve(self, t_range, curve, surface, location=Location()): """ assert type(surface) == Surface assert type(curve) == Curve2D + curve._eval_check(t_range[0], surface, self.points()[0]) + curve._eval_check(t_range[1], surface, self.points()[1]) self.repr.append( (self.Repr.Curve2d, t_range, curve, surface, location) ) def attach_to_plane(self, surface, v0, v1): @@ -892,6 +918,7 @@ def attach_to_plane(self, surface, v0, v1): :return: """ assert type(surface) == Surface + self.attach_to_2d_curve((0.0, 1.0), Approx.line_2d([v0, v1]), surface) def implicit_curve(self): @@ -972,6 +999,7 @@ def attach_to_3d_curve(self, t, curve, location=Location()): :param location: Location object. Default is None = identity location. :return: None """ + curve._eval_check(t, self.point) self.repr.append( (self.Repr.Curve3d, t, curve, location) ) def attach_to_2d_curve(self, t, curve, surface, location=Location()): @@ -983,6 +1011,7 @@ def attach_to_2d_curve(self, t, curve, surface, location=Location()): :param location: Location object. Default is None = identity location. :return: None """ + curve._eval_check(t, surface, self.point) self.repr.append( (self.Repr.Curve2d, t, curve, surface, location) ) def attach_to_surface(self, u, v, surface, location=Location()): @@ -993,8 +1022,10 @@ def attach_to_surface(self, u, v, surface, location=Location()): :param location: Location object. Default is None = identity location. :return: None """ + surface._eval_check(u, v, self.point) self.repr.append( (self.Repr.Surface, u,v, surface, location) ) + def _dfs(self, groups): Shape._dfs(self,groups) for repr in self.repr: @@ -1058,4 +1089,3 @@ def write_model(stream, compound, location): #stream.write("0\n") - diff --git a/tests/test_bspline_plot.py b/tests/test_bspline_plot.py index 280b87e..98caf36 100644 --- a/tests/test_bspline_plot.py +++ b/tests/test_bspline_plot.py @@ -6,7 +6,7 @@ class TestPlottingMatplot: - plotting = bp.Plotting(bp.PlottingMatplot()) + # plotting = bp.Plotting(bp.PlottingMatplot()) def plotting_2d(self, plotting): # simple 2d graphs From 3a2b07d2d99662e78dd05d8801a28bf406102559 Mon Sep 17 00:00:00 2001 From: jiri Date: Thu, 23 Nov 2017 21:35:13 +0100 Subject: [PATCH 42/67] working version --- src/isec_surf_surf.py | 138 +++++++++++++++++++++++++++++++++++++++--- tests/test_isec.py | 68 +++++++++++---------- 2 files changed, 165 insertions(+), 41 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 8c1056f..2cd5834 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -1,5 +1,5 @@ import sys -build_path="/home/jiri/Soft/Geomop/Intersections/external/bih" +build_path="/home/jiri/Soft/Geomop/Intersections/external/bih/build" sys.path+=[build_path] print(sys.path) @@ -22,21 +22,141 @@ def __init__(self, surf1, surf2): @staticmethod def bounding_boxes(surf): tree = bih.BIH() - n_patch = (surf.u_basis.n_intervals - 2)*(surf.v_basis.n_intervals - 2) + n_patch = (surf.u_basis.n_intervals)*(surf.v_basis.n_intervals) + + patch_poles = np.zeros([9, 3, n_patch]) i_patch = 0 - for k in range( surf.u_basis.n_intervals - 2): - for l in range(surf.v_basis.n_intervals - 2): + for k in range(surf.u_basis.n_intervals): + for l in range(surf.v_basis.n_intervals): n_points = 0 - i_patch += 1 for i in range(0,3): for j in range(0,3): - patch_poles[n_points,:,i_patch] = surf.poles[k+1, l+j, :] - - boxes = [bih.AABB(patch_poles[:,:,p]) for p in range(n_patch)] + patch_poles[n_points,:,i_patch] = surf.poles[k+i, l+j, :] + n_points += 1 + i_patch += 1 + boxes = [bih.AABB(patch_poles[:,:,p].tolist()) for p in range(n_patch)] + #print(patch_poles[:, :, 0]) tree.add_boxes( boxes ) tree.construct() - return tree + #print(boxes) + #for b in boxes: + # print(b.min()[0:2],b.max()[0:2]) + return boxes, tree + + + + def get_intersection(surf1,surf2,nt): + # nt - number of threads (integer) + #X,Xs, u_n_intervs,v_n_intervs,u_knots,v_knots, + #us_n_intervs,vs_n_intervs,us_knots,vs_knots,isec,n_isec, nit,nt ) + # computes intersection of BSpline patch with BSpline thread + + def patch_patch_intersection( uvt, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l): + #returns coordinetes of the intersection of the patches (if exist), one + #parameter must be fixed + pt = np.zeros(3,1) + conv =0 + tol = 1e-6; # in x,y,z + tol2 = 1e-4; # in u,v + + if np.length(u2i) == 1: + u2f = surf2.u_basis._eval_vector_deg_2(m,u2i) + + if np.length(v2i) == 1: + v2f = surf2.v_basis._eval_vector_deg_2(o,v2i) + + + for i in range(nit): + uf = surf1.u_basis._eval_vector_deg_2(k,uvt(1)) + vf = surf1.v_basis._eval_vector_deg_2(l,uvt(2)) + ufd = surf1.u_basis._eval_diff_vector_deg_2(k,uvt(1)) + vfd = surf1.v_basis._eval_diff_vector_deg_2(l,uvt(2)) + + if np.length(u2i) == 1: + v2f = surf2.v_basis._eval_vector_deg_2(o,uvt(3)) + v2fd = surf2.v_basis._eval_diff_vector_deg_2(o,uvt(3)) + dXYZp2 = (kron(v2fd',u2f')*Xs)'; + + if np.length(v2i) == 1: + u2f = splinebasevec(us_knots,uvt(3),0,m) + u2fd = splinebasevec(us_knots,uvt(3),1,m) + dXYZp2 = (kron(v2f',u2fd')*Xs)'; + + + dXYZu1 = (kron(vf',ufd')*X)' + dXYZv1 = (kron(vfd',uf')*X)' + J = [dXYZu1, dXYZv1, -dXYZp2] + deltaXYZ = (kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)' + uvt = uvt- J\deltaXYZ + test,uvt = rangetest(uvt,ui,vi,u2i,v2i,0.0) + + test,uvt = rangetest(uvt,ui,vi,u2i,v2i,tol2); + if test == 1 + dist = get_delta(u_knots, v_knots,us_knots, vs_knots,uvt,u2i,v2i,k,l,m,o,X,Xs); + + + if test == 1: + if dist <= tol: + pt =kron(vf',uf')*X + conv =1 + else: + uvt = np.zeros(3,1) + + return uvu2v2, ptl, conv + + + n_points = 0 + ninter = np.zeros(surf1.u_basis.n_intervals,surf1.v_basis.n_intervals) + for k in range(surf1.u_basis.n_intervals): + us1 = surf1.u_basis.knots[k + 1] + ue1 = surf1.u_basis.knots[k + 2] + u1_c =(us1 + ue1)/2 + ui = np.array([us1, ue1]) + for l in range(surf1.v_basis.n_intervals): + vs1 = surf1.v_basis.knots(l+1) + ve1 = surf1.v_basis.knots(l+2) + v1_c = (vs1 + ve1)/2 + vi = np.array([vs1, ve1]) + s=0 + if n_isec(k,l) ~= 0 + for p =1: n_isec(k,l) + m = ceil(isec(k,l,p) / surf2.u_basis.n_intervals) + o = isec(k,l,p) - (m-1)*surf2.u_basis.n_intervals + sp_i = (m-1)*(surf2.u_basis.n_intervals) + o + # v2 fixed + u2i = [surf2.u_basis.knots(m+1), surf2.u_basis.knots(m+2)] + v_knot = np.linspace(surf2.v_basis.knots(o+1),surf2.v_basis(o+2),nt) + for h in range(nt): #=1:length(v_knot) + u2_c = (u2i[0] + u2i[1])/2 + v2i = v_knot[h] + uvu2v2 = [u1_c, v1_c, u2_c] # initial condition + uvu2v2,ptl,conv = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); + if conv ~= 0 + s = s+1; + n_points = n_points +1; + point(n_points,:) = [uvu2v2',v_knot(h),ptl(1),ptl(2),ptl(3),k,l,m,o]; + + # u2 fixed + v2i = [vs_knots(o+2) vs_knots(o+3)]; + u_knot = linspace(us_knots(m+2),us_knots(m+3),nt); + for h =1:length(u_knot) + v2_c = (vs_knots(o+2) + vs_knots(o+3))/2; + u2i = u_knot(h); + uvu2v2 = [u1_c;v1_c;v2_c]; % initial condition + [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); + if conv ~= 0 + s = s+1; + n_points = n_points +1; + point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o]; + end + end + ninter(k,l) = s; + + return point , n_points,ninter + + + # def get_intersections(tree1,tree2): #$ for k in range( self.surf.u_basis.n_intervals - 2): diff --git a/tests/test_isec.py b/tests/test_isec.py index 076e337..5818bb9 100644 --- a/tests/test_isec.py +++ b/tests/test_isec.py @@ -3,7 +3,8 @@ import bspline as bs import numpy as np import math -import bspline_plot as bs_plot +import bspline_plot as bp + from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt @@ -11,15 +12,11 @@ class TestSurface: def plot_extrude(self): - fig1 = plt.figure() + #fig1 = plt.figure() - ax1 = fig1.gca(projection='3d') + #ax1 = fig1.gca(projection='3d') - # curve extruded to surface - #poles_yz = [[0., 0.], [1.0, 0.5], [2., -2.], [3., 1.]] - #poles_x = [0, 1, 2] - #poles = [ [ [x] + yz for yz in poles_yz ] for x in poles_x ] def function(x): return math.sin(x[0]*4) * math.cos(x[1]*4) @@ -27,44 +24,44 @@ def function(x): def function2(x): return math.cos(x[0]*4) * math.sin(x[1]*4) - u_basis = bs.SplineBasis.make_equidistant(2, 10) - v_basis = bs.SplineBasis.make_equidistant(2, 15) + u1_int = 4 + v1_int = 4 + u2_int = 4 + v2_int = 4 - poles = bs.make_function_grid(function, 12, 17) + u_basis = bs.SplineBasis.make_equidistant(2, u1_int) #10 + v_basis = bs.SplineBasis.make_equidistant(2, v1_int) #15 + poles = bs.make_function_grid(function, u1_int + 2, v1_int + 2) #12, 17 surface_extrude = bs.Surface((u_basis, v_basis), poles) - - bs_plot.plot_surface_3d(surface_extrude, ax1, poles = True) - # plt.show() - ### - #fig2 = plt.figure() - #ax2 = fig2.gca(projection='3d') - poles2 = bs.make_function_grid(function2, 12, 17) + myplot = bp.Plotting((bp.PlottingPlotly())) + myplot.plot_surface_3d(surface_extrude, poles = False) + poles2 = bs.make_function_grid(function2, u2_int + 2, v2_int + 2) #12, 17 surface_extrude2 = bs.Surface((u_basis, v_basis), poles2) + myplot.plot_surface_3d(surface_extrude2, poles=False) - bs_plot.plot_surface_3d(surface_extrude2, ax1, poles=True) - plt.show() + myplot.show() return surface_extrude, surface_extrude2 - def plot_function(self): - fig = plt.figure() - ax = fig.gca(projection='3d') + #def plot_function(self): + # fig = plt.figure() + # ax = fig.gca(projection='3d') # function surface - def function(x): - return math.sin(x[0]) * math.cos(x[1]) + # def function(x): + # return math.sin(x[0]) * math.cos(x[1]) - poles = bs.make_function_grid(function, 4, 5) - u_basis = bs.SplineBasis.make_equidistant(2, 2) - v_basis = bs.SplineBasis.make_equidistant(2, 3) - surface_func = bs.Surface( (u_basis, v_basis), poles) - bs_plot.plot_surface_3d(surface_func, ax) - bs_plot.plot_surface_poles_3d(surface_func, ax) + #poles = bs.make_function_grid(function, 4, 5) + #u_basis = bs.SplineBasis.make_equidistant(2, 2) + #v_basis = bs.SplineBasis.make_equidistant(2, 3) + #surface_func = bs.Surface( (u_basis, v_basis), poles) + #bs_plot.plot_surface_3d(surface_func, ax) + #bs_plot.plot_surface_poles_3d(surface_func, ax) - plt.show() + #plt.show() def boudingbox(self): @@ -75,4 +72,11 @@ def test_isec(self): surf1, surf2 = self.plot_extrude() isec = iss.IsecSurfSurf(surf1, surf2) - self.boudingbox(self) \ No newline at end of file + boxes1, tree1 = isec.bounding_boxes(surf1) + boxes2, tree2 = isec.bounding_boxes(surf2) + #print(tree1.find_box(boxes2[0])) + #print(surf1.poles[:,:,1]) + #print(surf1.u_basis.n_intervals) + #print(surf1.u_basis.knots) + + From cad043ee0a5d70d49043062cfb104994165be1b5 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 29 Nov 2017 17:42:06 +0100 Subject: [PATCH 43/67] Add reset_transform method. --- src/bspline.py | 65 ++++++++++++++++++++++++++++--------------- tests/test_bspline.py | 21 +++++++++++++- 2 files changed, 63 insertions(+), 23 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 2620114..f33e7b2 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -537,6 +537,36 @@ def __init__(self, xy_quad, z_surface): self.v_basis = z_surface.v_basis # Basis for UV directions. + self.reset_transform(xy_quad) + + + def make_full_surface(self): + """ + Return representation of the surface by the 3d Surface object. + Compute redundant XY poles. + :return: Surface. + """ + basis = (self.z_surface.u_basis, self.z_surface.v_basis) + + u = basis[0].make_linear_poles() + v = basis[1].make_linear_poles() + V, U = np.meshgrid(v,u) + uv_poles_vec = np.stack([U.ravel(), V.ravel()], axis=1) + xy_poles = self.uv_to_xy(uv_poles_vec).reshape(U.shape[0], U.shape[1], 2) + poles = np.concatenate( (xy_poles, self.z_surface.poles), axis = 2 ) + + return Surface(basis, poles) + + def reset_transform(self, xy_quad): + """ + Set XY transform according to given domain quadrilateral (or triangle for linear mapping case). + :param xy_quad: np array N x 2 + Four or three points, determining bilinear or linear mapping, respectively. + Four points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0), (1,1) + Three points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0) + Linear case is also detected for the four points. + :return: None + """ # Build envelope quadrilateral polygon in XY plane. self.quad = np.array(xy_quad, dtype=float) assert self.quad.shape[0] in [3, 4], "Three or four points must be given." @@ -563,28 +593,6 @@ def __init__(self, xy_quad, z_surface): self.uv_to_xy = self._bilinear_uv_to_xy - #TODO: remove this, after fixing GridSurface z_eval - #self.z_scale =1.0 - #self.z_shift = 1.0 - - def make_full_surface(self): - """ - Return representation of the surface by the 3d Surface object. - Compute redundant XY poles. - :return: Surface. - """ - basis = (self.z_surface.u_basis, self.z_surface.v_basis) - - u = basis[0].make_linear_poles() - v = basis[1].make_linear_poles() - V, U = np.meshgrid(v,u) - uv_poles_vec = np.stack([U.ravel(), V.ravel()], axis=1) - xy_poles = self.uv_to_xy(uv_poles_vec).reshape(U.shape[0], U.shape[1], 2) - poles = np.concatenate( (xy_poles, self.z_surface.poles), axis = 2 ) - - return Surface(basis, poles) - - def transform(self, xy_mat, z_mat = np.array( [1.0, 0.0] ) ): """ Transform the Z-surface by arbitrary XY linear transform and Z linear transform. @@ -807,6 +815,7 @@ def _get_grid_corners(self, xy_points): vtx_10 = xy_points[-nv, :] vtx_11 = xy_points[-1, :] self.quad = np.array( [ vtx_01, vtx_00, vtx_10, vtx_11 ], dtype = float ) + self._orig_quad = self.quad # check that quad is parallelogram. diff = np.roll(self.quad, -1, axis = 0) - self.quad @@ -854,6 +863,18 @@ def _check_map(self): #print( "Check quad: ", uv_quad ) assert np.allclose( uv_quad, np.array([[0, 1], [0, 0], [1, 0], [1, 1]]) ) + def reset_transform(self): + """ + Set identify transform just as after construction. + :param xy_mat: np array, 2 rows 3 cols, last column is xy shift + :param z_mat: [ z_scale, z_shift] + :return: + """ + self.z_scale = 1.0 + self.z_shift = 0.0 + self.z_surface.reset_transform(self._orig_quad) + self.quad = self._orig_quad + self._check_map() diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 7215c84..19e35b9 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -310,7 +310,7 @@ def function(x): plotting.show() def test_eval_uv(self): - self.plot_function_uv() + #self.plot_function_uv() pass def test_aabb(self): @@ -425,3 +425,22 @@ def test_grid_surface(self): assert np.allclose(v_min, np.array([-3.0/math.sqrt(2) -2, 0.0 + 5, 1.3])) assert np.allclose(v_max, np.array([3.0 / math.sqrt(2) - 2, 4.0 / math.sqrt(2) + 5, math.sin(1.0) + 1.3])) + def test_grid_surface_transform(self): + surface = self.make_point_grid() + xy_mat = np.array([ [3.0, 0.0], [0.0, 2.0] ]) + xy_shift = np.array([[-2.0, 5.0 ]]) + surface.transform(np.concatenate((xy_mat, xy_shift.T), axis=1), None) + quad = surface.quad + #print(quad) + assert quad[0][0] == -2 + assert quad[0][1] == 7 + assert quad[2][0] == 1 + assert quad[2][1] == 5 + + surface.reset_transform() + quad = surface.quad + assert quad[0][0] == 0 + assert quad[0][1] == 1 + assert quad[2][0] == 1 + assert quad[2][1] == 0 + From 95ac0f0c87ca061717fe666ffbe3c083f9fabcbb Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 3 Dec 2017 22:56:33 +0100 Subject: [PATCH 44/67] Add scatter plots --- src/bspline_plot.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/bspline_plot.py b/src/bspline_plot.py index b7ca427..06ecb42 100644 --- a/src/bspline_plot.py +++ b/src/bspline_plot.py @@ -122,6 +122,14 @@ def plot_2d(self, X, Y): """ self.backend.add_curve_2d(X,Y) + def scatter_2d(self, X, Y): + """ + Add point scatter plot. Every plot use automatically different color. + :param X: x-coords of points + :param Y: y-coords of points + """ + self.backend.add_points_2d(X,Y) + def plot_surface(self, X, Y, Z): """ Add line scatter plot. Every plot use automatically different color. @@ -158,6 +166,14 @@ def plot_curve_poles_2d(self, curve): x_poles, y_poles = curve.poles.T[0:2, :] # remove weights return self.backend.add_points_2d(x_poles, y_poles) + def scatter_3d(self, X, Y, Z): + """ + Add point scatter plot. Every plot use automatically different color. + :param X: x-coords of points + :param Y: y-coords of points + """ + self.backend.add_points_3d(X, Y, Z) + def plot_surface_3d(self, surface, n_points=(100, 100), poles=False): """ From 87dac0f9cf3c18e56e59b001b357a7917d5dbfa0 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 3 Dec 2017 22:57:04 +0100 Subject: [PATCH 45/67] Make Z Surface interface compatible with GridSurface --- src/bspline.py | 85 ++++++++++++++++++++++++++++++++----------- tests/test_bspline.py | 9 ++++- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index f33e7b2..7dce3c9 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -537,8 +537,12 @@ def __init__(self, xy_quad, z_surface): self.v_basis = z_surface.v_basis # Basis for UV directions. - self.reset_transform(xy_quad) + self.quad = None + # Boundary quadrilateral. + self.reset_transform(xy_quad) + # Set further private attributes, see comment there: + # _z_mat, _have_z_mat, _xy_shift, _mat_xy_to_uv, _mat_uv_to_xy def make_full_surface(self): """ @@ -553,7 +557,11 @@ def make_full_surface(self): V, U = np.meshgrid(v,u) uv_poles_vec = np.stack([U.ravel(), V.ravel()], axis=1) xy_poles = self.uv_to_xy(uv_poles_vec).reshape(U.shape[0], U.shape[1], 2) - poles = np.concatenate( (xy_poles, self.z_surface.poles), axis = 2 ) + z_poles = self.z_surface.poles.copy() + if self._have_z_mat: + z_poles *= self.z_mat[0] + z_poles += self.z_mat[1] + poles = np.concatenate( (xy_poles, z_poles), axis = 2 ) return Surface(basis, poles) @@ -592,31 +600,47 @@ def reset_transform(self, xy_quad): self.xy_to_uv = self._bilinear_xy_to_uv self.uv_to_xy = self._bilinear_uv_to_xy + self.z_mat = np.array( [1.0, 0.0] ) + # [ z_scale, z_shift ] + + self._have_z_mat = False + # Indicate that we have z_mat different from identity. + # Optimization for array evaluation methods. z_mat must be set anyway. - def transform(self, xy_mat, z_mat = np.array( [1.0, 0.0] ) ): + def transform(self, xy_mat, z_mat = None ): """ Transform the Z-surface by arbitrary XY linear transform and Z linear transform. :param xy_mat: np array, 2 rows 3 cols, last column is xy shift - :param z_shift: [ z_scale, z_shift] + :param z_shift: np.array, [ z_scale, z_shift] :return: None """ - assert xy_mat.shape == (2, 3) - assert z_mat.shape == (2, ) - - self._mat_uv_to_xy = xy_mat[0:2,0:2].dot( self._mat_uv_to_xy ) - self._xy_shift = xy_mat[0:2,0:2].dot( self._xy_shift ) + xy_mat[0:2, 2] - self._mat_xy_to_uv = la.inv(self._mat_uv_to_xy) + if xy_mat is not None: + assert xy_mat.shape == (2, 3) + self._mat_uv_to_xy = xy_mat[0:2,0:2].dot( self._mat_uv_to_xy ) + self._xy_shift = xy_mat[0:2,0:2].dot( self._xy_shift ) + xy_mat[0:2, 2] + self._mat_xy_to_uv = la.inv(self._mat_uv_to_xy) - # transform quad - self.quad = np.dot(self.quad, xy_mat[0:2,0:2].T) + xy_mat[0:2, 2] + # transform quad + self.quad = np.dot(self.quad, xy_mat[0:2,0:2].T) + xy_mat[0:2, 2] # apply z-transfrom directly to the poles - self.z_surface.poles *= z_mat[0] - self.z_surface.poles += z_mat[1] + if z_mat is not None: + assert z_mat.shape == (2,) + self.z_mat[0] *= z_mat[0] + self.z_mat[1] = z_mat[0] * self.z_mat[1] + z_mat[1] + self._have_z_mat = True + + def apply_z_transform(self): + """ + Apply current Z transform to the poles. Reset the Z transform. + :return: + """ + if self._have_z_mat: + self.z_surface.poles *= self.z_mat[0] + self.z_surface.poles += self.z_mat[1] + self.z_mat = np.array([1.0, 0.0]) + self._have_z_mat = False - #TODO: remove this, after fixing GridSurface z_eval - #self.z_scale *= z_mat[0] - #self.z_shift = self.z_shift*z_mat[0] + z_mat[1] @@ -665,6 +689,7 @@ def eval(self, u, v): :return: D-dimensional numpy array. D - is dimension given by dimension of poles. """ z = self.z_surface.eval(u, v) + z = self.z_mat[0] * z + self.z_mat[1] uv_points = np.array([[u, v]]) x, y = self.uv_to_xy( uv_points )[0] return np.array( [x, y, z] ) @@ -678,6 +703,10 @@ def eval_array(self, uv_points): """ assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) + if self._have_z_mat: + z_points *= self.z_mat[0] + z_points += self.z_mat[1] + xy_points = self.uv_to_xy(uv_points) return np.concatenate( (xy_points, z_points), axis = 1) @@ -689,8 +718,7 @@ def eval_xy_array(self, xy_points): :return: array N x D; D - is dimension given by dimension of poles. """ uv_points = self.xy_to_uv(xy_points) - z_points = self.z_surface.eval_array(uv_points) - return np.concatenate( (xy_points, z_points), axis = 1) + return self.eval_array(uv_points) def z_eval_array(self, uv_points): @@ -701,6 +729,9 @@ def z_eval_array(self, uv_points): """ assert uv_points.shape[1] == 2 z_points = self.z_surface.eval_array(uv_points) + if self._have_z_mat: + z_points *= self.z_mat[0] + z_points += self.z_mat[1] return z_points.reshape(-1) @@ -711,15 +742,22 @@ def z_eval_xy_array(self, xy_points): :return: array N x D; D - is dimension given by dimension of poles. """ uv_points = self.xy_to_uv(xy_points) - z_points = self.z_surface.eval_array(uv_points) - return z_points.reshape(-1) + return self.z_eval_array(uv_points) + + + def center(self): + """ + Evaluate surface in center of UV square. + :return: (X, Y, Z) + """ + return self.eval_array( np.array([ [0.5, 0.5] ])) def aabb(self): xyz_box = np.empty( (2, 3) ) xyz_box[0, 0:2] = np.amin(self.quad, axis=0) xyz_box[1, 0:2] = np.amax(self.quad, axis=0) - xyz_box[:, 2] = self.z_surface.aabb()[:,0] + xyz_box[:, 2] = self.z_mat[0]*self.z_surface.aabb()[:,0] + self.z_mat[1] return xyz_box @@ -850,6 +888,9 @@ def _make_z_surface(self, points): poles_z = points[:, 2].reshape(nu, nv, 1) self.z_surface = Z_Surface(self.quad[0:3], Surface((u_basis, v_basis), poles_z) ) + self.u_basis = self.z_surface.u_basis + self.v_basis = self.z_surface.v_basis + uv_points = self.z_surface.xy_to_uv( points[:, 0:2] ) grid_uv = uv_points.reshape(nu, nv, 2) self.grid_uvz = np.concatenate((grid_uv, poles_z), axis=2) diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 19e35b9..0d75b52 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -302,15 +302,16 @@ def function(x): #quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) quad = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) z_surf = bs.Z_Surface(quad, surface_func) + z_surf.transform(None, np.array([2.0, 0]) ) full_surf = z_surf.make_full_surface() - z_surf.transform(np.array([[1., 0, 0], [0, 1, 0]]), np.array([2.0, 0]) ) + z_surf.transform(None, np.array([1.0, 0.1]) ) plotting.plot_surface_3d(z_surf) plotting.plot_surface_3d(full_surf) plotting.show() def test_eval_uv(self): - #self.plot_function_uv() + self.plot_function_uv() pass def test_aabb(self): @@ -328,6 +329,10 @@ def function(x): box = z_surf.aabb() assert np.allclose(box, np.array([[0, 0, 3], [1.1, 1.1, 5]])) + z_surf.transform(None, np.array([2.0, 1.0])) + box = z_surf.aabb() + assert np.allclose(box, np.array([[0, 0, 7], [1.1, 1.1, 11]])) + class TestPointGrid: From decc6cd77f618e99ff8ee0031e4e475dd1f2c8c9 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Sun, 3 Dec 2017 23:09:24 +0100 Subject: [PATCH 46/67] public SurfaceApprox class - implemented calculation of min area bounding box --- src/bspline_approx.py | 442 +++++++++++++++++++++++++++++----------- tests/test_bs_approx.py | 145 ++++++++----- 2 files changed, 417 insertions(+), 170 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 2197212..ea68353 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -2,15 +2,18 @@ Collection of functions to produce Bapline curves and surfaces as approximation of various analytical curves and surfaces. """ -import bspline as bs -import numpy as np -import math +import logging import time -#import numpy.linalg +#import math + +import numpy as np +import numpy.linalg as la import scipy.sparse import scipy.sparse.linalg import scipy.interpolate +import bspline as bs + """ Approximation methods for B/splines of degree 2. @@ -94,7 +97,7 @@ def surface_from_grid(grid_surface, nuv, **kwargs): :param (nu, nv) Prescribed number of poles in u and v directions. :return: Z_surface object. """ - approx = _SurfaceApprox(grid_surface, nuv, **kwargs) + approx = SurfaceApprox(grid_surface, nuv, **kwargs) return approx.get_approximation() @@ -129,14 +132,114 @@ def curve_from_grid(points, **kwargs): +def convex_hull_2d(sample): + link = lambda a, b: np.concatenate((a, b[1:])) + edge = lambda a, b: np.concatenate(([a], [b])) + def dome(sample, base): + """ + Return convex hull of the points on the right side from the base. + :param sample: Nx2 nupy array of points + :param base: A segment, np array [[x0,y0], [x1,y1]] + :return: np array of points Nx2 on forming the convex hull + """ + # End points of line. + h, t = base + normal = np.dot(((0,-1),(1,0)),(t-h)) + # Distances from the line. + dists = np.dot(sample-h, normal) + + outer = np.repeat(sample, dists>0, 0) + if len(outer): + pivot = sample[np.argmax(dists)] + return link(dome(outer, edge(h, pivot)), + dome(outer, edge(pivot, t))) + else: + return base + if len(sample) > 2: + axis = sample[:,0] + # Get left most and right most points. + base = np.take(sample, [np.argmin(axis), np.argmax(axis)], 0) + return link(dome(sample, base), dome(sample, base[::-1])) + else: + return sample -class _SurfaceApprox: + +def min_bounding_rect(hull): + """ + Compute minimal area bounding box from a convex hull. + Quadratic algorithm with respect to number of hull points is used, anyway calculation a convex hull + takes longer since number of hull points is about sqrt of all points. + :param hull: Nx2 numpy array of the convex hull points. First and last must be the same. + :return: Corners of the rectangle. """ + # Compute edges (x2-x1,y2-y1) + edges = hull[1:, :] - hull[:-1, :] + + # Calculate edge angles atan2(y/x) + edge_angles = np.arctan2(edges[:, 1], edges[:, 0]) + + # Check for angles in 1st quadrant + edge_angles = np.abs( edge_angles%(np.pi/2)) + + # Remove duplicate angles + edge_angles = np.unique(edge_angles) + + # Test each angle to find bounding box with smallest area + min_bbox = (0, float("inf"), 0, 0, 0, 0, 0, 0) # rot_angle, area, width, height, min_x, max_x, min_y, max_y + for i in range( len(edge_angles) ): + + # Create rotation matrix to shift points to baseline + # R = [ cos(theta) , cos(theta-PI/2) + # cos(theta+PI/2) , cos(theta) ] + angle = edge_angles[i] + R = np.array([[np.cos(angle), np.cos(angle - (np.pi / 2))], + [np.cos(angle + (np.pi / 2)), np.cos(angle)]]) + + # Apply this rotation to convex hull points + rot_points = np.dot(R, np.transpose(hull)) # 2x2 * 2xn + + # Find min/max x,y points + min_x = np.nanmin(rot_points[0], axis=0) + max_x = np.nanmax(rot_points[0], axis=0) + min_y = np.nanmin(rot_points[1], axis=0) + max_y = np.nanmax(rot_points[1], axis=0) + + # Calculate height/width/area of this bounding rectangle + area = (max_x - min_x) * (max_y - min_y) + + # Store the smallest rect found first (a simple convex hull might have 2 answers with same area) + if (area < min_bbox[1]): + min_bbox = ( edge_angles[i], area, min_x, max_x, min_y, max_y ) + + # Re-create rotation matrix for smallest rect + angle = min_bbox[0] + R = np.array([[np.cos(angle), np.cos(angle- (np.pi / 2))], + [np.cos(angle + (np.pi / 2)), np.cos(angle)]]) + + + # min/max x,y points are against baseline + min_x = min_bbox[2] + max_x = min_bbox[3] + min_y = min_bbox[4] + max_y = min_bbox[5] + + # Calculate corner points and project onto rotated frame + corner_points = np.zeros( (4,2) ) # empty 2 column array + corner_points[0] = np.dot( [ min_x, max_y ], R ) + corner_points[1] = np.dot( [ min_x, min_y ], R ) + corner_points[2] = np.dot( [ max_x, min_y ], R ) + corner_points[3] = np.dot( [ max_x, max_y ], R ) + + return corner_points + + +class SurfaceApprox: + """ + Class to compute a Bspline surface approximation from given set of XYZ points. TODO: - Check efficiency of scipy methods, compare it to our approach assuming theoretical number of operations. - - Optimize construction of A regularization matrix. - Compute BtB directly during single assembly pass, local 9x9 matricies as in A matrix. - In contradiction to some literature (Hoschek) solution of the LS system is fast as long as the basis is local ( this is true for B-splines). @@ -144,105 +247,177 @@ class _SurfaceApprox: """ + @staticmethod + def approx_from_file(filename): + """ + Load a sequence of XYZ points on a surface to be approximated. + Optionally points may have weights (i.e. four values per line: XYZW) + :param filename: Path to the input text file. + :return: The approximation object. + """ + point_seq = np.loadtxt(filename) + return SurfaceApprox(point_seq) + + @staticmethod + def approx_from_grid_surface(grid_surface): + u_basis, v_basis = grid_surface.u_basis, grid_surface.v_basis - def __init__(self, grid_surface, nuv=None, **kwargs): - self.degree = np.array((2, 2)) - self.grid_surf = grid_surface - self.nuv = nuv - self.regularization_weight = kwargs.get('reg_weight', 0.001) + u_coord = u_basis.make_linear_poles() + v_coord = v_basis.make_linear_poles() - def get_approximation(self): - if not hasattr(self, 'z_surf'): - self.z_surf = self.approx_chol() - return self.z_surf + U, V = np.meshgrid(u_coord, v_coord) + uv_points = np.stack( [U.ravel(), V.ravel()], axis = 1 ) + xyz = grid_surface.eval_array(uv_points) + approx = SurfaceApprox(xyz) + approx.quad = grid_surface.quad + return approx - def approx_chol(self): + def __init__(self, points): """ - This function tries to approximate terrain data with B-Spline surface patches - using Cholesky decomposition - :param terrain_data: matrix of 3D terrain data - :param quad: points defining quadrangle area (array) - :param u_knots: array of u knots - :param v_knots: array of v knots - :param sparse: if sparse matrix is used - :param filter_thresh: threshold of filter - :return: B-Spline patch + Initialize the approximation object with the points. + :param points: Nx3 (XYZ) or Nx4 (XYZW - points with weights) """ + # Degree of approximation in U anv V directions, fixed to 2. + self._degree = np.array((2, 2)) - print('Transforming points to parametric space ...') - start_time = time.time() - points_uvz = self.grid_surf.grid_uvz.reshape(-1, 3) - points_uv = points_uvz[:, 0:2] + assert( points.shape[1] >= 3 ) + # XYZ points + self._n_points = points.shape[0] + self._xy_points = points[:, 0:2] + self._z_points = points[:, 2] - # remove points far from unit square - eps = 1.0e-15 - cut_min = np.array( [ -eps, -eps ]) - cut_max = np.array( [ 1+eps, 1+eps ]) - in_idx = np.all(np.logical_and(cut_min < points_uv, points_uv <= cut_max), axis=1) - points_uv = points_uv[in_idx] - self.points_z = points_uvz[in_idx, 2][:,None] - print("Number of points out of the grid domain: {}".format(len(points_uv) - np.sum(in_idx))) + # point weights + if points.shape[1] > 3: + self._weights = points[:, 3] + else: + self._weights = None - # snap to unit square - points_uv = np.maximum(points_uv, np.array([0.0, 0.0])) - self.points_uv = np.minimum(points_uv, np.array([1.0, 1.0])) + ## Approximation parameters. - # determine number of knots - assert len(self.points_uv) == len(self.points_z) - n_points = len(self.points_uv) + # Bounding quadrilateral of the approximation (currently only parallelograms are supported). + # Only first three points P0, P1, P2 are considered. V direction is P0 - P1, U direction is P2 - P1. + # I.e. points are sorted counter-clockwise. + self.quad = None - grid_shape = np.asarray(self.grid_surf.shape, dtype=int) - if self.nuv == None: - nuv = np.floor_divide( grid_shape / 3 ) - else: - nuv = np.floor(np.array(self.nuv)) - nuv = np.minimum( nuv, grid_shape - self.degree - 2) - size_u, size_v = nuv + self.degree + # (nu, nv) number of subintervals of the BSSurface on U and V axis. + # Default is estimated from the number of input points N as nu=nv=sqrt(N)/3. + self.nuv = None + + # Weight of the regularizing term. + self.regularization_weight = 0.001 + + ## Approximation results + + # Approximationg BSSurface + self.surface = None + + # Error of the approximation + self.error = None + + + def compute_default_quad(self): + """ + Compute and set boundary quad as a minimum area bounding box of the input XY point set. + :return: The quadrilateral vertices. + """ + hull = convex_hull_2d(self._xy_points) + self.quad = min_bounding_rect(hull) + return self.quad + + def _compute_default_nuv(self, n_points): + """ + Default nu and nv for given number of points inside of quad. + :return: (nu, nv) + """ + assert(self.quad is not None) + + dv = la.norm(self.quad[0, :] - self.quad[1, :]) + du = la.norm(self.quad[2, :] - self.quad[1, :]) # try to make number of unknowns less then number of remaining points # +1 to improve determination - if (size_u + 1) * (size_v + 1) > n_points: - sv = np.floor(np.sqrt( n_points * size_v / size_u )) - su = np.floor(sv * size_u / size_v) - nuv = np.array( [su, sv] ) - self.degree - nuv = nuv.astype(int) - if nuv[0] < 1 or nuv[1] < 1: - raise Exception("Two few points, {}, to make approximation, degree: {}".format(n_points, self.degree)) - - print("Using {} x {} B-spline approximation.".format(nuv[0], nuv[1])) - self.u_basis = bs.SplineBasis.make_equidistant(2, nuv[0]) - self.v_basis = bs.SplineBasis.make_equidistant(2, nuv[1]) + nv = np.sqrt( n_points * dv / du ) + nu = nv * du / dv + nuv = np.array( [np.floor(nu / 3), np.floor(nv / 3)] ) - self._degree + self.nuv = np.maximum(1, nuv) + return self.nuv - end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) - # Own computation of approximation - print('Creating B matrix ...') + def compute_approximation(self, **kwargs): + """ + Compute approximation of the point set (given to constructor). + Approximation parameters can be passed in through kwargs or set in the object before the call. + :return: B-Spline surface + """ + self.quad = kwargs.get("quad", self.quad) + self.nuv = kwargs.get("nuv", self.nuv) + self.regularization_weight = kwargs.get("regularization_weight", self.regularization_weight) + + logging.info('Transforming points to parametric space ...') + start_time = time.time() + + if self.quad is None: + self.quad = self.compute_default_quad() + self._compute_uv_points() + + if self.nuv is None: + self.nuv = self._compute_default_nuv(len(self._z_quad_points)) + self.nuv = self.nuv.astype(int) + if self.nuv[0] < 1 or self.nuv[1] < 1: + raise Exception("Two few points, {}, to make approximation, degree: {}".format(self._n_points, self._degree)) + + logging.info("Using {} x {} B-spline approximation.".format(self.nuv[0], self.nuv[1])) + self._u_basis = bs.SplineBasis.make_equidistant(2, self.nuv[0]) + self._v_basis = bs.SplineBasis.make_equidistant(2, self.nuv[1]) + + end_time = time.time() + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + + # Approximation itself + logging.info('Creating B matrix ...') start_time = time.time() - b_mat, interval = self.build_ls_matrix() + b_mat, interval = self._build_ls_matrix() end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - print('Creating A matrix ...') + logging.info('Creating A matrix ...') start_time = time.time() - a_mat = self.build_sparse_reg_matrix() + a_mat = self._build_sparse_reg_matrix() end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - print('Computing B^T B matrix ...') + logging.info('Scaling ...') start_time = time.time() - bb_mat = b_mat.transpose().dot(b_mat) + g_vec = self._z_quad_points[:] + + if self._weights is not None: + W = scipy.sparse.diags(self._w_quad_points, 0) + wg_vec = np.dot(W, g_vec) + wb_mat = np.dot(W, b_mat) + else: + wg_vec = g_vec + wb_mat = b_mat + + b_vec = b_mat.transpose().dot( wg_vec ) end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + end_time = time.time() + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - print('Computing A and B svds approximation ...') + logging.info('Computing B^T B matrix ...') + start_time = time.time() + bb_mat = b_mat.transpose().dot(wb_mat) + end_time = time.time() + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + + logging.info('Computing A and B svds approximation ...') start_time = time.time() bb_norm = scipy.sparse.linalg.svds(bb_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, @@ -251,42 +426,67 @@ def approx_chol(self): maxiter=300, return_singular_vectors=False) c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat - g_vec = self.points_z[:, 0] - b_vec = b_mat.transpose().dot( g_vec ) - end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) - print('Computing Z coordinates ...') + logging.info('Computing Z coordinates ...') start_time = time.time() z_vec = scipy.sparse.linalg.spsolve(c_mat, b_vec) assert not np.isnan(np.sum(z_vec)), "Singular matrix for approximation." - - print(type(z_vec)) end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) - + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - print('Computing differences ...') + logging.info('Computing differences ...') start_time = time.time() diff = b_mat.dot(z_vec) - g_vec - max_diff = np.max(diff) - print("Approximation error (max norm): {}".format(max_diff) ) + self.error = max_diff = np.max(diff) + logging.info("Approximation error (max norm): {}".format(max_diff) ) end_time = time.time() - print('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in {0} seconds.'.format(end_time - start_time)) # Construct Z-Surface - poles_z = z_vec.reshape(self.v_basis.size, self.u_basis.size).T - poles_z *= self.grid_surf.z_scale - poles_z += self.grid_surf.z_shift - surface_z = bs.Surface((self.u_basis, self.v_basis), poles_z[:,:,None]) - z_surf = bs.Z_Surface(self.grid_surf.quad[0:3], surface_z) + poles_z = z_vec.reshape(self._v_basis.size, self._u_basis.size).T + #poles_z *= self.grid_surf.z_scale + #poles_z += self.grid_surf.z_shift + surface_z = bs.Surface((self._u_basis, self._v_basis), poles_z[:,:,None]) + self.surface = bs.Z_Surface(self.quad[0:3], surface_z) + + return self.surface + + def _compute_uv_points(self): + """ + Map XY points to quad, remove points out of quad. + Results: self._uv_quad_points, self._z_quad_points, self._w_quad_points + :return: + """ + xy_shift = self.quad[1,:] + v_vec = self.quad[0, :] - self.quad[1, :] + u_vec = self.quad[2, :] - self.quad[1, :] + mat_uv_to_xy = np.column_stack((u_vec, v_vec)) + mat_xy_to_uv = la.inv(mat_uv_to_xy) + points_uv = np.dot((self._xy_points - xy_shift), mat_xy_to_uv.T) + + # remove points far from unit square + eps = 1.0e-15 + cut_min = np.array( [ -eps, -eps ]) + cut_max = np.array( [ 1+eps, 1+eps ]) + in_idx = np.all(np.logical_and(cut_min < points_uv, points_uv <= cut_max), axis=1) + points_uv = points_uv[in_idx] + + logging.debug("Number of points out of the grid domain: {}".format(len(points_uv) - np.sum(in_idx))) + + # snap to unit square + points_uv = np.maximum(points_uv, np.array([0.0, 0.0])) + self._uv_quad_points = np.minimum(points_uv, np.array([1.0, 1.0])) + self._z_quad_points = self._z_points[in_idx] + if self._weights is not None: + self._w_quad_points = self._weights[in_idx] + + - return z_surf - def build_ls_matrix(self): + def _build_ls_matrix(self): """ Construction of the matrix (B) of the system of linear algebraic equations for control points of the 2th order B-spline surface @@ -296,11 +496,11 @@ def build_ls_matrix(self): :param sparse: :return: """ - u_n_basf = self.u_basis.size - v_n_basf = self.v_basis.size - n_points = self.points_uv.shape[0] + u_n_basf = self._u_basis.size + v_n_basf = self._v_basis.size + n_points = self._uv_quad_points.shape[0] - n_uv_loc_nz = (self.u_basis.degree + 1) * (self.v_basis.degree + 1) + n_uv_loc_nz = (self._u_basis.degree + 1) * (self._v_basis.degree + 1) row = np.zeros(n_points * n_uv_loc_nz) col = np.zeros(n_points * n_uv_loc_nz) data = np.zeros(n_points * n_uv_loc_nz) @@ -310,11 +510,11 @@ def build_ls_matrix(self): interval = np.empty((n_points, 2)) for idx in range(0, n_points): - u, v = self.points_uv[idx, 0:2] - iu = self.u_basis.find_knot_interval(u) - iv = self.v_basis.find_knot_interval(v) - u_base_vec = self.u_basis.eval_base_vector(iu, u) - v_base_vec = self.v_basis.eval_base_vector(iv, v) + u, v = self._uv_quad_points[idx, 0:2] + iu = self._u_basis.find_knot_interval(u) + iv = self._v_basis.find_knot_interval(v) + u_base_vec = self._u_basis.eval_base_vector(iu, u) + v_base_vec = self._v_basis.eval_base_vector(iv, v) # Hard-coded Kronecker product (problem based) for n in range(0, 3): data[nnz_b + 3 * n:nnz_b + 3 * (n + 1)] = v_base_vec[n] * u_base_vec @@ -348,14 +548,14 @@ def _basis_in_q_points(self, basis): q_point[n] = up u_base_vec = basis.eval_base_vector(i, up) u_base_vec_diff = basis.eval_diff_base_vector(i, up) - point_val_outer[:, :, i] += self._weights[j] * np.outer(u_base_vec,u_base_vec) - d_point_val_outer[:, :, i] += self._weights[j] * np.outer(u_base_vec_diff,u_base_vec_diff) + point_val_outer[:, :, i] += self._q_weights[j] * np.outer(u_base_vec,u_base_vec) + d_point_val_outer[:, :, i] += self._q_weights[j] * np.outer(u_base_vec_diff,u_base_vec_diff) n += 1 return point_val_outer, d_point_val_outer,q_point - def build_sparse_reg_matrix(self): + def _build_sparse_reg_matrix(self): """ Construction of the regularization matrix (A) to decrease variation of the terrain B z = b ---> (B^T B + A)z = B^T b @@ -372,30 +572,30 @@ def build_sparse_reg_matrix(self): #c = quad[:, 1] - quad[:, 2] #d = quad[:, 0] - quad[:, 3] - u_n_basf = self.u_basis.size - v_n_basf = self.v_basis.size - u_n_inter = self.u_basis.n_intervals - v_n_inter = self.v_basis.n_intervals - n_uv_loc_nz = (self.u_basis.degree + 1) * (self.v_basis.degree + 1) + u_n_basf = self._u_basis.size + v_n_basf = self._v_basis.size + u_n_inter = self._u_basis.n_intervals + v_n_inter = self._v_basis.n_intervals + n_uv_loc_nz = (self._u_basis.degree + 1) * (self._v_basis.degree + 1) # TODO: use Gauss quadrature from scipy # in fact for general degrees we should use different quadrature for u and different for v - self._q_points = [0, (0.5 - 1 / math.sqrt(20)), (0.5 + 1 / math.sqrt(20)), 1] - self._weights = [1.0 / 6, 5.0 / 6, 5.0 / 6, 1.0 / 6] + self._q_points = [0, (0.5 - 1 / np.sqrt(20)), (0.5 + 1 / np.sqrt(20)), 1] + self._q_weights = [1.0 / 6, 5.0 / 6, 5.0 / 6, 1.0 / 6] nq_points = len(self._q_points) - u_val_outer, u_diff_val_outer, q_u_point = self._basis_in_q_points(self.u_basis) - v_val_outer, v_diff_val_outer, q_v_point = self._basis_in_q_points(self.v_basis) + u_val_outer, u_diff_val_outer, q_u_point = self._basis_in_q_points(self._u_basis) + v_val_outer, v_diff_val_outer, q_v_point = self._basis_in_q_points(self._v_basis) row_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) col_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) data_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) nnz_a = 0 - linsp = np.linspace(0,self.u_basis.degree,self.u_basis.degree+1) - llinsp = np.tile(linsp,self.u_basis.degree+1) + linsp = np.linspace(0,self._u_basis.degree,self._u_basis.degree+1) + llinsp = np.tile(linsp,self._u_basis.degree+1) - print("vnint: {} unint: {} nqp: {} prod: {}".format(v_n_inter, u_n_inter, nq_points, v_n_inter* u_n_inter* nq_points*nq_points)) + #print("vnint: {} unint: {} nqp: {} prod: {}".format(v_n_inter, u_n_inter, nq_points, v_n_inter* u_n_inter* nq_points*nq_points)) for i in range(v_n_inter): for l in range(u_n_inter): jac = 1.0 / u_n_inter / v_n_inter @@ -406,11 +606,11 @@ def build_sparse_reg_matrix(self): du_val_outer_loc = u_diff_val_outer[:, : , i] data_m[nnz_a:nnz_a + idx_range] = jac * ( np.kron(v_val_outer_loc, du_val_outer_loc) + np.kron(dv_val_outer_loc, u_val_outer_loc) ).ravel() - colv = np.repeat((i + linsp) * u_n_basf,self.u_basis.degree+1) + llinsp + colv = np.repeat((i + linsp) * u_n_basf,self._u_basis.degree+1) + llinsp col_m[nnz_a:nnz_a + idx_range] = np.repeat(colv,n_uv_loc_nz) row_m[nnz_a:nnz_a + idx_range] = np.tile(colv,n_uv_loc_nz) nnz_a += idx_range - print("Assembled") + #print("Assembled") mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), shape=(u_n_basf * v_n_basf, u_n_basf * v_n_basf)).tocsr() return mat_a \ No newline at end of file diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 2db43d3..25e1317 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -6,6 +6,9 @@ import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import bspline_plot as bs_plot +import time +import logging + def function_sin_cos(x): return math.sin(x[0] * 4) * math.cos(x[1] * 4) @@ -41,18 +44,15 @@ def eval_surface_on_grid(surface, shape=(50, 50)): return surface.eval_array(UV).reshape(nu, nv, 3) def plot_cmp(a_grid, b_grid): - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], a_grid[:, :, 2], color='green') - ax.plot_surface(b_grid[:, :, 0], b_grid[:, :, 1], b_grid[:, :, 2], color='red') + plt = bs_plot.Plotting() + plt.plot_surface_3d(a_grid[:, :, 0], a_grid[:, :, 1], a_grid[:, :, 2]) + plt.plot_surface_3d(b_grid[:, :, 0], b_grid[:, :, 1], b_grid[:, :, 2]) plt.show() diff = b_grid - a_grid - fig = plt.figure() - ax = fig.gca(projection='3d') - ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 0], color='red') - ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 1], color='green') - ax.plot_surface(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 2], color='blue') + plt.plot_surface_3d(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 0]) + plt.plot_surface_3d(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 1]) + plt.plot_surface_3d(a_grid[:, :, 0], a_grid[:, :, 1], diff[:, :, 2]) plt.show() def grid_cmp( a, b, tol): @@ -76,55 +76,79 @@ def grid_cmp( a, b, tol): class TestSurfaceApprox: # todo: numerical test - def plot_surf(self, surf): - fig = plt.figure() - ax = fig.gca(projection='3d') - bs_plot.plot_surface_3d(surf, ax) - plt.show() - def test_approx_func(self): + logging.basicConfig(level=logging.DEBUG) + xy_mat = np.array( [ [1.0, -1.0, 10 ], [1.0, 1.0, 20 ]]) # rotate left pi/4 and blow up 1.44 z_mat = np.array( [2.0, -10] ) - xyz_func = eval_func_on_grid(function_sin_cos, xy_mat[:2,:2], xy_mat[:, 2], z_mat) - print("Compare: Func - GridSurface.transform") - points = bs.make_function_grid(function_sin_cos, 200, 200) + # print("Compare: Func - GridSurface.transform") + # points = bs.make_function_grid(function_sin_cos, 200, 200) + # gs = bs.GridSurface(points.reshape(-1, 3)) + # gs.transform(xy_mat, z_mat) + # xyz_grid = eval_z_surface_on_grid(gs, xy_mat[:2,:2], xy_mat[:, 2]) + # grid_cmp(xyz_func, xyz_grid, 0.02) + # + # print("Compare: Func - GridSurface.transform.z_surf") + # xyz_grid = eval_z_surface_on_grid(gs.z_surface, xy_mat[:2, :2], xy_mat[:, 2]) + # xyz_grid[:,:,2] *= z_mat[0] + # xyz_grid[:, :, 2] += z_mat[1] + # grid_cmp(xyz_func, xyz_grid, 0.02) + + print("Compare: Func - GridSurface.approx") + points = bs.make_function_grid(function_sin_cos, 50, 50) gs = bs.GridSurface(points.reshape(-1, 3)) gs.transform(xy_mat, z_mat) - xyz_grid = eval_z_surface_on_grid(gs, xy_mat[:2,:2], xy_mat[:, 2]) + approx = bs_approx.SurfaceApprox.approx_from_grid_surface(gs) + surface = approx.compute_approximation() + xyz_grid = eval_z_surface_on_grid(surface, xy_mat[:2, :2], xy_mat[:, 2]) + print("Approx error: ", approx.error) grid_cmp(xyz_func, xyz_grid, 0.02) - print("Compare: Func - GridSurface.transform.z_surf") - xyz_grid = eval_z_surface_on_grid(gs.z_surface, xy_mat[:2, :2], xy_mat[:, 2]) - xyz_grid[:,:,2] *= z_mat[0] - xyz_grid[:, :, 2] += z_mat[1] - grid_cmp(xyz_func, xyz_grid, 0.02) - - print("Compare: Func - GridSurface.transform.Z_surf_approx") - #z_surf = bs_approx.surface_from_grid(gs, (16, 24)) - z_surf = bs_approx.surface_from_grid(gs, (100, 100)) - #z_surf.transform(xy_mat, z_mat) - xyz_grid = eval_z_surface_on_grid(z_surf, xy_mat[:2,:2], xy_mat[:, 2]) - grid_cmp(xyz_func, xyz_grid, 0.02) - - print("Compare: Func - GridSurface.transform.Z_surf_approx.full_surface") - xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) - grid_cmp(xyz_func, xyz_grid, 0.01) - - - - - - - #points = bs.make_function_grid(function_sin_cos, 20, 30) - #points = np.dot(points, mat.T) + np.array([10, 20, -5.0]) - #gs = bs.GridSurface(points.reshape(-1, 3)) - #z_surf = bs_approx.surface_from_grid(gs, (3,4) ) - #self.plot_surf(z_surf) - - + print("Compare: Func - points.approx") + np.random.seed(seed=123) + uv = np.random.rand(1000,2) + xy = xy_mat[:2,:2].dot(uv.T).T + xy_mat[:, 2] + z = np.array( [function_sin_cos([u, v]) for u, v in uv] ) + xyz = np.concatenate((xy, z[:, None]), axis=1) + approx = bs_approx.SurfaceApprox(xyz) + quad = approx.compute_default_quad() + surface = approx.compute_approximation() + nuv = approx.nuv + ref_quad = np.array([ [-1 , 1], [0,0], [1, 1], [0, 2] ]) + ref_quad += np.array([10, 20]) + assert np.allclose(ref_quad, quad, atol=1e-2) + + + surface.transform(xy_mat = None, z_mat=z_mat) + nu, nv = 50, 50 + uv_probe = gen_uv_grid(nu, nv) + uv_probe = (0.9*uv_probe + 0.05) + xy_probe = xy_mat[:2,:2].dot(uv_probe.T).T + xy_mat[:, 2] + z_func = np.array( [z_mat[0] * function_sin_cos([u, v]) + z_mat[1] for u, v in uv_probe] ) + xyz_func = np.concatenate((xy_probe, z_func[:, None]), axis=1).reshape(nu, nv, 3) + xyz_approx = surface.eval_xy_array(xy_probe).reshape(nu, nv, 3) + print("Approx error: ", approx.error) + grid_cmp(xyz_func, xyz_approx, 0.02) + + # approx = bs_approx.SurfaceApprox(xyz) + # surface = approx.compute_approximation() + # + # + # plt = bs_plot.Plotting() + # plt.plot_surface_3d(gs) + # plt.plot_surface_3d(surface) + # plt.scatter_3d(xyz[:,0], xyz[:,1], xyz[:,2]) + # plt.show() + + # xyz_grid = eval_z_surface_on_grid(z_surf, xy_mat[:2,:2], xy_mat[:, 2]) + # grid_cmp(xyz_func, xyz_grid, 0.02) + # + # print("Compare: Func - GridSurface.transform.Z_surf_approx.full_surface") + # xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) + # grid_cmp(xyz_func, xyz_grid, 0.01) @@ -145,7 +169,30 @@ def test_surface_approx(self): pass - +class TestBoundingBox: + def test_hull_and_box(self): + points = np.random.randn(1000000,2) + + print("hull") + start = time.perf_counter() + for i in range(1): + hull = bs_approx.convex_hull_2d(points) + end = time.perf_counter() + print("conver hull time: ", end - start) + + start = time.perf_counter() + for i in range(10): + quad = bs_approx.min_bounding_rect(hull) + end = time.perf_counter() + print("quad time: ", end - start) + return + + plt = bs_plot.Plotting() + plt.scatter_2d(points[:,0], points[:,1]) + plt.plot_2d(hull[:, 0], hull[:, 1]) + box_lines = np.concatenate((quad, quad[0:1,:]), axis=0) + plt.plot_2d(box_lines[:, 0], box_lines[:, 1]) + plt.show() class TestCurveApprox: From e1f06adb6e6ec0906b6be4ddff050d897dd83091 Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 4 Dec 2017 11:22:10 +0100 Subject: [PATCH 47/67] working version, needs some fix --- src/isec_surf_surf.py | 223 +++++++++++++++++++++++++++--------------- tests/test_isec.py | 8 +- 2 files changed, 151 insertions(+), 80 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 2cd5834..6a2ad76 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -13,11 +13,13 @@ class IsecSurfSurf: - def __init__(self, surf1, surf2): + def __init__(self, surf1, surf2, nt=2,nit=10): self.surf1 = surf1 self.surf2 = surf2 self.tree1 = self.bounding_boxes(self.surf1) self.tree2 = self.bounding_boxes(self.surf2) + self.nt = nt + self.nit = nit @staticmethod def bounding_boxes(surf): @@ -46,116 +48,183 @@ def bounding_boxes(surf): - def get_intersection(surf1,surf2,nt): + @staticmethod + def get_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit): # nt - number of threads (integer) #X,Xs, u_n_intervs,v_n_intervs,u_knots,v_knots, #us_n_intervs,vs_n_intervs,us_knots,vs_knots,isec,n_isec, nit,nt ) # computes intersection of BSpline patch with BSpline thread - def patch_patch_intersection( uvt, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l): + + + + + def _patch_patch_intersection( surf1, surf2,uvt, ui,vi,u2i,v2i, nit ,m,o,k,l): #returns coordinetes of the intersection of the patches (if exist), one #parameter must be fixed - pt = np.zeros(3,1) + pt = np.zeros([3,1]) conv =0 tol = 1e-6; # in x,y,z tol2 = 1e-4; # in u,v + # nt = self.nt - if np.length(u2i) == 1: - u2f = surf2.u_basis._eval_vector_deg_2(m,u2i) + Xs = surf2.poles[m:m + 3, o:o + 3, 0] + X = surf1.poles[k:k + 3, l:l + 3, 0] - if np.length(v2i) == 1: - v2f = surf2.v_basis._eval_vector_deg_2(o,v2i) + if u2i.shape[0] == 1: + u2f = surf2.u_basis.eval_vector(m,u2i) # eval_vector(self, i_base, t): + + if v2i.shape[0] == 1: + v2f = surf2.v_basis.eval_vector(o,v2i) for i in range(nit): - uf = surf1.u_basis._eval_vector_deg_2(k,uvt(1)) - vf = surf1.v_basis._eval_vector_deg_2(l,uvt(2)) - ufd = surf1.u_basis._eval_diff_vector_deg_2(k,uvt(1)) - vfd = surf1.v_basis._eval_diff_vector_deg_2(l,uvt(2)) + uf = surf1.u_basis.eval_vector(k,uvt[0]) + vf = surf1.v_basis.eval_vector(l,uvt[1]) + ufd = surf1.u_basis.eval_diff_vector(k,uvt[0]) + vfd = surf1.v_basis.eval_diff_vector(l,uvt[1]) - if np.length(u2i) == 1: - v2f = surf2.v_basis._eval_vector_deg_2(o,uvt(3)) - v2fd = surf2.v_basis._eval_diff_vector_deg_2(o,uvt(3)) - dXYZp2 = (kron(v2fd',u2f')*Xs)'; + if u2i.shape[0] == 1: + v2f = surf2.v_basis.eval_vector(o,uvt[2]) + v2fd = surf2.v_basis.eval_diff_vector(o,uvt[2]) + dXYZp2 = la.multi_dot([u2f,Xs,v2fd]) + #(kron(v2fd',u2f')*Xs)'; + + if v2i.shape[0] == 1: + u2f = surf2.u_basis.eval_vector(m,uvt[2]) #splinebasevec(us_knots,uvt(3),0,m) + u2fd = surf2.u_basis.eval_diff_vector(m,uvt[2]) #splinebasevec(us_knots,uvt(3),1,m) + dXYZp2 = la.multi_dot([u2fd, Xs, v2f]) - if np.length(v2i) == 1: - u2f = splinebasevec(us_knots,uvt(3),0,m) - u2fd = splinebasevec(us_knots,uvt(3),1,m) - dXYZp2 = (kron(v2f',u2fd')*Xs)'; - dXYZu1 = (kron(vf',ufd')*X)' - dXYZv1 = (kron(vfd',uf')*X)' - J = [dXYZu1, dXYZv1, -dXYZp2] - deltaXYZ = (kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)' - uvt = uvt- J\deltaXYZ - test,uvt = rangetest(uvt,ui,vi,u2i,v2i,0.0) + dXYZu1 = la.multi_dot([ufd, X, vf]) #(kron(vf',ufd')*X)' + dXYZv1 = la.multi_dot([uf,X,vfd])# (kron(vfd',uf')*X)' + J = np.array([[dXYZu1.transpose()], [dXYZv1.transpose()], [-dXYZp2.transpose()]]) + + print(dXYZu1) + print(dXYZv1) + print(-dXYZp2) + + + print(J) + return + deltaXYZ = la.multi_dot([uf,X,vf]) - la.multi_dot([u2f,Xs,vf]) # kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)' + uvt = uvt - la.solve(J,deltaXYZ) #uvt = uvt- J\deltaXYZ + test,uvt = _rangetest(uvt,ui,vi,u2i,v2i,0.0) + + test,uvt = _rangetest(uvt,ui,vi,u2i,v2i,tol2); + if test == 1: + if np.length(u2i) == 1: + surf2_pos = surf2.eval(u2i,uvt[2]) + if np.length(v2i) == 1: + surf2_pos = surf2.eval(uvt[2],v2i) + + dist = la.norm(surf1.eval(uvt[0],uvt[1]) - surf2_pos) # may be faster, indices of patches are known - test,uvt = rangetest(uvt,ui,vi,u2i,v2i,tol2); - if test == 1 - dist = get_delta(u_knots, v_knots,us_knots, vs_knots,uvt,u2i,v2i,k,l,m,o,X,Xs); if test == 1: if dist <= tol: - pt =kron(vf',uf')*X + pt = la.multi_dot(uf,X,vf) #kron(vf',uf')*X conv =1 else: uvt = np.zeros(3,1) - return uvu2v2, ptl, conv - - - n_points = 0 - ninter = np.zeros(surf1.u_basis.n_intervals,surf1.v_basis.n_intervals) - for k in range(surf1.u_basis.n_intervals): - us1 = surf1.u_basis.knots[k + 1] - ue1 = surf1.u_basis.knots[k + 2] - u1_c =(us1 + ue1)/2 - ui = np.array([us1, ue1]) - for l in range(surf1.v_basis.n_intervals): - vs1 = surf1.v_basis.knots(l+1) - ve1 = surf1.v_basis.knots(l+2) - v1_c = (vs1 + ve1)/2 - vi = np.array([vs1, ve1]) - s=0 - if n_isec(k,l) ~= 0 - for p =1: n_isec(k,l) - m = ceil(isec(k,l,p) / surf2.u_basis.n_intervals) - o = isec(k,l,p) - (m-1)*surf2.u_basis.n_intervals + return uvt, pt, conv + + def _rangetest(uvt, ui, vi, u2i, v2i, tol): + # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + + test = 0 + + du = np.array([uvt[0] - ui[0], ui[1] - uvt[0]]) + dv = np.array([uvt[1] - vi[0], vi[1] - uvt[1]]) + + if np.length(v2i) == 1: + d2p = [uvt[2] - u2i[0], u2i[1] - uvt[2]] + pi = u2i + + if np.length(u2i) == 1: + d2p = np.array([uvt[2] - v2i[0], v2i[1] - uvt[2]]) + pi = v2i + + for i in range(0,2): + if (du[i] < -tol): + uvt[0] = ui[i] + + for i in range(0,2): + if (dv[i] < -tol): + uvt[1] = vi[i] + + for i in range(0,2): + if (d2p[i] < -tol): + uvt[2] = pi[i] + + + if np.logical_and(uvt[0] >= ui[0],uvt[0] <= ui[1]): + if np.logical_and(uvt[1] >= vi[0], uvt[1] <= vi[1]): + if np.logical_and(uvt[2] >= pi[0], uvt[2] <= pi[1]): + test = 1 + + return uvt, test + + + + def _compute_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit ): + n_points = 0 + point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) + ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) + for k in range(surf1.u_basis.n_intervals): + us1 = surf1.u_basis.knots[k + 1] + ue1 = surf1.u_basis.knots[k + 2] + u1_c =(us1 + ue1)/2 + ui = np.array([us1, ue1]) + for l in range(surf1.v_basis.n_intervals): + vs1 = surf1.v_basis.knots[l+1] + ve1 = surf1.v_basis.knots[l+2] + v1_c = (vs1 + ve1)/2 + vi = np.array([vs1, ve1]) + s=0 + box_id = l + surf1.u_basis.n_intervals * k + n_isec = tree1.find_box(box2[box_id]) + + #if n_isec.shape[1] > 0: + for p in n_isec: + m = int(np.ceil(p / surf2.u_basis.n_intervals)) + o = int(p - (m-1)*surf2.u_basis.n_intervals) sp_i = (m-1)*(surf2.u_basis.n_intervals) + o # v2 fixed - u2i = [surf2.u_basis.knots(m+1), surf2.u_basis.knots(m+2)] - v_knot = np.linspace(surf2.v_basis.knots(o+1),surf2.v_basis(o+2),nt) + u2i = np.array([surf2.u_basis.knots[m+1], surf2.u_basis.knots[m+2]]) + v_knot = np.linspace(surf2.v_basis.knots[o+1],surf2.v_basis.knots[o+2],nt) for h in range(nt): #=1:length(v_knot) u2_c = (u2i[0] + u2i[1])/2 - v2i = v_knot[h] - uvu2v2 = [u1_c, v1_c, u2_c] # initial condition - uvu2v2,ptl,conv = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); - if conv ~= 0 - s = s+1; - n_points = n_points +1; - point(n_points,:) = [uvu2v2',v_knot(h),ptl(1),ptl(2),ptl(3),k,l,m,o]; - - # u2 fixed - v2i = [vs_knots(o+2) vs_knots(o+3)]; - u_knot = linspace(us_knots(m+2),us_knots(m+3),nt); - for h =1:length(u_knot) - v2_c = (vs_knots(o+2) + vs_knots(o+3))/2; - u2i = u_knot(h); - uvu2v2 = [u1_c;v1_c;v2_c]; % initial condition - [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l); - if conv ~= 0 - s = s+1; - n_points = n_points +1; - point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o]; - end - end - ninter(k,l) = s; - - return point , n_points,ninter + v2i = np.array([v_knot[h]]) + uvu2v2 = np.array([u1_c, v1_c, u2_c]) # initial condition + uvu2v2,ptl,conv = _patch_patch_intersection( surf1, surf2,uvu2v2, ui,vi,u2i,v2i, nit ,m,o,k,l) + if np.not_equal(conv, 0): + s = s+1 + n_points = n_points +1 + point[n_points,:] = [uvu2v2,v_knot[h],ptl(1),ptl(2),ptl(3),k,l,m,o] + # u2 fixed + #v2i = [vs_knots(o+2) vs_knots(o+3)] + #u_knot = linspace(us_knots(m+2),us_knots(m+3),nt) + #for h =1:length(u_knot) + # v2_c = (vs_knots(o+2) + vs_knots(o+3))/2 + # u2i = u_knot(h) + # uvu2v2 = [u1_c,v1_c,v2_c]; % initial condition + # [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l) + # if conv ~= 0 + # s = s+1 + # n_points = n_points +1 + # point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o] + + ninter[k,l] = s + + return point, n_points, ninter + + point, n_points, ninter = _compute_intersection(surf1, surf2, tree1, tree2, box1, box2, nt,nit) # def get_intersections(tree1,tree2): diff --git a/tests/test_isec.py b/tests/test_isec.py index 5818bb9..dd705a2 100644 --- a/tests/test_isec.py +++ b/tests/test_isec.py @@ -40,7 +40,7 @@ def function2(x): surface_extrude2 = bs.Surface((u_basis, v_basis), poles2) myplot.plot_surface_3d(surface_extrude2, poles=False) - myplot.show() + #myplot.show() # view return surface_extrude, surface_extrude2 @@ -72,8 +72,10 @@ def test_isec(self): surf1, surf2 = self.plot_extrude() isec = iss.IsecSurfSurf(surf1, surf2) - boxes1, tree1 = isec.bounding_boxes(surf1) - boxes2, tree2 = isec.bounding_boxes(surf2) + box1, tree1 = isec.bounding_boxes(surf1) + box2, tree2 = isec.bounding_boxes(surf2) + isec.get_intersection(surf1,surf2,tree1,tree2,box1,box2,isec.nt,isec.nit) # surf1,surf2,tree1,tree2 + #print(tree1.find_box(boxes2[0])) #print(surf1.poles[:,:,1]) #print(surf1.u_basis.n_intervals) From 196a8f587d9f94f89372d41154e074eba2f6b28d Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Tue, 5 Dec 2017 22:19:58 +0100 Subject: [PATCH 48/67] bspline Z_Surface changes --- src/bspline.py | 28 ++++++++++++++++++--- tests/test_bspline.py | 58 ++++++++++++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index 7dce3c9..136fe7e 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -14,7 +14,7 @@ import numpy as np import numpy.linalg as la - +import copy __author__ = 'Jan Brezina , Jiri Hnidek , Jiri Kopal ' @@ -493,6 +493,12 @@ def eval(self, u, v): #print("val: {}".format(value.shape)) return np.inner( value, v_base_vec) + def deep_copy(self): + u_basis = copy.copy(self.u_basis) + v_basis = copy.copy(self.v_basis) + poles = copy.copy(self.poles) + return Surface( ( u_basis, v_basis), poles) + def eval_array(self, uv_points): """ Evaluate in array of uv-points. @@ -537,6 +543,7 @@ def __init__(self, xy_quad, z_surface): self.v_basis = z_surface.v_basis # Basis for UV directions. + self.orig_quad = xy_quad self.quad = None # Boundary quadrilateral. @@ -565,7 +572,7 @@ def make_full_surface(self): return Surface(basis, poles) - def reset_transform(self, xy_quad): + def reset_transform(self, xy_quad=None): """ Set XY transform according to given domain quadrilateral (or triangle for linear mapping case). :param xy_quad: np array N x 2 @@ -573,9 +580,13 @@ def reset_transform(self, xy_quad): Four points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0), (1,1) Three points giving XY coordinates for the uv corners: (0,1), (0,0), (1,0) Linear case is also detected for the four points. + + If no xy_quad is given, we reset to the quad used in constructor. :return: None """ # Build envelope quadrilateral polygon in XY plane. + if xy_quad is None: + xy_quad = self.orig_quad self.quad = np.array(xy_quad, dtype=float) assert self.quad.shape[0] in [3, 4], "Three or four points must be given." assert self.quad.shape[1] == 2 @@ -632,16 +643,25 @@ def transform(self, xy_mat, z_mat = None ): def apply_z_transform(self): """ - Apply current Z transform to the poles. Reset the Z transform. + Make copy of the bs.Surface for Z coordinates and apply current Z transform to the poles of the copy. + Reset the Z transform. :return: """ if self._have_z_mat: + self.z_surface = self.z_surface.deep_copy() self.z_surface.poles *= self.z_mat[0] self.z_surface.poles += self.z_mat[1] self.z_mat = np.array([1.0, 0.0]) self._have_z_mat = False + def get_copy(self): + """ + Returns a copy of the Z_surface with own Z and XY transforms, but shared + bs.Surface for Z coordinates (Z poles). + :return: A copy of the Z_Surface object. + """ + return copy.copy(self) """ @@ -750,7 +770,7 @@ def center(self): Evaluate surface in center of UV square. :return: (X, Y, Z) """ - return self.eval_array( np.array([ [0.5, 0.5] ])) + return self.eval_array( np.array([ [0.5, 0.5] ]))[0] def aabb(self): diff --git a/tests/test_bspline.py b/tests/test_bspline.py index 0d75b52..d35c50b 100644 --- a/tests/test_bspline.py +++ b/tests/test_bspline.py @@ -288,20 +288,21 @@ class TestZ_Surface: # TODO: Compute max norm of the difference of two surfaces and assert that it is cose to zero. + def make_z_surf(self, func, quad): + poles = bs.make_function_grid(func, 4, 5) + u_basis = bs.SplineBasis.make_equidistant(2, 2) + v_basis = bs.SplineBasis.make_equidistant(2, 3) + surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) + return bs.Z_Surface(quad, surface_func) def plot_function_uv(self): # function surface def function(x): return math.sin(x[0]*4) * math.cos(x[1]*4) - poles = bs.make_function_grid(function, 4, 5) - u_basis = bs.SplineBasis.make_equidistant(2, 2) - v_basis = bs.SplineBasis.make_equidistant(2, 3) - surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) - - #quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) quad = np.array([[0, 0], [1, 0], [1, 1], [0, 1]]) - z_surf = bs.Z_Surface(quad, surface_func) + z_surf = self.make_z_surf(function, quad) + z_surf.transform(None, np.array([2.0, 0]) ) full_surf = z_surf.make_full_surface() z_surf.transform(None, np.array([1.0, 0.1]) ) @@ -311,7 +312,7 @@ def function(x): plotting.show() def test_eval_uv(self): - self.plot_function_uv() + #self.plot_function_uv() pass def test_aabb(self): @@ -319,13 +320,9 @@ def test_aabb(self): def function(x): return x[0] * (x[1] + 1.0) + 3.0 - poles = bs.make_function_grid(function, 4, 5) - u_basis = bs.SplineBasis.make_equidistant(2, 2) - v_basis = bs.SplineBasis.make_equidistant(2, 3) - surface_func = bs.Surface( (u_basis, v_basis), poles[:,:, [2] ]) - quad = np.array( [ [0, 0], [0, 0.5], [1, 0.1], [1.1, 1.1] ] ) - z_surf = bs.Z_Surface(quad, surface_func) + z_surf = self.make_z_surf(function, quad) + box = z_surf.aabb() assert np.allclose(box, np.array([[0, 0, 3], [1.1, 1.1, 5]])) @@ -333,6 +330,38 @@ def function(x): box = z_surf.aabb() assert np.allclose(box, np.array([[0, 0, 7], [1.1, 1.1, 11]])) + def test_reset_transform(self): + def function(x): + return math.sin(x[0]*4) * math.cos(x[1]*4) + + quad = np.array([[1., 3.5], [1., 2.], [2., 2.2], [2, 3.7]]) + z_surf = self.make_z_surf(function, quad) + xy_mat = np.array([[2, 1, -1],[1, 2, -2]]) + z_mat = np.array([2, 1]) + z_surf.transform(xy_mat, z_mat) + assert np.allclose( z_surf.quad[0], np.array([4.5, 6]) ) + z_surf.reset_transform() + assert np.allclose(z_surf.quad, quad) + + def test_get_copy(self): + def function(x): + return math.sin(x[0]*4) * math.cos(x[1]*4) + + quad = np.array([[1., 3.5], [1., 2.], [2., 2.2], [2, 3.7]]) + a_surf = self.make_z_surf(function, quad) + b_surf = a_surf.get_copy() + b_surf.transform(None, np.array([2, 1])) + assert np.allclose(a_surf.center()[0:1], b_surf.center()[0:1]) + assert np.isclose(2 * a_surf.center()[2] + 1, b_surf.center()[2]) + za = a_surf.center()[2] + zb = b_surf.center()[2] + + b_surf.apply_z_transform() + assert np.allclose(a_surf.center()[0:1], b_surf.center()[0:1]) + assert np.isclose(zb, b_surf.center()[2]) + assert np.isclose(za, a_surf.center()[2]) + + class TestPointGrid: @@ -396,6 +425,7 @@ def check_surface(self, surf, xy_mat, xy_shift, z_mat): self.grid_cmp(XYZ_func_eval, XYZ_grid_eval, tol) self.grid_cmp(XYZ_func_eval, XYZ_surf_eval, tol) + def test_grid_surface(self): xy_mat = np.array([ [1.0, 0.0], [0.0, 1.0] ]) xy_shift = np.array([0.0, 0.0 ]) From 89ea4eb6f3e267e56739ad56f378f32fca56133f Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Tue, 5 Dec 2017 22:46:00 +0100 Subject: [PATCH 49/67] Changes in SurfaceApprox - public nuv estimate - transformation of quad before calculation --- src/bspline_approx.py | 76 ++++++++++++++++++++++++++++++----------- tests/test_bs_approx.py | 18 ++++++++-- 2 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index ea68353..fd3255a 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -261,6 +261,12 @@ def approx_from_file(filename): @staticmethod def approx_from_grid_surface(grid_surface): + """ + Approximation from a GrodSurface object. Use grid of Z coords in + XY pozitions of poles. + :param grid_surface: GridSurface. + :return: + """ u_basis, v_basis = grid_surface.u_basis, grid_surface.v_basis u_coord = u_basis.make_linear_poles() @@ -328,26 +334,41 @@ def compute_default_quad(self): self.quad = min_bounding_rect(hull) return self.quad - def _compute_default_nuv(self, n_points): + + def transformed_quad(self, xy_mat): """ - Default nu and nv for given number of points inside of quad. - :return: (nu, nv) + Return actual quad transformed by given transform matrix. + Boudary quadrilateral of the approximation is not touched. + :param xy_mat: np array, 2 rows 3 cols, last column is xy shift + :return: transformed quad as 4x2 numpy array or None """ - assert(self.quad is not None) + if self.quad is None: + return None + assert xy_mat.shape == (2, 3) - dv = la.norm(self.quad[0, :] - self.quad[1, :]) - du = la.norm(self.quad[2, :] - self.quad[1, :]) + # transform quad + return np.dot(self.quad, xy_mat[0:2, 0:2].T) + xy_mat[0:2, 2] - # try to make number of unknowns less then number of remaining points - # +1 to improve determination - nv = np.sqrt( n_points * dv / du ) - nu = nv * du / dv - nuv = np.array( [np.floor(nu / 3), np.floor(nv / 3)] ) - self._degree - self.nuv = np.maximum(1, nuv) + + def compute_default_nuv(self): + """ + Compute default quad (if not set) filter points in the quad and estimate + nuv from their count. Set self.nuv + :return: nuv = (nu, nv) + """ + if self.quad is None: + self.quad = self.compute_default_quad() + self._compute_uv_points() + + nuv = self._compute_default_nuv(len(self._z_quad_points)) + self.nuv = nuv.astype(int) + if self.nuv[0] < 1 or self.nuv[1] < 1: + raise Exception("Two few points, {}, to make approximation, degree: {}".format(self._n_points, self._degree)) return self.nuv + def compute_approximation(self, **kwargs): """ Compute approximation of the point set (given to constructor). @@ -360,16 +381,10 @@ def compute_approximation(self, **kwargs): logging.info('Transforming points to parametric space ...') start_time = time.time() - if self.quad is None: - self.quad = self.compute_default_quad() - self._compute_uv_points() - + self.compute_default_quad() if self.nuv is None: - self.nuv = self._compute_default_nuv(len(self._z_quad_points)) - self.nuv = self.nuv.astype(int) - if self.nuv[0] < 1 or self.nuv[1] < 1: - raise Exception("Two few points, {}, to make approximation, degree: {}".format(self._n_points, self._degree)) + self.compute_default_nuv() logging.info("Using {} x {} B-spline approximation.".format(self.nuv[0], self.nuv[1])) self._u_basis = bs.SplineBasis.make_equidistant(2, self.nuv[0]) @@ -452,6 +467,27 @@ def compute_approximation(self, **kwargs): return self.surface + + def _compute_default_nuv(self, n_points): + """ + Default nu and nv for given number of points inside of quad. + :return: (nu, nv) + """ + assert(self.quad is not None) + + dv = la.norm(self.quad[0, :] - self.quad[1, :]) + du = la.norm(self.quad[2, :] - self.quad[1, :]) + + # try to make number of unknowns less then number of remaining points + # +1 to improve determination + nv = np.sqrt( n_points * dv / du ) + nu = nv * du / dv + nuv = np.array( [np.floor(nu / 3), np.floor(nv / 3)] ) - self._degree + self.nuv = np.maximum(1, nuv) + return self.nuv + + + def _compute_uv_points(self): """ Map XY points to quad, remove points out of quad. diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 25e1317..114e4ac 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -115,13 +115,16 @@ def test_approx_func(self): xyz = np.concatenate((xy, z[:, None]), axis=1) approx = bs_approx.SurfaceApprox(xyz) quad = approx.compute_default_quad() - surface = approx.compute_approximation() + nuv = approx.nuv ref_quad = np.array([ [-1 , 1], [0,0], [1, 1], [0, 2] ]) ref_quad += np.array([10, 20]) assert np.allclose(ref_quad, quad, atol=1e-2) + nuv = approx.compute_default_nuv() + assert np.allclose( np.array([8, 8]), nuv) + surface = approx.compute_approximation() surface.transform(xy_mat = None, z_mat=z_mat) nu, nv = 50, 50 uv_probe = gen_uv_grid(nu, nv) @@ -150,9 +153,18 @@ def test_approx_func(self): # xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) # grid_cmp(xyz_func, xyz_grid, 0.01) + def test_transformed_quad(self): + xy_mat = np.array( [ [1.0, -1.0, 0 ], [1.0, 1.0, 0 ]]) # rotate left pi/4 and blow up 1.44 + np.random.seed(seed=123) + uv = np.random.rand(1000,2) + xy = xy_mat[:2,:2].dot(uv.T).T + xy_mat[:, 2] + z = np.array( [function_sin_cos([u, v]) for u, v in uv] ) + xyz = np.concatenate((xy, z[:, None]), axis=1) + approx = bs_approx.SurfaceApprox(xyz) + approx.quad = np.array([ [-1 , 1], [0,0], [1, 1], [0, 2] ]) - - + xy_mat = np.array([[2, 1, -1],[1, 2, -2]]) + assert np.allclose( np.array([ [-2 , -1], [-1,-2], [2, 1], [1, 2] ]), approx.transformed_quad(xy_mat)) def plot_approx_transformed_grid(self): From b107109393c7d25d363ac9fba2a17e3fb03baba8 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 6 Dec 2017 18:52:22 +0100 Subject: [PATCH 50/67] Z_Surface.transform conversion to array --- src/bspline.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/bspline.py b/src/bspline.py index 136fe7e..b602699 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -626,6 +626,7 @@ def transform(self, xy_mat, z_mat = None ): :return: None """ if xy_mat is not None: + xy_mat = np.array(xy_mat) assert xy_mat.shape == (2, 3) self._mat_uv_to_xy = xy_mat[0:2,0:2].dot( self._mat_uv_to_xy ) self._xy_shift = xy_mat[0:2,0:2].dot( self._xy_shift ) + xy_mat[0:2, 2] @@ -636,11 +637,19 @@ def transform(self, xy_mat, z_mat = None ): # apply z-transfrom directly to the poles if z_mat is not None: + z_mat = np.array(z_mat) assert z_mat.shape == (2,) self.z_mat[0] *= z_mat[0] self.z_mat[1] = z_mat[0] * self.z_mat[1] + z_mat[1] self._have_z_mat = True + def get_xy_matrix(self): + """ + Return xy_mat of curent XY tranasform. + :return: + """ + return np.concatenate((self._mat_uv_to_xy, self._xy_shift[:, None]), axis=1) + def apply_z_transform(self): """ Make copy of the bs.Surface for Z coordinates and apply current Z transform to the poles of the copy. From e1d4e0cac19853ea117ca4f5c930a48ed3c03d5b Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 13 Dec 2017 14:09:33 +0100 Subject: [PATCH 51/67] Fix assembly of regularization matrix --- src/bspline_approx.py | 41 +++++++++++++++++++++++++---------------- tests/test_bs_approx.py | 6 ++++++ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index fd3255a..96e4ee3 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -622,29 +622,38 @@ def _build_sparse_reg_matrix(self): u_val_outer, u_diff_val_outer, q_u_point = self._basis_in_q_points(self._u_basis) v_val_outer, v_diff_val_outer, q_v_point = self._basis_in_q_points(self._v_basis) + # xy_outer shape is (3, 3, n_inter) row_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) col_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) data_m = np.zeros((v_n_inter * u_n_inter * n_uv_loc_nz * n_uv_loc_nz)) nnz_a = 0 - linsp = np.linspace(0,self._u_basis.degree,self._u_basis.degree+1) - llinsp = np.tile(linsp,self._u_basis.degree+1) - + #linsp = np.linspace(0, self._u_basis.degree, self._u_basis.degree+1) + #llinsp = np.tile(linsp, self._u_basis.degree+1) + #np.repeat((iv + linsp) * u_n_basf, self._u_basis.degree + 1) + llinsp + i_local = np.arange(self._u_basis.degree+1, dtype=int) + iuv_local = (u_n_basf * i_local[:, None] + i_local[None,:]).ravel() # 0,1,2, N+[0,1,2], 2*N+[0,1,2] #print("vnint: {} unint: {} nqp: {} prod: {}".format(v_n_inter, u_n_inter, nq_points, v_n_inter* u_n_inter* nq_points*nq_points)) - for i in range(v_n_inter): - for l in range(u_n_inter): - jac = 1.0 / u_n_inter / v_n_inter - idx_range = n_uv_loc_nz * n_uv_loc_nz - v_val_outer_loc = v_val_outer[:, :, i] - dv_val_outer_loc = v_diff_val_outer[:, : , i] - u_val_outer_loc = u_val_outer[:, :, i] - du_val_outer_loc = u_diff_val_outer[:, : , i] - data_m[nnz_a:nnz_a + idx_range] = jac * ( np.kron(v_val_outer_loc, du_val_outer_loc) - + np.kron(dv_val_outer_loc, u_val_outer_loc) ).ravel() - colv = np.repeat((i + linsp) * u_n_basf,self._u_basis.degree+1) + llinsp - col_m[nnz_a:nnz_a + idx_range] = np.repeat(colv,n_uv_loc_nz) - row_m[nnz_a:nnz_a + idx_range] = np.tile(colv,n_uv_loc_nz) + jac = 1.0 / u_n_inter / v_n_inter + idx_range = n_uv_loc_nz * n_uv_loc_nz # 9 * 9 = 81 NZ per single bspline square + for iv in range(v_n_inter): + v_val_outer_loc = v_val_outer[:, :, iv] + dv_val_outer_loc = v_diff_val_outer[:, :, iv] + + for iu in range(u_n_inter): + u_val_outer_loc = u_val_outer[:, :, iu] + du_val_outer_loc = u_diff_val_outer[:, : , iu] + # xy_outer_loc have shape 3x3 + + v_du = np.kron(v_val_outer_loc, du_val_outer_loc) + dv_u = np.kron(dv_val_outer_loc, u_val_outer_loc) + data_m[nnz_a:nnz_a + idx_range] = jac * ( v_du + dv_u).ravel() # 9x9 values + + iuv = iu + iv * u_n_basf + colv = iuv + iuv_local + col_m[nnz_a:nnz_a + idx_range] = np.repeat(colv, n_uv_loc_nz) + row_m[nnz_a:nnz_a + idx_range] = np.tile(colv, n_uv_loc_nz) nnz_a += idx_range #print("Assembled") mat_a = scipy.sparse.coo_matrix((data_m, (row_m, col_m)), diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 114e4ac..9e3874f 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -153,6 +153,12 @@ def test_approx_func(self): # xyz_grid = eval_surface_on_grid(z_surf.make_full_surface()) # grid_cmp(xyz_func, xyz_grid, 0.01) + def test_approx_real_surface(self): + # Test approximation of real surface grid using a random subset + # hard test of regularization. + # TODO: use Hradek grid, + pass + def test_transformed_quad(self): xy_mat = np.array( [ [1.0, -1.0, 0 ], [1.0, 1.0, 0 ]]) # rotate left pi/4 and blow up 1.44 np.random.seed(seed=123) From 385029715cd6ed87bcac22693050fcf094e1c5a2 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 13 Dec 2017 22:35:44 +0100 Subject: [PATCH 52/67] Test of approx regularization --- src/bspline_approx.py | 4 ++++ tests/test_bs_approx.py | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 96e4ee3..cd9ef3f 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -324,6 +324,10 @@ def __init__(self, points): # Error of the approximation self.error = None + def set_quad(self, quad = None): + if quad is None: + quad = np.array([[0,1], [0,0], [1,0], [1,1]]) + self.quad = quad def compute_default_quad(self): """ diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 9e3874f..719244a 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -156,8 +156,26 @@ def test_approx_func(self): def test_approx_real_surface(self): # Test approximation of real surface grid using a random subset # hard test of regularization. - # TODO: use Hradek grid, - pass + logging.basicConfig(level=logging.DEBUG) + + xy_mat = np.array( [ [1.0, 0.0, 0 ], [0.0, 1.0, 0 ]]) # rotate left pi/4 and blow up 1.44 + z_mat = np.array( [1.0, 0] ) + xyz_func = eval_func_on_grid(function_sin_cos, xy_mat[:2,:2], xy_mat[:, 2], z_mat) + + print("Compare: Func - Randomized.approx") + points = bs.make_function_grid(function_sin_cos, 50, 50) + points = points.reshape( (-1, 3) ) + n_sample_points= 400 # this is near the limit number of points to keep desired precision + random_subset = np.random.random_integers(0, len(points)-1, n_sample_points) + points_random = points[random_subset, :] + + approx = bs_approx.SurfaceApprox(points_random) + approx.set_quad(None) # set unit square + surface = approx.compute_approximation() + xyz_grid = eval_z_surface_on_grid(surface, xy_mat[:2, :2], xy_mat[:, 2]) + print("Approx error: ", approx.error) + grid_cmp(xyz_func, xyz_grid, 0.02) + def test_transformed_quad(self): xy_mat = np.array( [ [1.0, -1.0, 0 ], [1.0, 1.0, 0 ]]) # rotate left pi/4 and blow up 1.44 From 544a98ec7be6f7bba6cca2dc67f8093c35df8e3e Mon Sep 17 00:00:00 2001 From: jiri Date: Thu, 14 Dec 2017 12:21:54 +0100 Subject: [PATCH 53/67] working version - need to solve problems in linear algebra oparations & data types --- src/isec_surf_surf.py | 323 +++++++++++++++++++++++------------------- tests/test_isec.py | 7 +- 2 files changed, 183 insertions(+), 147 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 6a2ad76..31552e7 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -16,10 +16,11 @@ class IsecSurfSurf: def __init__(self, surf1, surf2, nt=2,nit=10): self.surf1 = surf1 self.surf2 = surf2 - self.tree1 = self.bounding_boxes(self.surf1) - self.tree2 = self.bounding_boxes(self.surf2) + self.box1, self.tree1 = self.bounding_boxes(self.surf1) + self.box2, self.tree2 = self.bounding_boxes(self.surf2) self.nt = nt self.nit = nit + #tolerance @staticmethod def bounding_boxes(surf): @@ -36,7 +37,9 @@ def bounding_boxes(surf): for j in range(0,3): patch_poles[n_points,:,i_patch] = surf.poles[k+i, l+j, :] n_points += 1 + assert i_patch == (k * surf.v_basis.n_intervals + l) i_patch += 1 + boxes = [bih.AABB(patch_poles[:,:,p].tolist()) for p in range(n_patch)] #print(patch_poles[:, :, 0]) tree.add_boxes( boxes ) @@ -48,183 +51,215 @@ def bounding_boxes(surf): - @staticmethod - def get_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit): + #@staticmethod + #def get_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit): + def get_intersection(self): + # surf1, surf2, tree1, tree2, box1, box2, nt, nit # nt - number of threads (integer) #X,Xs, u_n_intervs,v_n_intervs,u_knots,v_knots, #us_n_intervs,vs_n_intervs,us_knots,vs_knots,isec,n_isec, nit,nt ) # computes intersection of BSpline patch with BSpline thread + point, n_points, ninter = self._compute_intersection(self.surf1,self.surf2,self.tree1,self.tree2,self.box1,self.box2) + # surf1, surf2, tree1, tree2, box1, box2, nt, nit + + def _compute_intersection(self,surf1,surf2,tree1,tree2,box1,box2): + + nt = self.nt + nit = self.nit + + #surf1, surf2, tree1, tree2, box1, box2, nt, nit + n_points = 0 + point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) + ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) + for u_index in range(surf1.u_basis.n_intervals): + us1 = surf1.u_basis.knots[u_index + 2] + ue1 = surf1.u_basis.knots[u_index + 3] + u1_center =(us1 + ue1)/2 + ui = np.zeros([2,1]) + print(ui.shape) + ui[0:1,0] = ([us1, ue1]) + #ui = np.array([us1, ue1] ) + for v_index in range(surf1.v_basis.n_intervals): + vs1 = surf1.v_basis.knots[v_index+2] + ve1 = surf1.v_basis.knots[v_index+3] + v1_center = (vs1 + ve1)/2 + vi = np.array([vs1, ve1]) + s=0 + box_id = v_index + surf1.u_basis.n_intervals * u_index + intersectioned_boxes1 = tree1.find_box(box2[box_id]) + + #if n_isec.shape[1] > 0: + for i_boxes1 in intersectioned_boxes1: + u2_index = int(np.floor(i_boxes1 / surf2.v_basis.n_intervals)) + v2_index = int(i_boxes1 - (u2_index * surf2.v_basis.n_intervals)) + sp_i = u2_index * surf2.v_basis.n_intervals + v2_index + # v2 fixed + u2i = np.array([surf2.u_basis.knots[u2_index+2], surf2.u_basis.knots[u2_index+3]]) + v2_thread = np.linspace(surf2.v_basis.knots[v2_index+2],surf2.v_basis.knots[v2_index+3],nt) + for v2_val in v2_thread: #=1:length(v_knot) + u2_center = (u2i[0] + u2i[1])/2 + v2i = np.array([v2_val]) + uvu2v2 = np.array([u1_center, v1_center, u2_center])#initial condition + uvu2v2,ptl,conv = self._patch_patch_intersection( surf1, surf2,uvu2v2, ui,vi,u2i,v2i, nit ,u2_index,v2_index,u_index,v_index) + + if np.not_equal(conv, 0): + s = s+1 + n_points = n_points +1 + point[n_points,:] = [uvu2v2,v2_val,ptl(1),ptl(2),ptl(3),u2_index,v2_index,u_index,v_index] + + # u2 fixed + #v2i = [vs_knots(o+2) vs_knots(o+3)] + #u_knot = linspace(us_knots(m+2),us_knots(m+3),nt) + #for h =1:length(u_knot) + # v2_c = (vs_knots(o+2) + vs_knots(o+3))/2 + # u2i = u_knot(h) + # uvu2v2 = [u1_c,v1_c,v2_c]; % initial condition + # [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l) + # if conv ~= 0 + # s = s+1 + # n_points = n_points +1 + # point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o] + ninter[u_index,v_index] = s + return point, n_points, ninter - def _patch_patch_intersection( surf1, surf2,uvt, ui,vi,u2i,v2i, nit ,m,o,k,l): - #returns coordinetes of the intersection of the patches (if exist), one - #parameter must be fixed - pt = np.zeros([3,1]) - conv =0 - tol = 1e-6; # in x,y,z - tol2 = 1e-4; # in u,v - # nt = self.nt + def _patch_patch_intersection( self,surf1, surf2,uvt, ui,vi,u2i,v2i, nit ,m,o,k,l): + #returns coordinetes of the intersection of the patches (if exist), one + #parameter must be fixed + pt = np.zeros([3,1]) + conv =0 + tol = 1e-6 # in x,y,z + tol2 = 1e-4 # in u,v + # nt = self.nt + #print(uvt.shape) + #print(uvt[0]) + #print(uvt) + #print(m,o,k,l) - Xs = surf2.poles[m:m + 3, o:o + 3, 0] - X = surf1.poles[k:k + 3, l:l + 3, 0] + X2 = surf2.poles[m:m + 3, o:o + 3,:] + X = surf1.poles[k:k + 3, l:l + 3,:] - if u2i.shape[0] == 1: - u2f = surf2.u_basis.eval_vector(m,u2i) # eval_vector(self, i_base, t): - if v2i.shape[0] == 1: - v2f = surf2.v_basis.eval_vector(o,v2i) + if u2i.shape[0] == 1: + u2f = surf2.u_basis.eval_vector(m,u2i) # eval_vector(self, i_base, t): + if v2i.shape[0] == 1: + v2f = surf2.v_basis.eval_vector(o,v2i) - for i in range(nit): - uf = surf1.u_basis.eval_vector(k,uvt[0]) - vf = surf1.v_basis.eval_vector(l,uvt[1]) - ufd = surf1.u_basis.eval_diff_vector(k,uvt[0]) - vfd = surf1.v_basis.eval_diff_vector(l,uvt[1]) + for i in range(nit): + uf = surf1.u_basis.eval_vector(k,uvt[0]) + vf = surf1.v_basis.eval_vector(l,uvt[1]) + ufd = surf1.u_basis.eval_diff_vector(k,uvt[0]) + vfd = surf1.v_basis.eval_diff_vector(l,uvt[1]) if u2i.shape[0] == 1: v2f = surf2.v_basis.eval_vector(o,uvt[2]) v2fd = surf2.v_basis.eval_diff_vector(o,uvt[2]) - dXYZp2 = la.multi_dot([u2f,Xs,v2fd]) - #(kron(v2fd',u2f')*Xs)'; + uX2 = np.tensordot(u2f,X2,axes=([0],[0])) + dXYZp2 = np.tensordot(uX2,v2fd,axes=([0],[0])) if v2i.shape[0] == 1: - u2f = surf2.u_basis.eval_vector(m,uvt[2]) #splinebasevec(us_knots,uvt(3),0,m) - u2fd = surf2.u_basis.eval_diff_vector(m,uvt[2]) #splinebasevec(us_knots,uvt(3),1,m) - dXYZp2 = la.multi_dot([u2fd, Xs, v2f]) + u2f = surf2.u_basis.eval_vector(m,uvt[2]) + u2fd = surf2.u_basis.eval_diff_vector(m,uvt[2]) + uX2 = np.tensordot(u2fd,X2,axes=([0],[0])) + dXYZp2 = np.tensordot(uX2,v2f,axes=([0],[0])) + + X1v = np.tensordot(X, vf,axes=([0],[0])) + dXYZu1 = np.tensordot(ufd,X1v,axes=([0],[0])) + X1vd = np.tensordot(X,vfd,axes=([0],[0])) + dXYZv1 = np.tensordot(uf,X1vd,axes=([0],[0])) + J = np.column_stack((dXYZu1, dXYZv1, -dXYZp2)) + + uX1 = np.tensordot(uf,X,axes=([0],[0])) + XYZ1 = np.tensordot(uX1,vf,axes=([0],[0])) + uX2 = np.tensordot(u2f,X,axes=([0],[0])) + XYZ2 = np.tensordot(uX2,v2f,axes=([0],[0]))[:,0] + #print(uf.shape) + #print(vf.shape) + #print(u2f.shape) + #print(v2f.shape) + #print(XYZ1.shape) + #print(XYZ2.shape) + deltaXYZ = XYZ1 - XYZ2 + + + #print(XYZ1) + #print(XYZ2) + + #print(deltaXYZ) + uvt = uvt - la.solve(J,deltaXYZ) #uvt = uvt- J\deltaXYZ + #print(uvt) + test,uvt = self._rangetest(uvt,ui,vi,u2i,v2i,0.0) + test,uvt = self._rangetest(uvt,ui,vi,u2i,v2i,tol2) + if test == 1: + if u2i.shape[0] == 1: + surf2_pos = surf2.eval(u2i,uvt[2]) + if v2i.shape[0] == 1: + surf2_pos = surf2.eval(uvt[2],v2i) + dist = la.norm(surf1.eval(uvt[0],uvt[1]) - surf2_pos) # may be faster, indices of patches are known - dXYZu1 = la.multi_dot([ufd, X, vf]) #(kron(vf',ufd')*X)' - dXYZv1 = la.multi_dot([uf,X,vfd])# (kron(vfd',uf')*X)' - J = np.array([[dXYZu1.transpose()], [dXYZv1.transpose()], [-dXYZp2.transpose()]]) + if test == 1: + if dist <= tol: + #pt = la.multi_dot(uf,X,vf) #kron(vf',uf')*X + uX1 = np.tensordot(uf,X,axes=([0],[0])) + pt = np.dot(uX1,vf,axes=([0],[0])) + conv =1 + else: + uvt = np.zeros(3,1) - print(dXYZu1) - print(dXYZv1) - print(-dXYZp2) + return uvt, pt, conv + def _rangetest(self,uvt, ui, vi, u2i, v2i, tol): + # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + test = 0 + #print(uvt) + print(ui) - print(J) - return - deltaXYZ = la.multi_dot([uf,X,vf]) - la.multi_dot([u2f,Xs,vf]) # kron(vf',uf') * X)' - (kron(v2f',u2f') * Xs)' - uvt = uvt - la.solve(J,deltaXYZ) #uvt = uvt- J\deltaXYZ - test,uvt = _rangetest(uvt,ui,vi,u2i,v2i,0.0) + du = np.array([uvt[0] - ui[0], ui[1] - uvt[0]]) + dv = np.array([uvt[1] - vi[0], vi[1] - uvt[1]]) + #print(uvt.shape) + #print(uvt[0]) + #print(ui[0]) + #print(ui[1]) - test,uvt = _rangetest(uvt,ui,vi,u2i,v2i,tol2); - if test == 1: - if np.length(u2i) == 1: - surf2_pos = surf2.eval(u2i,uvt[2]) - if np.length(v2i) == 1: - surf2_pos = surf2.eval(uvt[2],v2i) - dist = la.norm(surf1.eval(uvt[0],uvt[1]) - surf2_pos) # may be faster, indices of patches are known + if v2i.shape[0] == 1: + d2p = [uvt[2] - u2i[0], u2i[1] - uvt[2]] + pi = u2i + if u2i.shape[0] == 1: + d2p = np.array([uvt[2] - v2i[0], v2i[1] - uvt[2]]) + pi = v2i + #print(du) + for i in range(0,2): + #print(du[i]) + if (du[i] < -tol): + uvt[0] = ui[i] + for i in range(0,2): + if (dv[i] < -tol): + uvt[1] = vi[i] + + for i in range(0,2): + if (d2p[i] < -tol): + uvt[2] = pi[i] + + if np.logical_and(uvt[0] >= ui[0],uvt[0] <= ui[1]): + if np.logical_and(uvt[1] >= vi[0], uvt[1] <= vi[1]): + if np.logical_and(uvt[2] >= pi[0], uvt[2] <= pi[1]): + test = 1 + + return uvt, test - if test == 1: - if dist <= tol: - pt = la.multi_dot(uf,X,vf) #kron(vf',uf')*X - conv =1 - else: - uvt = np.zeros(3,1) - - return uvt, pt, conv - - def _rangetest(uvt, ui, vi, u2i, v2i, tol): - # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary - - test = 0 - - du = np.array([uvt[0] - ui[0], ui[1] - uvt[0]]) - dv = np.array([uvt[1] - vi[0], vi[1] - uvt[1]]) - - if np.length(v2i) == 1: - d2p = [uvt[2] - u2i[0], u2i[1] - uvt[2]] - pi = u2i - - if np.length(u2i) == 1: - d2p = np.array([uvt[2] - v2i[0], v2i[1] - uvt[2]]) - pi = v2i - - for i in range(0,2): - if (du[i] < -tol): - uvt[0] = ui[i] - - for i in range(0,2): - if (dv[i] < -tol): - uvt[1] = vi[i] - - for i in range(0,2): - if (d2p[i] < -tol): - uvt[2] = pi[i] - - - if np.logical_and(uvt[0] >= ui[0],uvt[0] <= ui[1]): - if np.logical_and(uvt[1] >= vi[0], uvt[1] <= vi[1]): - if np.logical_and(uvt[2] >= pi[0], uvt[2] <= pi[1]): - test = 1 - - return uvt, test - - - - def _compute_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit ): - n_points = 0 - point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) - ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) - for k in range(surf1.u_basis.n_intervals): - us1 = surf1.u_basis.knots[k + 1] - ue1 = surf1.u_basis.knots[k + 2] - u1_c =(us1 + ue1)/2 - ui = np.array([us1, ue1]) - for l in range(surf1.v_basis.n_intervals): - vs1 = surf1.v_basis.knots[l+1] - ve1 = surf1.v_basis.knots[l+2] - v1_c = (vs1 + ve1)/2 - vi = np.array([vs1, ve1]) - s=0 - box_id = l + surf1.u_basis.n_intervals * k - n_isec = tree1.find_box(box2[box_id]) - - #if n_isec.shape[1] > 0: - for p in n_isec: - m = int(np.ceil(p / surf2.u_basis.n_intervals)) - o = int(p - (m-1)*surf2.u_basis.n_intervals) - sp_i = (m-1)*(surf2.u_basis.n_intervals) + o - # v2 fixed - u2i = np.array([surf2.u_basis.knots[m+1], surf2.u_basis.knots[m+2]]) - v_knot = np.linspace(surf2.v_basis.knots[o+1],surf2.v_basis.knots[o+2],nt) - for h in range(nt): #=1:length(v_knot) - u2_c = (u2i[0] + u2i[1])/2 - v2i = np.array([v_knot[h]]) - uvu2v2 = np.array([u1_c, v1_c, u2_c]) # initial condition - uvu2v2,ptl,conv = _patch_patch_intersection( surf1, surf2,uvu2v2, ui,vi,u2i,v2i, nit ,m,o,k,l) - - if np.not_equal(conv, 0): - s = s+1 - n_points = n_points +1 - point[n_points,:] = [uvu2v2,v_knot[h],ptl(1),ptl(2),ptl(3),k,l,m,o] - # u2 fixed - #v2i = [vs_knots(o+2) vs_knots(o+3)] - #u_knot = linspace(us_knots(m+2),us_knots(m+3),nt) - #for h =1:length(u_knot) - # v2_c = (vs_knots(o+2) + vs_knots(o+3))/2 - # u2i = u_knot(h) - # uvu2v2 = [u1_c,v1_c,v2_c]; % initial condition - # [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l) - # if conv ~= 0 - # s = s+1 - # n_points = n_points +1 - # point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o] - ninter[k,l] = s - return point, n_points, ninter - point, n_points, ninter = _compute_intersection(surf1, surf2, tree1, tree2, box1, box2, nt,nit) # def get_intersections(tree1,tree2): diff --git a/tests/test_isec.py b/tests/test_isec.py index dd705a2..dfe47ec 100644 --- a/tests/test_isec.py +++ b/tests/test_isec.py @@ -72,9 +72,10 @@ def test_isec(self): surf1, surf2 = self.plot_extrude() isec = iss.IsecSurfSurf(surf1, surf2) - box1, tree1 = isec.bounding_boxes(surf1) - box2, tree2 = isec.bounding_boxes(surf2) - isec.get_intersection(surf1,surf2,tree1,tree2,box1,box2,isec.nt,isec.nit) # surf1,surf2,tree1,tree2 + #box1, tree1 = isec.bounding_boxes(surf1) + #box2, tree2 = isec.bounding_boxes(surf2) + #isec.get_intersection(surf1,surf2,tree1,tree2,box1,box2,isec.nt,isec.nit) # surf1,surf2,tree1,tree2 + isec.get_intersection() #print(tree1.find_box(boxes2[0])) #print(surf1.poles[:,:,1]) From 39cb5b0154e01c61ae40d6b309b32eb833f323f9 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Wed, 20 Dec 2017 11:29:25 +0100 Subject: [PATCH 54/67] Speed test + satble SVD approx. --- src/bspline_approx.py | 42 +++++++++++++++-------------------------- tests/test_bs_approx.py | 31 ++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 96e4ee3..40baeee 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -379,7 +379,7 @@ def compute_approximation(self, **kwargs): self.nuv = kwargs.get("nuv", self.nuv) self.regularization_weight = kwargs.get("regularization_weight", self.regularization_weight) - logging.info('Transforming points to parametric space ...') + logging.info('Transforming points (n={}) ...'.format(self._n_points)) start_time = time.time() if self.quad is None: self.compute_default_quad() @@ -391,26 +391,24 @@ def compute_approximation(self, **kwargs): self._v_basis = bs.SplineBasis.make_equidistant(2, self.nuv[1]) end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) # Approximation itself logging.info('Creating B matrix ...') start_time = time.time() b_mat, interval = self._build_ls_matrix() end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Creating A matrix ...') start_time = time.time() a_mat = self._build_sparse_reg_matrix() end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) - logging.info('Scaling ...') + logging.info('Scaling + B^T B ...') start_time = time.time() - g_vec = self._z_quad_points[:] - if self._weights is not None: W = scipy.sparse.diags(self._w_quad_points, 0) wg_vec = np.dot(W, g_vec) @@ -418,45 +416,35 @@ def compute_approximation(self, **kwargs): else: wg_vec = g_vec wb_mat = b_mat - b_vec = b_mat.transpose().dot( wg_vec ) - end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - - end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) - - logging.info('Computing B^T B matrix ...') - start_time = time.time() bb_mat = b_mat.transpose().dot(wb_mat) end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) logging.info('Computing A and B svds approximation ...') start_time = time.time() - - bb_norm = scipy.sparse.linalg.svds(bb_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, + bb_norm = scipy.sparse.linalg.svds(bb_mat, k=1, ncv=10, tol=1e-2, which='LM', v0=None, maxiter=300, return_singular_vectors=False) - a_norm = scipy.sparse.linalg.svds(a_mat, k=1, ncv=10, tol=1e-4, which='LM', v0=None, - maxiter=300, return_singular_vectors=False) + a_norm = scipy.sparse.linalg.eigsh(a_mat, k=1, ncv=10, tol=1e-2, which='LM', + maxiter=300, return_eigenvectors=False) c_mat = bb_mat + self.regularization_weight * (bb_norm[0] / a_norm[0]) * a_mat + end_time = time.time() + logging.info('Computed in: {} s'.format(end_time - start_time)) - - - logging.info('Computing Z coordinates ...') + logging.info('Solving for Z coordinates ...') start_time = time.time() z_vec = scipy.sparse.linalg.spsolve(c_mat, b_vec) assert not np.isnan(np.sum(z_vec)), "Singular matrix for approximation." end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) - logging.info('Computing differences ...') + logging.info('Computing error ...') start_time = time.time() diff = b_mat.dot(z_vec) - g_vec self.error = max_diff = np.max(diff) logging.info("Approximation error (max norm): {}".format(max_diff) ) end_time = time.time() - logging.info('Computed in {0} seconds.'.format(end_time - start_time)) + logging.info('Computed in: {} s'.format(end_time - start_time)) # Construct Z-Surface poles_z = z_vec.reshape(self._v_basis.size, self._u_basis.size).T diff --git a/tests/test_bs_approx.py b/tests/test_bs_approx.py index 9e3874f..ae49a6e 100644 --- a/tests/test_bs_approx.py +++ b/tests/test_bs_approx.py @@ -97,7 +97,7 @@ def test_approx_func(self): # xyz_grid[:, :, 2] += z_mat[1] # grid_cmp(xyz_func, xyz_grid, 0.02) - print("Compare: Func - GridSurface.approx") + print("\nCompare: Func - GridSurface.approx") points = bs.make_function_grid(function_sin_cos, 50, 50) gs = bs.GridSurface(points.reshape(-1, 3)) gs.transform(xy_mat, z_mat) @@ -107,7 +107,7 @@ def test_approx_func(self): print("Approx error: ", approx.error) grid_cmp(xyz_func, xyz_grid, 0.02) - print("Compare: Func - points.approx") + print("\nCompare: Func - points.approx") np.random.seed(seed=123) uv = np.random.rand(1000,2) xy = xy_mat[:2,:2].dot(uv.T).T + xy_mat[:, 2] @@ -180,29 +180,40 @@ def plot_plane(self): surf = bs_approx.plane_surface([ [0.0, 0, 0], [1.0, 0, 0], [0.0, 0, 1] ], overhang=0.1) self.plot_surf(surf) - def test_surface_approx(self): - # self.plot_approx_grid() - # self.plot_approx_transformed_grid() - # self.plot_plane() - pass + def test_approx_speed(self): + logging.basicConfig(level=logging.DEBUG) + print("Performance test for 100k points.") + np.random.seed(seed=123) + uv = np.random.rand(100000,2) + xy = uv + z = np.array( [function_sin_cos([u, v]) for u, v in uv] ) + xyz = np.concatenate((xy, z[:, None]), axis=1) + start_time = time.time() + approx = bs_approx.SurfaceApprox(xyz) + surface = approx.compute_approximation() + end_time = time.time() + print("\nApprox 100k points by 100x100 grid in: {} sec".format(end_time - start_time)) + assert end_time - start_time < 6 + # target is approximation of 1M points in one minute + # B matrix 3.6 sec, A matrix 0.7 sec, SVD + Z solve 0.7 sec class TestBoundingBox: def test_hull_and_box(self): points = np.random.randn(1000000,2) - print("hull") + print() start = time.perf_counter() for i in range(1): hull = bs_approx.convex_hull_2d(points) end = time.perf_counter() - print("conver hull time: ", end - start) + print("\nConvex hull of 1M points: {} s".format(end - start)) start = time.perf_counter() for i in range(10): quad = bs_approx.min_bounding_rect(hull) end = time.perf_counter() - print("quad time: ", end - start) + print("Min area bounding box: {} s".format(end - start)) return plt = bs_plot.Plotting() From d9637ef7d9fa1cb92add9f23f2433e4ac8d9e2f5 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Thu, 21 Dec 2017 23:40:39 +0100 Subject: [PATCH 55/67] Fix compute_approximation --- src/bspline_approx.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 21781ee..11bd399 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -14,7 +14,8 @@ import bspline as bs - +#logging.basicConfig(level=logging.DEBUG) +#logging.info("Test info mesg.") """ Approximation methods for B/splines of degree 2. @@ -379,6 +380,7 @@ def compute_approximation(self, **kwargs): Approximation parameters can be passed in through kwargs or set in the object before the call. :return: B-Spline surface """ + self.quad = kwargs.get("quad", self.quad) self.nuv = kwargs.get("nuv", self.nuv) self.regularization_weight = kwargs.get("regularization_weight", self.regularization_weight) @@ -390,6 +392,9 @@ def compute_approximation(self, **kwargs): if self.nuv is None: self.compute_default_nuv() + # TODO: better logic, since this has to be recomputed only if quad is changed. + self._compute_uv_points() + logging.info("Using {} x {} B-spline approximation.".format(self.nuv[0], self.nuv[1])) self._u_basis = bs.SplineBasis.make_equidistant(2, self.nuv[0]) self._v_basis = bs.SplineBasis.make_equidistant(2, self.nuv[1]) From 1e523f1b71498227a922739a2bf454353f4c197c Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Fri, 22 Dec 2017 00:03:04 +0100 Subject: [PATCH 56/67] Simple replacement for slow np.txtload --- src/bspline_approx.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 11bd399..0a168c1 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -13,6 +13,7 @@ import scipy.interpolate import bspline as bs +import csv #logging.basicConfig(level=logging.DEBUG) #logging.info("Test info mesg.") @@ -256,7 +257,11 @@ def approx_from_file(filename): :param filename: Path to the input text file. :return: The approximation object. """ - point_seq = np.loadtxt(filename) + with open(filename, 'r') as f: + point_seq = np.array([l for l in csv.reader(f, delimiter=' ')], dtype=float) + + # too slow: alternatives: loadtxt (16s), csv.reader (1.6s), pandas. read_csv (0.6s) + #point_seq = np.loadtxt(filename) return SurfaceApprox(point_seq) From 11196ad9422892e034e752bb1b195e023343ff89 Mon Sep 17 00:00:00 2001 From: jiri Date: Wed, 3 Jan 2018 09:00:20 +0100 Subject: [PATCH 57/67] working version, intersection updated --- src/isec_surf_surf.py | 374 +++++++++++++++++++++--------------------- tests/test_isec.py | 4 +- 2 files changed, 192 insertions(+), 186 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 31552e7..97fd658 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -22,22 +22,21 @@ def __init__(self, surf1, surf2, nt=2,nit=10): self.nit = nit #tolerance - @staticmethod - def bounding_boxes(surf): + def bounding_boxes(self,surf): tree = bih.BIH() n_patch = (surf.u_basis.n_intervals)*(surf.v_basis.n_intervals) patch_poles = np.zeros([9, 3, n_patch]) i_patch = 0 - for k in range(surf.u_basis.n_intervals): - for l in range(surf.v_basis.n_intervals): + for iu in range(surf.u_basis.n_intervals): + for iv in range(surf.v_basis.n_intervals): n_points = 0 for i in range(0,3): for j in range(0,3): - patch_poles[n_points,:,i_patch] = surf.poles[k+i, l+j, :] + patch_poles[n_points,:,i_patch] = surf.poles[iu+i, iv+j, :] n_points += 1 - assert i_patch == (k * surf.v_basis.n_intervals + l) + assert i_patch == (iu * surf.v_basis.n_intervals + iv) i_patch += 1 boxes = [bih.AABB(patch_poles[:,:,p].tolist()) for p in range(n_patch)] @@ -61,10 +60,10 @@ def get_intersection(self): # computes intersection of BSpline patch with BSpline thread - point, n_points, ninter = self._compute_intersection(self.surf1,self.surf2,self.tree1,self.tree2,self.box1,self.box2) + point, n_points, ninter = self._intersection(self.surf1,self.surf2,self.tree1,self.tree2,self.box1,self.box2) # surf1, surf2, tree1, tree2, box1, box2, nt, nit - def _compute_intersection(self,surf1,surf2,tree1,tree2,box1,box2): + def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use nt = self.nt nit = self.nit @@ -72,212 +71,219 @@ def _compute_intersection(self,surf1,surf2,tree1,tree2,box1,box2): #surf1, surf2, tree1, tree2, box1, box2, nt, nit n_points = 0 point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) - ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) - for u_index in range(surf1.u_basis.n_intervals): - us1 = surf1.u_basis.knots[u_index + 2] - ue1 = surf1.u_basis.knots[u_index + 3] - u1_center =(us1 + ue1)/2 - ui = np.zeros([2,1]) - print(ui.shape) - ui[0:1,0] = ([us1, ue1]) - #ui = np.array([us1, ue1] ) - for v_index in range(surf1.v_basis.n_intervals): - vs1 = surf1.v_basis.knots[v_index+2] - ve1 = surf1.v_basis.knots[v_index+3] - v1_center = (vs1 + ve1)/2 - vi = np.array([vs1, ve1]) + #ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) + for iu1 in range(surf1.u_basis.n_intervals): + for iv1 in range(surf1.v_basis.n_intervals): s=0 - box_id = v_index + surf1.u_basis.n_intervals * u_index - intersectioned_boxes1 = tree1.find_box(box2[box_id]) - - #if n_isec.shape[1] > 0: - for i_boxes1 in intersectioned_boxes1: - u2_index = int(np.floor(i_boxes1 / surf2.v_basis.n_intervals)) - v2_index = int(i_boxes1 - (u2_index * surf2.v_basis.n_intervals)) - sp_i = u2_index * surf2.v_basis.n_intervals + v2_index - # v2 fixed - u2i = np.array([surf2.u_basis.knots[u2_index+2], surf2.u_basis.knots[u2_index+3]]) - v2_thread = np.linspace(surf2.v_basis.knots[v2_index+2],surf2.v_basis.knots[v2_index+3],nt) - for v2_val in v2_thread: #=1:length(v_knot) - u2_center = (u2i[0] + u2i[1])/2 - v2i = np.array([v2_val]) - uvu2v2 = np.array([u1_center, v1_center, u2_center])#initial condition - uvu2v2,ptl,conv = self._patch_patch_intersection( surf1, surf2,uvu2v2, ui,vi,u2i,v2i, nit ,u2_index,v2_index,u_index,v_index) - - if np.not_equal(conv, 0): - s = s+1 - n_points = n_points +1 - point[n_points,:] = [uvu2v2,v2_val,ptl(1),ptl(2),ptl(3),u2_index,v2_index,u_index,v_index] - - # u2 fixed - #v2i = [vs_knots(o+2) vs_knots(o+3)] - #u_knot = linspace(us_knots(m+2),us_knots(m+3),nt) - #for h =1:length(u_knot) - # v2_c = (vs_knots(o+2) + vs_knots(o+3))/2 - # u2i = u_knot(h) - # uvu2v2 = [u1_c,v1_c,v2_c]; % initial condition - # [ uvu2v2,ptl,conv ] = patch_patch_intersection( uvu2v2, ui,vi,u2i,v2i, u_knots, v_knots, X,us_knots, vs_knots, Xs, nit ,m,o,k,l) - # if conv ~= 0 - # s = s+1 - # n_points = n_points +1 - # point(n_points,:) = [uvu2v2(1:2)',u_knot(h),uvu2v2(3),ptl(1),ptl(2),ptl(3),k,l,m,o] - - ninter[u_index,v_index] = s + box_id = iv1 + surf1.v_basis.n_intervals * iu1 + intersectioned_patches1 = tree1.find_box(box2[box_id]) + for ipatch2 in intersectioned_patches1: + iu2 = int(np.floor(ipatch2/ surf2.v_basis.n_intervals)) + iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) + assert ipatch2 == iu2 * surf2.v_basis.n_intervals + iv2 + uv1, uv2, r3_coor, conv = self._patch_patch_intersection(surf1,iu1, iv1, surf2, iu2, iv2) + print(uv1) + print(uv2) + + if np.not_equal(conv, 0): + s = s+1 + n_points = n_points +1 + #print(uv1[0], uv1[1]) + #print(uv2.shape) + #print(uv2.size) + #print( uv2) + print(r3_coor[0], r3_coor[1], r3_coor[2]) + print(iu2, iv2, iu1, iv1) + print(uv1[0], uv1[1], uv2[0], uv2[1], r3_coor[0], r3_coor[1], r3_coor[2], iu2, iv2, iu1, iv1) + point[n_points,:] = uv1[0], uv1[1], uv2[0], uv2[1], r3_coor[0], r3_coor[1], r3_coor[2], iu2, iv2, iu1, iv1 + #ninter[iu1,iv1] = s return point, n_points, ninter - def _patch_patch_intersection( self,surf1, surf2,uvt, ui,vi,u2i,v2i, nit ,m,o,k,l): + @staticmethod + def _energetic_inner_product(u,v,X): + """ + Computes energetic inner product u^T X v + :param u: vector of nonzero basis function in u + :param v: vector of nonzero basis function in v + :param X: tensor of poles in x,y,z + :return: xyz + """ + #xyz = np.zeros([3,1]) + uX = np.tensordot(u, X, axes=([0], [0])) + xyz = np.tensordot(uX, v, axes=([0], [0])) + return xyz + + @staticmethod + def _compute_bounds(knots,idx): + """ + Computes bounds of the patch + :param knots: knot vector + :param idx: index of tha patch + :return: s,e,c (lower bound, upper bound, center) + """ + s = knots[idx +2] + e = knots[idx +3] + c = (s + e)/2 + return s,e,c + + def _compute_jacobian_and_delta(self,uvt,sum_idx,surf1,iu1,iv1,X,t_poles,t_basis,it): + """ + + :param uvt: vector of unknowns [u1,v1,t] + :param sum_idx: index which determines sumation - u fixed = 0, v fixed = 1 + :param surf1: intersected surface + :param iu1: index of the patch of the surface (surf1) in u + :param iv1: index of the patch of the surface (surf1) in u + :param X: poles of the surface (surf1) + :param t_poles: poles of curve + :param t_basis: function basis of the curve + :param it: index of the interval of the curve + :return: J: jacobian , deltaXYZ: vector of deltas + """ + + uf = surf1.u_basis.eval_vector(iu1, uvt[0, 0]) + vf = surf1.v_basis.eval_vector(iv1, uvt[1, 0]) + ufd = surf1.u_basis.eval_diff_vector(iu1, uvt[0, 0]) + vfd = surf1.v_basis.eval_diff_vector(iv1, uvt[1, 0]) + tf = t_basis.eval_vector(it, uvt[2, 0]) + tfd = t_basis.eval_diff_vector(it, uvt[2, 0]) + + dXYZt = np.tensordot(tfd, t_poles, axes=([0], [sum_idx])) + dXYZu1 = self._energetic_inner_product(ufd, vf, X) + dXYZv1 = self._energetic_inner_product(uf, vfd, X) + J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) + + XYZ1 = self._energetic_inner_product(uf, vf, X) + XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) + + xyz1 = np.zeros([3, 1]) + xyz1[0, 0] = XYZ1[0] + xyz1[1, 0] = XYZ1[1] + xyz1[2, 0] = XYZ1[2] + XYZ1 = xyz1 + + deltaXYZ = XYZ1 - XYZ2 + + return J, deltaXYZ + + def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): #returns coordinetes of the intersection of the patches (if exist), one #parameter must be fixed - pt = np.zeros([3,1]) - conv =0 - tol = 1e-6 # in x,y,z - tol2 = 1e-4 # in u,v - # nt = self.nt - #print(uvt.shape) - #print(uvt[0]) - #print(uvt) - #print(m,o,k,l) - - X2 = surf2.poles[m:m + 3, o:o + 3,:] - X = surf1.poles[k:k + 3, l:l + 3,:] - - - if u2i.shape[0] == 1: - u2f = surf2.u_basis.eval_vector(m,u2i) # eval_vector(self, i_base, t): - - if v2i.shape[0] == 1: - v2f = surf2.v_basis.eval_vector(o,v2i) - - for i in range(nit): - uf = surf1.u_basis.eval_vector(k,uvt[0]) - vf = surf1.v_basis.eval_vector(l,uvt[1]) - ufd = surf1.u_basis.eval_diff_vector(k,uvt[0]) - vfd = surf1.v_basis.eval_diff_vector(l,uvt[1]) - - if u2i.shape[0] == 1: - v2f = surf2.v_basis.eval_vector(o,uvt[2]) - v2fd = surf2.v_basis.eval_diff_vector(o,uvt[2]) - uX2 = np.tensordot(u2f,X2,axes=([0],[0])) - dXYZp2 = np.tensordot(uX2,v2fd,axes=([0],[0])) - - if v2i.shape[0] == 1: - u2f = surf2.u_basis.eval_vector(m,uvt[2]) - u2fd = surf2.u_basis.eval_diff_vector(m,uvt[2]) - uX2 = np.tensordot(u2fd,X2,axes=([0],[0])) - dXYZp2 = np.tensordot(uX2,v2f,axes=([0],[0])) - - X1v = np.tensordot(X, vf,axes=([0],[0])) - dXYZu1 = np.tensordot(ufd,X1v,axes=([0],[0])) - X1vd = np.tensordot(X,vfd,axes=([0],[0])) - dXYZv1 = np.tensordot(uf,X1vd,axes=([0],[0])) - J = np.column_stack((dXYZu1, dXYZv1, -dXYZp2)) - - uX1 = np.tensordot(uf,X,axes=([0],[0])) - XYZ1 = np.tensordot(uX1,vf,axes=([0],[0])) - uX2 = np.tensordot(u2f,X,axes=([0],[0])) - XYZ2 = np.tensordot(uX2,v2f,axes=([0],[0]))[:,0] - #print(uf.shape) - #print(vf.shape) - #print(u2f.shape) - #print(v2f.shape) - #print(XYZ1.shape) - #print(XYZ2.shape) - deltaXYZ = XYZ1 - XYZ2 - - - #print(XYZ1) - #print(XYZ2) - - #print(deltaXYZ) - uvt = uvt - la.solve(J,deltaXYZ) #uvt = uvt- J\deltaXYZ - #print(uvt) - test,uvt = self._rangetest(uvt,ui,vi,u2i,v2i,0.0) - - test,uvt = self._rangetest(uvt,ui,vi,u2i,v2i,tol2) - if test == 1: - if u2i.shape[0] == 1: - surf2_pos = surf2.eval(u2i,uvt[2]) - if v2i.shape[0] == 1: - surf2_pos = surf2.eval(uvt[2],v2i) - - dist = la.norm(surf1.eval(uvt[0],uvt[1]) - surf2_pos) # may be faster, indices of patches are known - - if test == 1: - if dist <= tol: - #pt = la.multi_dot(uf,X,vf) #kron(vf',uf')*X - uX1 = np.tensordot(uf,X,axes=([0],[0])) - pt = np.dot(uX1,vf,axes=([0],[0])) - conv =1 - else: - uvt = np.zeros(3,1) - return uvt, pt, conv + nit = self.nit - def _rangetest(self,uvt, ui, vi, u2i, v2i, tol): - # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary - test = 0 - #print(uvt) - print(ui) + r3_coor = np.zeros([3, 1]) + uv1 = np.zeros([2, 1]) + uv2 = np.zeros([2, 1]) - du = np.array([uvt[0] - ui[0], ui[1] - uvt[0]]) - dv = np.array([uvt[1] - vi[0], vi[1] - uvt[1]]) - #print(uvt.shape) - #print(uvt[0]) - #print(ui[0]) - #print(ui[1]) + conv = 0 + abs_tol = 1e-6 # in x,y,z + tol2 = 1e-4 # in u,v + # nt = self.nt + u1s, u1e, u1c = self._compute_bounds(surf1.u_basis.knots,iu1) + v1s, v1e, v1c = self._compute_bounds(surf1.v_basis.knots, iv1) + u2s, u2e, u2c = self._compute_bounds(surf2.u_basis.knots, iu2) + v2s, v2e, v2c = self._compute_bounds(surf2.v_basis.knots, iv2) - if v2i.shape[0] == 1: - d2p = [uvt[2] - u2i[0], u2i[1] - uvt[2]] - pi = u2i + u1i = np.array([u1s, u1e]) + v1i = np.array([v1s, v1e]) + u2i = np.array([u2s, u2e]) + v2i = np.array([v2s, v2e]) - if u2i.shape[0] == 1: - d2p = np.array([uvt[2] - v2i[0], v2i[1] - uvt[2]]) - pi = v2i - #print(du) - for i in range(0,2): - #print(du[i]) - if (du[i] < -tol): - uvt[0] = ui[i] + X = surf1.poles[iu1:iu1 + 3, iv1:iv1 + 3, :] + X2 = surf2.poles[iu2:iu2 + 3, iv2:iv2 + 3,:] - for i in range(0,2): - if (dv[i] < -tol): - uvt[1] = vi[i] - for i in range(0,2): - if (d2p[i] < -tol): - uvt[2] = pi[i] + for sum_idx in range(0,2): + for thread in range(0,2): - if np.logical_and(uvt[0] >= ui[0],uvt[0] <= ui[1]): - if np.logical_and(uvt[1] >= vi[0], uvt[1] <= vi[1]): - if np.logical_and(uvt[2] >= pi[0], uvt[2] <= pi[1]): - test = 1 + # initial condition + uvt = np.zeros([3, 1]) + uvt[0, 0] = u1c + uvt[1, 0] = v1c - return uvt, test + if sum_idx == 0: # u fixed + w = u2i[thread] + u2f = surf2.u_basis.eval_vector(iu2,w) + t_poles = np.tensordot(u2f,X2,axes=([0],[0])) + t_basis = surf2.v_basis + ti = v2i + it = iv2 + uvt[2, 0] = v2c + if sum_idx == 1: # v fixed + w = v2i[thread] + v2f = surf2.v_basis.eval_vector(iv2,w) + t_poles = np.tensordot(v2f,X2, axes=([0], [1])).transpose() + t_basis = surf2.u_basis + ti = u2i + it = iu2 + uvt[2, 0] = u2c + for i in range(nit): + J, deltaXYZ = self._compute_jacobian_and_delta(uvt,sum_idx,surf1,iu1,iv1,X,t_poles,t_basis,it) + uvt = uvt - la.solve(J,deltaXYZ) + test,uvt = self._rangetest(uvt,sum_idx,u1i,v1i,ti,0.0) + test,uvt = self._rangetest(uvt,sum_idx,u1i,v1i,ti,tol2) + if test == 1: + uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) + return uv1, uv2, r3_coor, conv + @staticmethod + def _test_intesection_tolerance(surf1,surf2,sum_idx,uvt,w,abs_tol): + + if sum_idx == 0: + surf2_pos = surf2.eval(w, uvt[2, 0]) # may be faster, patches indices are known + uv2 = np.array([w, uvt[2, 0]]) + if sum_idx == 1: + surf2_pos = surf2.eval(uvt[2, 0], w) + uv2 = np.array(uvt[2, 0], w) + r3_coor = surf1.eval(uvt[0, 0], uvt[1, 0]) + dist = la.norm(r3_coor - surf2_pos) + if dist <= abs_tol: + uv1 = np.array(uvt[0, 0], uvt[1, 0]) + conv = 1 + else: + uv1 = np.array([0, 0]) + conv = 0 + + return uv1, uv2, r3_coor, conv -# def get_intersections(tree1,tree2): -#$ for k in range( self.surf.u_basis.n_intervals - 2): - # for l in range(self.surf.v_basis.n_intervals - 2): - # self.tree1. + def _rangetest(self,uvt, sum_idx,ui, vi, ti, tol): + # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + test = 0 + du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) + dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) + if sum_idx == 1: + dt = [uvt[2,0] - ti[0], ti[1] - uvt[2,0]] + if sum_idx == 0: + dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) + for i in range(0,2): + if (du[i] < -tol): + uvt[0,0] = ui[i] + for i in range(0,2): + if (dv[i] < -tol): + uvt[1,0] = vi[i] + for i in range(0,2): + if (dt[i] < -tol): + uvt[2,0] = ti[i] - # def compute_intersection(self.tree1,self.tree2): - # - # - # intersect_box_ids = tree.find_point([0.7, 0.5, 0.5]) - # intersect_box_ids = tree.find_box(box) + if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): + if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): + if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): + test = 1 + return test, uvt ''' Calculation and representation of intersection of two B-spline surfaces. diff --git a/tests/test_isec.py b/tests/test_isec.py index dfe47ec..d012b18 100644 --- a/tests/test_isec.py +++ b/tests/test_isec.py @@ -64,8 +64,8 @@ def function2(x): #plt.show() - def boudingbox(self): - SurfSurf = IsecSurfSurf.bounding_boxes() + #def boudingbox(self): + # SurfSurf = IsecSurfSurf.bounding_boxes() def test_isec(self): From d682f7423e63cfac3c6a3f00a17e76b7df0200d2 Mon Sep 17 00:00:00 2001 From: jiri Date: Sun, 7 Jan 2018 22:55:22 +0100 Subject: [PATCH 58/67] working version, new class for intersections --- src/isec_surf_surf.py | 275 +++++++++++++++++++++++++++++++++++------- 1 file changed, 230 insertions(+), 45 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 97fd658..e68b3dc 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -7,9 +7,154 @@ import numpy as np import numpy.linalg as la +import bspline as bs +class PatchCurveIsec: + def __init__(self, surf,iu,iv,curv,it): + self.surf = surf + self.iu = iu + self.iv = iv + self.curv = curv + self.it = it + self.point = 1 + def _compute_jacobian_and_delta(self,uvt): + """ + :param uvt: vector of unknowns [u,v,t] + :return: J: jacobian , deltaXYZ: vector of deltas + """ + surf = self.surf + curv = self.curv + iu = self.iu + iv = self.iv + it = self.it + + surf_poles = surf.poles[iu:iu + 3, iv:iv + 3, :] + + t_poles = curv.poles[it:it+3,:] + + uf = surf.u_basis.eval_vector(iu, uvt[0, 0]) + vf = surf.v_basis.eval_vector(iv, uvt[1, 0]) + ufd = surf.u_basis.eval_diff_vector(iu, uvt[0, 0]) + vfd = surf.v_basis.eval_diff_vector(iv, uvt[1, 0]) + #tf = t_basis.eval_vector(it, uvt[2, 0]) + tf = curv.basis.eval_vector(it, uvt[2, 0]) + tfd = curv.basis.eval_diff_vector(it, uvt[2, 0]) + #print('jacobi') + dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) + #print(dXYZt) + dXYZu1 = self._energetic_inner_product(ufd, vf, surf_poles) + dXYZv1 = self._energetic_inner_product(uf, vfd, surf_poles) + J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) + #print(J) + + XYZ1 = self._energetic_inner_product(uf, vf, surf_poles) + XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) + + XYZ1 = XYZ1[:,None] + + deltaXYZ = XYZ1 - XYZ2 + + return J, deltaXYZ + + def get_intersection(self,uvt,nit,rel_tol,abs_tol): + """ + + :param uvt: vector of initial condition (array 3x1) + :param nit: number of iteration (scalar) + :param rel_tol: relative tolerance (absolute in parametric space) + :param abs_tol: absolute tolerance (in R3 space) + :return: point as array (3x1) + """ + + ui = self._compute_bounds(self.surf.u_basis.knots, self.iu) + vi = self._compute_bounds(self.surf.v_basis.knots, self.iv) + ti = self._compute_bounds(self.curv.basis.knots, self.it) + + for i in range(nit): + J, deltaXYZ = self._compute_jacobian_and_delta(uvt) + uvt = uvt - la.solve(J, deltaXYZ) + test, uvt = self._rangetest(uvt, ui, vi, ti, 0.0) + + test, uvt = self._rangetest(uvt, ui, vi, ti, rel_tol) + + if test == 1: + conv = self._test_intesection_tolerance(uvt, abs_tol) + + if conv != 1: + uvt = -np.ones([3,1]) + + return uvt + + @staticmethod + def _compute_bounds(knots,idx): + """ + Computes bounds of the patch (in parametric space) + :param knots: knot vector + :param idx: index of tha patch + :return: bounds as array 2x1 (lower bound, upper bound) + """ + s = knots[idx +2] + e = knots[idx +3] + bounds = np.array([s, e]) + return bounds + + @staticmethod + def _rangetest(uvt,ui, vi, ti, rel_tol): + # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + test = 0 + + du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) + dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) + dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) + + for i in range(0,2): + if (du[i] < -rel_tol): + uvt[0,0] = ui[i] + + for i in range(0,2): + if (dv[i] < -rel_tol): + uvt[1,0] = vi[i] + + for i in range(0,2): + if (dt[i] < -rel_tol): + uvt[2,0] = ti[i] + + if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): + if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): + if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): + test = 1 + + return test, uvt + + def _test_intesection_tolerance(self,uvt,abs_tol): + + conv = 0 + + surf_R3 = self.surf.eval(uvt[0,0],uvt[1,0]) + curv_R3 = self.curv.eval(uvt[2,0]) + dist = la.norm(surf_R3 - curv_R3) + + if dist <= abs_tol: + conv = 1 + + return conv + + + @staticmethod + def _energetic_inner_product(u,v,surf_poles): + """ + Computes energetic inner product u^T X v + :param u: vector of nonzero basis function in u + :param v: vector of nonzero basis function in v + :param X: tensor of poles in x,y,z + :return: xyz + """ + #xyz = np.zeros([3,1]) + uX = np.tensordot(u, surf_poles, axes=([0], [0])) + xyz = np.tensordot(uX, v, axes=([0], [0])) + return xyz class IsecSurfSurf: @@ -127,11 +272,11 @@ def _compute_bounds(knots,idx): c = (s + e)/2 return s,e,c - def _compute_jacobian_and_delta(self,uvt,sum_idx,surf1,iu1,iv1,X,t_poles,t_basis,it): + def _compute_jacobian_and_delta(self,uvt,surf1,iu1,iv1,X,t_poles,t_basis,it): """ :param uvt: vector of unknowns [u1,v1,t] - :param sum_idx: index which determines sumation - u fixed = 0, v fixed = 1 + #:param sum_idx: index which determines sumation - u fixed = 0, v fixed = 1 :param surf1: intersected surface :param iu1: index of the patch of the surface (surf1) in u :param iv1: index of the patch of the surface (surf1) in u @@ -148,20 +293,18 @@ def _compute_jacobian_and_delta(self,uvt,sum_idx,surf1,iu1,iv1,X,t_poles,t_basis vfd = surf1.v_basis.eval_diff_vector(iv1, uvt[1, 0]) tf = t_basis.eval_vector(it, uvt[2, 0]) tfd = t_basis.eval_diff_vector(it, uvt[2, 0]) - - dXYZt = np.tensordot(tfd, t_poles, axes=([0], [sum_idx])) + #print('jacobi') + dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) + #print(dXYZt) dXYZu1 = self._energetic_inner_product(ufd, vf, X) dXYZv1 = self._energetic_inner_product(uf, vfd, X) J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) + #print(J) XYZ1 = self._energetic_inner_product(uf, vf, X) XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) - xyz1 = np.zeros([3, 1]) - xyz1[0, 0] = XYZ1[0] - xyz1[1, 0] = XYZ1[1] - xyz1[2, 0] = XYZ1[2] - XYZ1 = xyz1 + XYZ1 = XYZ1[:,None] deltaXYZ = XYZ1 - XYZ2 @@ -172,6 +315,7 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): #parameter must be fixed nit = self.nit + nt = self.nt r3_coor = np.zeros([3, 1]) uv1 = np.zeros([2, 1]) @@ -179,7 +323,7 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): conv = 0 abs_tol = 1e-6 # in x,y,z - tol2 = 1e-4 # in u,v + rel_tol = 1e-4 # in u,v # nt = self.nt u1s, u1e, u1c = self._compute_bounds(surf1.u_basis.knots,iu1) @@ -194,43 +338,88 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): X = surf1.poles[iu1:iu1 + 3, iv1:iv1 + 3, :] X2 = surf2.poles[iu2:iu2 + 3, iv2:iv2 + 3,:] + #""" + + sum_idx = 0 # u2_fixed + for w in np.linspace(u2i[0],u2i[1],nt): + + # initial condition + uvt = np.zeros([3, 1]) + uvt[0, 0] = u1c + uvt[1, 0] = v1c + + # curve init + u2f = surf2.u_basis.eval_vector(iu2,w) + t_poles = np.tensordot(u2f,X2,axes=([0],[sum_idx])) + #print("tpoles") + #print(t_poles) + t_basis = surf2.v_basis + ti = v2i + it = iv2 + uvt[2, 0] = v2c + + curv_pol = surf2.poles[iu2:iu2 + 3, :,:] + c_pol = np.tensordot(u2f, curv_pol, axes=([0], [sum_idx])) + + curv = bs.Curve(t_basis,c_pol) + pci = PatchCurveIsec(surf1,iu1,iv1,curv,it) - for sum_idx in range(0,2): - for thread in range(0,2): + uvt = pci.get_intersection(uvt,nit,rel_tol,abs_tol) + print(uvt) - # initial condition - uvt = np.zeros([3, 1]) - uvt[0, 0] = u1c - uvt[1, 0] = v1c + uvt[0, 0] = u1c + uvt[1, 0] = v1c + uvt[2, 0] = v2c - if sum_idx == 0: # u fixed - w = u2i[thread] - u2f = surf2.u_basis.eval_vector(iu2,w) - t_poles = np.tensordot(u2f,X2,axes=([0],[0])) - t_basis = surf2.v_basis - ti = v2i - it = iv2 - uvt[2, 0] = v2c + for i in range(nit): + J, deltaXYZ = self._compute_jacobian_and_delta(uvt,surf1,iu1,iv1,X,t_poles,t_basis,it) + uvt = uvt - la.solve(J,deltaXYZ) + test,uvt = self._rangetest(uvt,u1i,v1i,ti,0.0) - if sum_idx == 1: # v fixed - w = v2i[thread] - v2f = surf2.v_basis.eval_vector(iv2,w) - t_poles = np.tensordot(v2f,X2, axes=([0], [1])).transpose() - t_basis = surf2.u_basis - ti = u2i - it = iu2 - uvt[2, 0] = u2c + test,uvt = self._rangetest(uvt,u1i,v1i,ti,rel_tol) - for i in range(nit): - J, deltaXYZ = self._compute_jacobian_and_delta(uvt,sum_idx,surf1,iu1,iv1,X,t_poles,t_basis,it) - uvt = uvt - la.solve(J,deltaXYZ) - test,uvt = self._rangetest(uvt,sum_idx,u1i,v1i,ti,0.0) + if test == 1: + uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) + # """ - test,uvt = self._rangetest(uvt,sum_idx,u1i,v1i,ti,tol2) + sum_idx = 1 # v2_fixed + for w in np.linspace(v2i[0],v2i[1],nt): - if test == 1: - uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) + # initial condition + uvt = np.zeros([3, 1]) + uvt[0, 0] = u1c + uvt[1, 0] = v1c + + # curve init + v2f = surf2.v_basis.eval_vector(iv2,w) + t_poles = np.tensordot(v2f,X2, axes=([0], [sum_idx]))#.transpose() + t_basis = surf2.u_basis + #print("tpoles1") + #print(t_poles) + + ti = u2i + it = iu2 + + #tfd = t_basis.eval_diff_vector(it, uvt[2, 0]) + #dXYZt = np.tensordot(tfd, t_poles, axes=([0], [sum_idx])) + #print(dXYZt) + #t_poles = np.tensordot(v2f, X2, axes=([0], [sum_idx])) + #dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) + #print(dXYZt) + + + uvt[2, 0] = u2c + + for i in range(nit): + J, deltaXYZ = self._compute_jacobian_and_delta(uvt,surf1,iu1,iv1,X,t_poles,t_basis,it) + uvt = uvt - la.solve(J,deltaXYZ) + test,uvt = self._rangetest(uvt,u1i,v1i,ti,0.0) + + test,uvt = self._rangetest(uvt,u1i,v1i,ti,rel_tol) + + if test == 1: + uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) return uv1, uv2, r3_coor, conv @@ -254,17 +443,13 @@ def _test_intesection_tolerance(surf1,surf2,sum_idx,uvt,w,abs_tol): return uv1, uv2, r3_coor, conv - def _rangetest(self,uvt, sum_idx,ui, vi, ti, tol): + def _rangetest(self,uvt,ui, vi, ti, tol): # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary test = 0 du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) - - if sum_idx == 1: - dt = [uvt[2,0] - ti[0], ti[1] - uvt[2,0]] - if sum_idx == 0: - dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) + dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) for i in range(0,2): if (du[i] < -tol): From 70475b26599a3db9eefe6db291f2c191d51aa373 Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 8 Jan 2018 09:42:01 +0100 Subject: [PATCH 59/67] working version, unused code erased --- src/isec_surf_surf.py | 232 ++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 121 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index e68b3dc..38505a4 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -9,7 +9,28 @@ import bspline as bs -class PatchCurveIsec: + +class IsecPoint: + + def __init__(self, surf1,iu1,iv1,surf2,iu2,iv2): + + self.surf1 = surf1 + self.iu1 = iu1 + self.iv1 = iv1 + + self.surf2 = surf2 + self.iu2 = iu2 + self.iv2 = iv2 + + self.tol = 1 + + R3_coor = 1 + + self.curv = curv + self.it = it + + +class IsecCurveSurf: def __init__(self, surf,iu,iv,curv,it): self.surf = surf @@ -210,9 +231,6 @@ def get_intersection(self): def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use - nt = self.nt - nit = self.nit - #surf1, surf2, tree1, tree2, box1, box2, nt, nit n_points = 0 point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) @@ -233,10 +251,6 @@ def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use if np.not_equal(conv, 0): s = s+1 n_points = n_points +1 - #print(uv1[0], uv1[1]) - #print(uv2.shape) - #print(uv2.size) - #print( uv2) print(r3_coor[0], r3_coor[1], r3_coor[2]) print(iu2, iv2, iu1, iv1) print(uv1[0], uv1[1], uv2[0], uv2[1], r3_coor[0], r3_coor[1], r3_coor[2], iu2, iv2, iu1, iv1) @@ -245,19 +259,19 @@ def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use return point, n_points, ninter - @staticmethod - def _energetic_inner_product(u,v,X): - """ - Computes energetic inner product u^T X v - :param u: vector of nonzero basis function in u - :param v: vector of nonzero basis function in v - :param X: tensor of poles in x,y,z - :return: xyz - """ - #xyz = np.zeros([3,1]) - uX = np.tensordot(u, X, axes=([0], [0])) - xyz = np.tensordot(uX, v, axes=([0], [0])) - return xyz + # @staticmethod + # def _energetic_inner_product(u,v,X): + # """ + # Computes energetic inner product u^T X v + # :param u: vector of nonzero basis function in u + # :param v: vector of nonzero basis function in v + # :param X: tensor of poles in x,y,z + # :return: xyz + # """ + # #xyz = np.zeros([3,1]) + # uX = np.tensordot(u, X, axes=([0], [0])) + # xyz = np.tensordot(uX, v, axes=([0], [0])) + # return xyz @staticmethod def _compute_bounds(knots,idx): @@ -311,8 +325,16 @@ def _compute_jacobian_and_delta(self,uvt,surf1,iu1,iv1,X,t_poles,t_basis,it): return J, deltaXYZ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): - #returns coordinetes of the intersection of the patches (if exist), one - #parameter must be fixed + """ + + :param surf1: intersected surface + :param iu1: u coordinate of the patch + :param iv1: v coordinate of the patch + :param surf2: surface which is broken into main curves + :param iu2: u coordinate of the patch + :param iv2: v coordinate of the patc + :return: + """ nit = self.nit nt = self.nt @@ -324,7 +346,7 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): conv = 0 abs_tol = 1e-6 # in x,y,z rel_tol = 1e-4 # in u,v - # nt = self.nt + u1s, u1e, u1c = self._compute_bounds(surf1.u_basis.knots,iu1) v1s, v1e, v1c = self._compute_bounds(surf1.v_basis.knots, iv1) @@ -336,9 +358,6 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): u2i = np.array([u2s, u2e]) v2i = np.array([v2s, v2e]) - X = surf1.poles[iu1:iu1 + 3, iv1:iv1 + 3, :] - X2 = surf2.poles[iu2:iu2 + 3, iv2:iv2 + 3,:] - #""" sum_idx = 0 # u2_fixed for w in np.linspace(u2i[0],u2i[1],nt): @@ -350,38 +369,22 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): # curve init u2f = surf2.u_basis.eval_vector(iu2,w) - t_poles = np.tensordot(u2f,X2,axes=([0],[sum_idx])) - #print("tpoles") - #print(t_poles) - t_basis = surf2.v_basis - ti = v2i + surf_pol = surf2.poles[iu2:iu2 + 3, :,:] + curv_pol = np.tensordot(u2f, surf_pol, axes=([0], [sum_idx])) + curv_basis = surf2.v_basis + it = iv2 uvt[2, 0] = v2c - curv_pol = surf2.poles[iu2:iu2 + 3, :,:] - c_pol = np.tensordot(u2f, curv_pol, axes=([0], [sum_idx])) + curv = bs.Curve(curv_basis,curv_pol) + curve_surf_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) - curv = bs.Curve(t_basis,c_pol) + uvt = curve_surf_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - pci = PatchCurveIsec(surf1,iu1,iv1,curv,it) + uv1 = uvt[0:1, :] + uv2[0, 0] = w + uv2[1, 0] = uvt[2, 0] - uvt = pci.get_intersection(uvt,nit,rel_tol,abs_tol) - print(uvt) - - uvt[0, 0] = u1c - uvt[1, 0] = v1c - uvt[2, 0] = v2c - - for i in range(nit): - J, deltaXYZ = self._compute_jacobian_and_delta(uvt,surf1,iu1,iv1,X,t_poles,t_basis,it) - uvt = uvt - la.solve(J,deltaXYZ) - test,uvt = self._rangetest(uvt,u1i,v1i,ti,0.0) - - test,uvt = self._rangetest(uvt,u1i,v1i,ti,rel_tol) - - if test == 1: - uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) - # """ sum_idx = 1 # v2_fixed for w in np.linspace(v2i[0],v2i[1],nt): @@ -390,85 +393,72 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): uvt = np.zeros([3, 1]) uvt[0, 0] = u1c uvt[1, 0] = v1c + uvt[2, 0] = u2c # curve init v2f = surf2.v_basis.eval_vector(iv2,w) - t_poles = np.tensordot(v2f,X2, axes=([0], [sum_idx]))#.transpose() - t_basis = surf2.u_basis - #print("tpoles1") - #print(t_poles) - - ti = u2i + surf_pol = surf2.poles[:,iv2:iv2 + 3,:] + curv_pol = np.tensordot(v2f, surf_pol, axes=([0], [sum_idx])) it = iu2 - #tfd = t_basis.eval_diff_vector(it, uvt[2, 0]) - #dXYZt = np.tensordot(tfd, t_poles, axes=([0], [sum_idx])) - #print(dXYZt) - #t_poles = np.tensordot(v2f, X2, axes=([0], [sum_idx])) - #dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) - #print(dXYZt) - - - uvt[2, 0] = u2c - - for i in range(nit): - J, deltaXYZ = self._compute_jacobian_and_delta(uvt,surf1,iu1,iv1,X,t_poles,t_basis,it) - uvt = uvt - la.solve(J,deltaXYZ) - test,uvt = self._rangetest(uvt,u1i,v1i,ti,0.0) + curv = bs.Curve(curv_basis,curv_pol) + patch_curve_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) - test,uvt = self._rangetest(uvt,u1i,v1i,ti,rel_tol) + uvt = patch_curve_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - if test == 1: - uv1, uv2, r3_coor, conv = self._test_intesection_tolerance(surf1, surf2, sum_idx, uvt, w, abs_tol) - return uv1, uv2, r3_coor, conv - - @staticmethod - def _test_intesection_tolerance(surf1,surf2,sum_idx,uvt,w,abs_tol): - - if sum_idx == 0: - surf2_pos = surf2.eval(w, uvt[2, 0]) # may be faster, patches indices are known - uv2 = np.array([w, uvt[2, 0]]) - if sum_idx == 1: - surf2_pos = surf2.eval(uvt[2, 0], w) - uv2 = np.array(uvt[2, 0], w) - r3_coor = surf1.eval(uvt[0, 0], uvt[1, 0]) - dist = la.norm(r3_coor - surf2_pos) - if dist <= abs_tol: - uv1 = np.array(uvt[0, 0], uvt[1, 0]) - conv = 1 - else: - uv1 = np.array([0, 0]) - conv = 0 + uv1 = uvt[0:1, :] + uv2[0, 0] = uvt[2, 0] + uv2[1, 0] = w return uv1, uv2, r3_coor, conv - def _rangetest(self,uvt,ui, vi, ti, tol): - # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary - test = 0 - - du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) - dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) - dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) - - for i in range(0,2): - if (du[i] < -tol): - uvt[0,0] = ui[i] - - for i in range(0,2): - if (dv[i] < -tol): - uvt[1,0] = vi[i] - - for i in range(0,2): - if (dt[i] < -tol): - uvt[2,0] = ti[i] - - if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): - if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): - if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): - test = 1 - - return test, uvt + # @staticmethod + # def _test_intesection_tolerance(surf1,surf2,sum_idx,uvt,w,abs_tol): + # + # if sum_idx == 0: + # surf2_pos = surf2.eval(w, uvt[2, 0]) # may be faster, patches indices are known + # uv2 = np.array([w, uvt[2, 0]]) + # if sum_idx == 1: + # surf2_pos = surf2.eval(uvt[2, 0], w) + # uv2 = np.array(uvt[2, 0], w) + # r3_coor = surf1.eval(uvt[0, 0], uvt[1, 0]) + # dist = la.norm(r3_coor - surf2_pos) + # if dist <= abs_tol: + # uv1 = np.array(uvt[0, 0], uvt[1, 0]) + # conv = 1 + # else: + # uv1 = np.array([0, 0]) + # conv = 0 + # + # return uv1, uv2, r3_coor, conv + + # def _rangetest(self,uvt,ui, vi, ti, tol): + # # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + # test = 0 + # + # du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) + # dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) + # dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) + # + # for i in range(0,2): + # if (du[i] < -tol): + # uvt[0,0] = ui[i] + # + # for i in range(0,2): + # if (dv[i] < -tol): + # uvt[1,0] = vi[i] + # + # for i in range(0,2): + # if (dt[i] < -tol): + # uvt[2,0] = ti[i] + # + # if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): + # if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): + # if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): + # test = 1 + # + # return test, uvt ''' Calculation and representation of intersection of two B-spline surfaces. From f47782f1cbed350cee87772d281dad94661d7344 Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 8 Jan 2018 09:42:26 +0100 Subject: [PATCH 60/67] Add eval_local methods. - these get the knot interval and do not perform checks. --- src/bspline.py | 69 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/bspline.py b/src/bspline.py index b602699..7687026 100644 --- a/src/bspline.py +++ b/src/bspline.py @@ -375,19 +375,15 @@ def __init__(self, basis, poles, rational = False): self._poles = (poles[:, 0:self.dim].T * self._weights ).T - def eval(self, t): + def eval_local(self, t, it): """ - Evaluate a B-spline curve for paramater 't'. + Evaluate a B-spline curve for parameter 't' with given knotinterval 'it'. :param t: Evaluation point. - :return: D-dimensional pnumpy array. D - is dimension given by dimension of poles. - TODO: - - use basis.eval_vector - - test evaluation for rational curves + :param it: Index of knot subinterval (see doc of 'find_knot_interval') + :return: D-dimensional numpy array. D - is dimension given by dimension of poles. """ - - it = self.basis.find_knot_interval(t) dt = self.basis.degree + 1 - t_base_vec = np.array([self.basis.eval(jt, t) for jt in range(it, it + dt)]) + t_base_vec = self.basis.eval_vector(it, t) if self.rational: top_value = np.inner(t_base_vec, self._poles[it: it + dt, :].T) @@ -396,6 +392,20 @@ def eval(self, t): else: return np.inner(t_base_vec, self.poles[it: it + dt, :].T) + + def eval(self, t): + """ + Evaluate a B-spline curve for paramater 't'. Check and fix range of 't'. + :param t: Evaluation point. + :return: D-dimensional numpy array. D - is dimension given by dimension of poles. + TODO: + - test evaluation for rational curves + """ + t = self.basis.check(t) + it = self.basis.find_knot_interval(t) + return self.eval_local(t, it) + + def eval_array(self, t_points): """ Evaluate in array of t-points. @@ -404,6 +414,7 @@ def eval_array(self, t_points): """ return np.array( [ self.eval(t) for t in t_points] ) + def aabb(self): """ Return Axes Aligned Bounding Box of the poles, which should be also bounding box of the curve itself. @@ -462,23 +473,20 @@ def __init__(self, basis, poles, rational=False): self._weights = poles[:, :, self.dim] self._poles = (poles[:, :, 0:self.dim].T * self._weights.T ).T - def eval(self, u, v): + + + + def eval_local(self, u, v, iu, iv): """ - Evaluate a B-spline surface for paramaters u,v. + Evaluate a B-spline surface for paramaters u,v with given knot subintervals. :param u, v: Evaluation point. + :param iu, iv: Knot subintervals of u, v. :return: D-dimensional numpy array. D - is dimension given by dimension of poles. - TODO: - - use basis.eval_vector - - test evaluation for rational curves """ - u = self.u_basis.check(u) - v = self.v_basis.check(v) - iu = self.u_basis.find_knot_interval(u) - iv = self.v_basis.find_knot_interval(v) du = self.u_basis.degree + 1 dv = self.v_basis.degree + 1 - u_base_vec = np.array([self.u_basis.eval(ju, u) for ju in range(iu, iu + du)]) - v_base_vec = np.array([self.v_basis.eval(jv, v) for jv in range(iv, iv + dv)]) + u_base_vec = self.u_basis.eval_vector(iu, u) + v_base_vec = self.v_basis.eval_vector(iv, v) if self.rational: top_value = np.inner(u_base_vec, self._poles[iu: iu + du, iv: iv + dv, :].T) @@ -493,6 +501,27 @@ def eval(self, u, v): #print("val: {}".format(value.shape)) return np.inner( value, v_base_vec) + + + def eval(self, u, v): + """ + Evaluate a B-spline surface for paramaters u,v. Check and fix range of 'u, v'. + :param u, v: Evaluation point. + :return: D-dimensional numpy array. D - is dimension given by dimension of poles. + TODO: + - test evaluation for rational curves + """ + u = self.u_basis.check(u) + v = self.v_basis.check(v) + iu = self.u_basis.find_knot_interval(u) + iv = self.v_basis.find_knot_interval(v) + return self.eval_local(u, v, iu, iv) + + + + + + def deep_copy(self): u_basis = copy.copy(self.u_basis) v_basis = copy.copy(self.v_basis) From 7d4fdaf76351c2e09793797a06ec0fcf04ce49fd Mon Sep 17 00:00:00 2001 From: Jan Brezina Date: Mon, 8 Jan 2018 09:42:47 +0100 Subject: [PATCH 61/67] Fix surface approximation of a grid surface. --- src/bspline_approx.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bspline_approx.py b/src/bspline_approx.py index 0a168c1..1c25a96 100644 --- a/src/bspline_approx.py +++ b/src/bspline_approx.py @@ -92,15 +92,16 @@ def line(vtxs, overhang = 0.0): -def surface_from_grid(grid_surface, nuv, **kwargs): +def surface_from_grid(grid_surface, nuv): """ Make a Z_Surface of degree 2 as an approximation of the GridSurface. :param grid_surface: grid surface to approximate :param (nu, nv) Prescribed number of poles in u and v directions. :return: Z_surface object. """ - approx = SurfaceApprox(grid_surface, nuv, **kwargs) - return approx.get_approximation() + approx = SurfaceApprox.approx_from_grid_surface(grid_surface) + return approx.compute_approximation(nuv=nuv) + def curve_from_grid(points, **kwargs): From a764b3b54317b8c92cf12bdc557119f3bec2d74e Mon Sep 17 00:00:00 2001 From: jiri Date: Tue, 16 Jan 2018 16:20:39 +0100 Subject: [PATCH 62/67] intersections - almost done --- src/isec_surf_surf.py | 293 +++++++++++++++--------------------------- 1 file changed, 105 insertions(+), 188 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 38505a4..2208c25 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -12,26 +12,32 @@ class IsecPoint: - def __init__(self, surf1,iu1,iv1,surf2,iu2,iv2): + def __init__(self, surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2): self.surf1 = surf1 self.iu1 = iu1 self.iv1 = iv1 + self.uv1 = uv1 + self.surf2 = surf2 self.iu2 = iu2 self.iv2 = iv2 + self.uv2 = uv2 self.tol = 1 R3_coor = 1 - self.curv = curv - self.it = it + #self.curv = curv + #self.it = it class IsecCurveSurf: - + """ + Class which provides intersection of the surface (patch with given interval iu,iv) and curve with a given + interval it + """ def __init__(self, surf,iu,iv,curv,it): self.surf = surf self.iu = iu @@ -42,8 +48,9 @@ def __init__(self, surf,iu,iv,curv,it): def _compute_jacobian_and_delta(self,uvt): """ - :param uvt: vector of unknowns [u,v,t] - :return: J: jacobian , deltaXYZ: vector of deltas + Computes Jacobian matrix and delta vector of the function + :param uvt: vector of unknowns [u,v,t] (array 3x1) + :return: J: jacobian , deltaXYZ: vector of deltas (array 3x3) """ surf = self.surf curv = self.curv @@ -52,66 +59,61 @@ def _compute_jacobian_and_delta(self,uvt): it = self.it surf_poles = surf.poles[iu:iu + 3, iv:iv + 3, :] - t_poles = curv.poles[it:it+3,:] uf = surf.u_basis.eval_vector(iu, uvt[0, 0]) vf = surf.v_basis.eval_vector(iv, uvt[1, 0]) ufd = surf.u_basis.eval_diff_vector(iu, uvt[0, 0]) vfd = surf.v_basis.eval_diff_vector(iv, uvt[1, 0]) - #tf = t_basis.eval_vector(it, uvt[2, 0]) tf = curv.basis.eval_vector(it, uvt[2, 0]) tfd = curv.basis.eval_diff_vector(it, uvt[2, 0]) - #print('jacobi') + dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) - #print(dXYZt) dXYZu1 = self._energetic_inner_product(ufd, vf, surf_poles) dXYZv1 = self._energetic_inner_product(uf, vfd, surf_poles) J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) - #print(J) XYZ1 = self._energetic_inner_product(uf, vf, surf_poles) XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) - XYZ1 = XYZ1[:,None] - deltaXYZ = XYZ1 - XYZ2 return J, deltaXYZ def get_intersection(self,uvt,nit,rel_tol,abs_tol): """ - + computes intersection of the patch and curve :param uvt: vector of initial condition (array 3x1) :param nit: number of iteration (scalar) :param rel_tol: relative tolerance (absolute in parametric space) :param abs_tol: absolute tolerance (in R3 space) - :return: point as array (3x1) + :return: point as array (3x1) [u,v,t] """ - ui = self._compute_bounds(self.surf.u_basis.knots, self.iu) - vi = self._compute_bounds(self.surf.v_basis.knots, self.iv) - ti = self._compute_bounds(self.curv.basis.knots, self.it) + iu = self.iu + iv = self.iv + it = self.it + + ui = self._compute_bounds(self.surf.u_basis.knots, iu) + vi = self._compute_bounds(self.surf.v_basis.knots, iv) + ti = self._compute_bounds(self.curv.basis.knots, it) for i in range(nit): J, deltaXYZ = self._compute_jacobian_and_delta(uvt) uvt = uvt - la.solve(J, deltaXYZ) - test, uvt = self._rangetest(uvt, ui, vi, ti, 0.0) + test, uvt = self._range_test(uvt, ui, vi, ti, 0.0) - test, uvt = self._rangetest(uvt, ui, vi, ti, rel_tol) + test, uvt = self._range_test(uvt, ui, vi, ti, rel_tol) if test == 1: - conv = self._test_intesection_tolerance(uvt, abs_tol) - - if conv != 1: - uvt = -np.ones([3,1]) + conv = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) - return uvt + return uvt, conv @staticmethod def _compute_bounds(knots,idx): """ - Computes bounds of the patch (in parametric space) + Computes bounds (lower and upper) of the patch (in parametric space) :param knots: knot vector :param idx: index of tha patch :return: bounds as array 2x1 (lower bound, upper bound) @@ -122,8 +124,18 @@ def _compute_bounds(knots,idx): return bounds @staticmethod - def _rangetest(uvt,ui, vi, ti, rel_tol): - # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary + def _range_test(uvt,ui, vi, ti, rel_tol): + """ + Test of the entries of uvt, lies in given intervals + with a given tolerance + :param uvt: + :param ui: + :param vi: + :param ti: + :param rel_tol: + :return: + """ + test = 0 du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) @@ -149,12 +161,21 @@ def _rangetest(uvt,ui, vi, ti, rel_tol): return test, uvt - def _test_intesection_tolerance(self,uvt,abs_tol): + def _test_intesection_tolerance(self,uvt, iu, iv, it,abs_tol): + """ + Test of the intersections in R3 + :param uvt: + :param iu: + :param iv: + :param it: + :param abs_tol: + :return: + """ conv = 0 - surf_R3 = self.surf.eval(uvt[0,0],uvt[1,0]) - curv_R3 = self.curv.eval(uvt[2,0]) + surf_R3 = self.surf.eval_local(uvt[0,0],uvt[1,0],iu, iv) + curv_R3 = self.curv.eval_local(uvt[2,0],it) dist = la.norm(surf_R3 - curv_R3) if dist <= abs_tol: @@ -186,9 +207,15 @@ def __init__(self, surf1, surf2, nt=2,nit=10): self.box2, self.tree2 = self.bounding_boxes(self.surf2) self.nt = nt self.nit = nit + self._ipoint_list = [] # append #tolerance def bounding_boxes(self,surf): + """ + Compute bounding boxes and construct BIH tree for a given surface + :param surf: + :return: + """ tree = bih.BIH() n_patch = (surf.u_basis.n_intervals)*(surf.v_basis.n_intervals) @@ -215,26 +242,29 @@ def bounding_boxes(self,surf): return boxes, tree - - #@staticmethod - #def get_intersection(surf1,surf2,tree1,tree2,box1,box2,nt,nit): def get_intersection(self): - # surf1, surf2, tree1, tree2, box1, box2, nt, nit - # nt - number of threads (integer) - #X,Xs, u_n_intervs,v_n_intervs,u_knots,v_knots, - #us_n_intervs,vs_n_intervs,us_knots,vs_knots,isec,n_isec, nit,nt ) - # computes intersection of BSpline patch with BSpline thread + """ + Main method to get intersection points + :return: + """ + point_list = self._intersection(self.surf1,self.surf2,self.tree1,self.box2) # patches of surf 1 with respect threads of the surface 2 + point_list2 = self._intersection(self.surf2,self.surf1,self.tree2,self.box1) - point, n_points, ninter = self._intersection(self.surf1,self.surf2,self.tree1,self.tree2,self.box1,self.box2) - # surf1, surf2, tree1, tree2, box1, box2, nt, nit + print(point_list.__len__()) + print(point_list2.__len__()) - def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use - #surf1, surf2, tree1, tree2, box1, box2, nt, nit - n_points = 0 - point = np.zeros([surf1.u_basis.n_intervals * surf1.v_basis.n_intervals, 11]) - #ninter = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals]) + def _intersection(self,surf1,surf2,tree1,box2): + """ + Tries to compute intersection for every patches which does not have empty intersection of the bounding boxes + :param surf1: surface which will be intersected + :param surf2: surface which patches are attempted + :param tree1: BIH tree of the bounding boxes of the patches of the surface 1 + :param box2: coordinates of the bounding boxes of the surface 2 + :return: list of points (intersections) + """ + point_list = [] for iu1 in range(surf1.u_basis.n_intervals): for iv1 in range(surf1.v_basis.n_intervals): s=0 @@ -244,41 +274,18 @@ def _intersection(self,surf1,surf2,tree1,tree2,box1,box2): # use iu2 = int(np.floor(ipatch2/ surf2.v_basis.n_intervals)) iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) assert ipatch2 == iu2 * surf2.v_basis.n_intervals + iv2 - uv1, uv2, r3_coor, conv = self._patch_patch_intersection(surf1,iu1, iv1, surf2, iu2, iv2) - print(uv1) - print(uv2) - - if np.not_equal(conv, 0): - s = s+1 - n_points = n_points +1 - print(r3_coor[0], r3_coor[1], r3_coor[2]) - print(iu2, iv2, iu1, iv1) - print(uv1[0], uv1[1], uv2[0], uv2[1], r3_coor[0], r3_coor[1], r3_coor[2], iu2, iv2, iu1, iv1) - point[n_points,:] = uv1[0], uv1[1], uv2[0], uv2[1], r3_coor[0], r3_coor[1], r3_coor[2], iu2, iv2, iu1, iv1 - #ninter[iu1,iv1] = s - - return point, n_points, ninter - - # @staticmethod - # def _energetic_inner_product(u,v,X): - # """ - # Computes energetic inner product u^T X v - # :param u: vector of nonzero basis function in u - # :param v: vector of nonzero basis function in v - # :param X: tensor of poles in x,y,z - # :return: xyz - # """ - # #xyz = np.zeros([3,1]) - # uX = np.tensordot(u, X, axes=([0], [0])) - # xyz = np.tensordot(uX, v, axes=([0], [0])) - # return xyz + points = self._patch_patch_intersection(surf1,iu1, iv1, surf2, iu2, iv2) + if points.__len__() != 0: + point_list.append(points) + + return point_list @staticmethod def _compute_bounds(knots,idx): """ - Computes bounds of the patch + Computes bounds of the intervals of the knot vector :param knots: knot vector - :param idx: index of tha patch + :param idx: index of the interval :return: s,e,c (lower bound, upper bound, center) """ s = knots[idx +2] @@ -286,46 +293,9 @@ def _compute_bounds(knots,idx): c = (s + e)/2 return s,e,c - def _compute_jacobian_and_delta(self,uvt,surf1,iu1,iv1,X,t_poles,t_basis,it): - """ - - :param uvt: vector of unknowns [u1,v1,t] - #:param sum_idx: index which determines sumation - u fixed = 0, v fixed = 1 - :param surf1: intersected surface - :param iu1: index of the patch of the surface (surf1) in u - :param iv1: index of the patch of the surface (surf1) in u - :param X: poles of the surface (surf1) - :param t_poles: poles of curve - :param t_basis: function basis of the curve - :param it: index of the interval of the curve - :return: J: jacobian , deltaXYZ: vector of deltas - """ - - uf = surf1.u_basis.eval_vector(iu1, uvt[0, 0]) - vf = surf1.v_basis.eval_vector(iv1, uvt[1, 0]) - ufd = surf1.u_basis.eval_diff_vector(iu1, uvt[0, 0]) - vfd = surf1.v_basis.eval_diff_vector(iv1, uvt[1, 0]) - tf = t_basis.eval_vector(it, uvt[2, 0]) - tfd = t_basis.eval_diff_vector(it, uvt[2, 0]) - #print('jacobi') - dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) - #print(dXYZt) - dXYZu1 = self._energetic_inner_product(ufd, vf, X) - dXYZv1 = self._energetic_inner_product(uf, vfd, X) - J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) - #print(J) - - XYZ1 = self._energetic_inner_product(uf, vf, X) - XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) - - XYZ1 = XYZ1[:,None] - - deltaXYZ = XYZ1 - XYZ2 - - return J, deltaXYZ - def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): """ + Intersection of two patches and its reduction to curve patch intersection :param surf1: intersected surface :param iu1: u coordinate of the patch @@ -343,51 +313,45 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): uv1 = np.zeros([2, 1]) uv2 = np.zeros([2, 1]) - conv = 0 abs_tol = 1e-6 # in x,y,z rel_tol = 1e-4 # in u,v - - u1s, u1e, u1c = self._compute_bounds(surf1.u_basis.knots,iu1) - v1s, v1e, v1c = self._compute_bounds(surf1.v_basis.knots, iv1) u2s, u2e, u2c = self._compute_bounds(surf2.u_basis.knots, iu2) v2s, v2e, v2c = self._compute_bounds(surf2.v_basis.knots, iv2) + u1c = (surf1.u_basis.knots[iu1+2] + surf1.u_basis.knots[iu1+3])/2 + v1c = (surf1.v_basis.knots[iv1+2] + surf1.v_basis.knots[iv1+3])/2 - u1i = np.array([u1s, u1e]) - v1i = np.array([v1s, v1e]) - u2i = np.array([u2s, u2e]) - v2i = np.array([v2s, v2e]) - + points = [] sum_idx = 0 # u2_fixed - for w in np.linspace(u2i[0],u2i[1],nt): + for w in np.linspace(u2s,u2e,nt): # initial condition uvt = np.zeros([3, 1]) uvt[0, 0] = u1c uvt[1, 0] = v1c + uvt[2, 0] = v2c # curve init u2f = surf2.u_basis.eval_vector(iu2,w) surf_pol = surf2.poles[iu2:iu2 + 3, :,:] curv_pol = np.tensordot(u2f, surf_pol, axes=([0], [sum_idx])) curv_basis = surf2.v_basis - it = iv2 - uvt[2, 0] = v2c curv = bs.Curve(curv_basis,curv_pol) curve_surf_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) + uvt, conv = curve_surf_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - uvt = curve_surf_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - - uv1 = uvt[0:1, :] - uv2[0, 0] = w - uv2[1, 0] = uvt[2, 0] - + if conv == 1: + uv1 = uvt[0:1, :] + uv2[0, 0] = w + uv2[1, 0] = uvt[2, 0] + point = IsecPoint(surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2) + points.append(point) sum_idx = 1 # v2_fixed - for w in np.linspace(v2i[0],v2i[1],nt): + for w in np.linspace(v2s,v2e,nt): # initial condition uvt = np.zeros([3, 1]) @@ -403,63 +367,16 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): curv = bs.Curve(curv_basis,curv_pol) patch_curve_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) + uvt, conv = patch_curve_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - uvt = patch_curve_isec.get_intersection(uvt,nit,rel_tol,abs_tol) - - - uv1 = uvt[0:1, :] - uv2[0, 0] = uvt[2, 0] - uv2[1, 0] = w - - return uv1, uv2, r3_coor, conv - - # @staticmethod - # def _test_intesection_tolerance(surf1,surf2,sum_idx,uvt,w,abs_tol): - # - # if sum_idx == 0: - # surf2_pos = surf2.eval(w, uvt[2, 0]) # may be faster, patches indices are known - # uv2 = np.array([w, uvt[2, 0]]) - # if sum_idx == 1: - # surf2_pos = surf2.eval(uvt[2, 0], w) - # uv2 = np.array(uvt[2, 0], w) - # r3_coor = surf1.eval(uvt[0, 0], uvt[1, 0]) - # dist = la.norm(r3_coor - surf2_pos) - # if dist <= abs_tol: - # uv1 = np.array(uvt[0, 0], uvt[1, 0]) - # conv = 1 - # else: - # uv1 = np.array([0, 0]) - # conv = 0 - # - # return uv1, uv2, r3_coor, conv - - # def _rangetest(self,uvt,ui, vi, ti, tol): - # # test if paramaters does not lie outside current patch, otherwise they are returned to the boundary - # test = 0 - # - # du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) - # dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) - # dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) - # - # for i in range(0,2): - # if (du[i] < -tol): - # uvt[0,0] = ui[i] - # - # for i in range(0,2): - # if (dv[i] < -tol): - # uvt[1,0] = vi[i] - # - # for i in range(0,2): - # if (dt[i] < -tol): - # uvt[2,0] = ti[i] - # - # if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): - # if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): - # if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): - # test = 1 - # - # return test, uvt + if conv == 1: + uv1 = uvt[0:1, :] + uv2[0, 0] = uvt[2, 0] + uv2[1, 0] = w + point = IsecPoint(surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2) + points.append(point) + return points ''' Calculation and representation of intersection of two B-spline surfaces. From a31070b0fcdb20bc261034215079d4cc3024f575 Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 22 Jan 2018 08:53:52 +0100 Subject: [PATCH 63/67] working version, surf-surf reduced to surf-main_threads --- src/isec_surf_surf.py | 332 ++++++++++++++++++++++++++++++------------ 1 file changed, 235 insertions(+), 97 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 2208c25..441ed5f 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -1,6 +1,7 @@ import sys -build_path="/home/jiri/Soft/Geomop/Intersections/external/bih/build" -sys.path+=[build_path] + +build_path = "/home/jiri/Soft/Geomop/Intersections/external/bih/build" +sys.path += [build_path] print(sys.path) import bih @@ -11,15 +12,12 @@ class IsecPoint: - - def __init__(self, surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2): - + def __init__(self, surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2): self.surf1 = surf1 self.iu1 = iu1 self.iv1 = iv1 self.uv1 = uv1 - self.surf2 = surf2 self.iu2 = iu2 self.iv2 = iv2 @@ -29,8 +27,8 @@ def __init__(self, surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2): R3_coor = 1 - #self.curv = curv - #self.it = it + # self.curv = curv + # self.it = it class IsecCurveSurf: @@ -38,15 +36,15 @@ class IsecCurveSurf: Class which provides intersection of the surface (patch with given interval iu,iv) and curve with a given interval it """ - def __init__(self, surf,iu,iv,curv,it): + + def __init__(self, surf, curv, sum_idx = -1, iw = -1, w = -1): self.surf = surf - self.iu = iu - self.iv = iv self.curv = curv - self.it = it - self.point = 1 + self.sum_idx = sum_idx + self.iw = iw + self.w = w - def _compute_jacobian_and_delta(self,uvt): + def _compute_jacobian_and_delta(self, uvt, iu, iv, it): """ Computes Jacobian matrix and delta vector of the function :param uvt: vector of unknowns [u,v,t] (array 3x1) @@ -54,12 +52,9 @@ def _compute_jacobian_and_delta(self,uvt): """ surf = self.surf curv = self.curv - iu = self.iu - iv = self.iv - it = self.it surf_poles = surf.poles[iu:iu + 3, iv:iv + 3, :] - t_poles = curv.poles[it:it+3,:] + t_poles = curv.poles[it:it + 3, :] uf = surf.u_basis.eval_vector(iu, uvt[0, 0]) vf = surf.v_basis.eval_vector(iv, uvt[1, 0]) @@ -75,12 +70,12 @@ def _compute_jacobian_and_delta(self,uvt): XYZ1 = self._energetic_inner_product(uf, vf, surf_poles) XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) - XYZ1 = XYZ1[:,None] + XYZ1 = XYZ1[:, None] deltaXYZ = XYZ1 - XYZ2 return J, deltaXYZ - def get_intersection(self,uvt,nit,rel_tol,abs_tol): + def get_intersection(self, uvt, iu, iv, it, nit, rel_tol, abs_tol): """ computes intersection of the patch and curve :param uvt: vector of initial condition (array 3x1) @@ -89,17 +84,27 @@ def get_intersection(self,uvt,nit,rel_tol,abs_tol): :param abs_tol: absolute tolerance (in R3 space) :return: point as array (3x1) [u,v,t] """ + conv = 0 + + + uv = np.zeros([2,1]) + if self.sum_idx == 0: + t = np.zeros([2,1]) + t[0,0] = self.w + elif self.sum_idx == 1: + t = np.zeros([2, 1]) + t[1, 0] = self.w + else: + t = np.zeros([1, 1]) - iu = self.iu - iv = self.iv - it = self.it + it = self.iw ui = self._compute_bounds(self.surf.u_basis.knots, iu) vi = self._compute_bounds(self.surf.v_basis.knots, iv) ti = self._compute_bounds(self.curv.basis.knots, it) for i in range(nit): - J, deltaXYZ = self._compute_jacobian_and_delta(uvt) + J, deltaXYZ = self._compute_jacobian_and_delta(uvt, iu, iv, it) uvt = uvt - la.solve(J, deltaXYZ) test, uvt = self._range_test(uvt, ui, vi, ti, 0.0) @@ -107,24 +112,38 @@ def get_intersection(self,uvt,nit,rel_tol,abs_tol): if test == 1: conv = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) + if self.sum_idx == 0: + t[1, 0] = uvt[2, 0] + elif self.sum_idx == 1: + t[0, 0] = uvt[2, 0] + else: + t[0, 0] = uvt[2, 0] + + - return uvt, conv + return uv, t, self.iw, conv @staticmethod - def _compute_bounds(knots,idx): + def _compute_bounds(knots, idx): """ Computes bounds (lower and upper) of the patch (in parametric space) :param knots: knot vector :param idx: index of tha patch :return: bounds as array 2x1 (lower bound, upper bound) """ - s = knots[idx +2] - e = knots[idx +3] + s = knots[idx + 2] + e = knots[idx + 3] bounds = np.array([s, e]) return bounds + def get_curve_interval_poles(self,it): + + poles = self.curv.poles[it:it + 3, :] + + return poles + @staticmethod - def _range_test(uvt,ui, vi, ti, rel_tol): + def _range_test(uvt, ui, vi, ti, rel_tol): """ Test of the entries of uvt, lies in given intervals with a given tolerance @@ -138,30 +157,30 @@ def _range_test(uvt,ui, vi, ti, rel_tol): test = 0 - du = np.array([uvt[0,0] - ui[0], ui[1] - uvt[0,0]]) - dv = np.array([uvt[1,0] - vi[0], vi[1] - uvt[1,0]]) - dt = np.array([uvt[2,0] - ti[0], ti[1] - uvt[2,0]]) + du = np.array([uvt[0, 0] - ui[0], ui[1] - uvt[0, 0]]) + dv = np.array([uvt[1, 0] - vi[0], vi[1] - uvt[1, 0]]) + dt = np.array([uvt[2, 0] - ti[0], ti[1] - uvt[2, 0]]) - for i in range(0,2): + for i in range(0, 2): if (du[i] < -rel_tol): - uvt[0,0] = ui[i] + uvt[0, 0] = ui[i] - for i in range(0,2): + for i in range(0, 2): if (dv[i] < -rel_tol): - uvt[1,0] = vi[i] + uvt[1, 0] = vi[i] - for i in range(0,2): + for i in range(0, 2): if (dt[i] < -rel_tol): - uvt[2,0] = ti[i] + uvt[2, 0] = ti[i] - if np.logical_and(uvt[0,0] >= ui[0],uvt[0,0] <= ui[1]): - if np.logical_and(uvt[1,0] >= vi[0], uvt[1,0] <= vi[1]): - if np.logical_and(uvt[2,0] >= ti[0], uvt[2,0] <= ti[1]): + if np.logical_and(uvt[0, 0] >= ui[0], uvt[0, 0] <= ui[1]): + if np.logical_and(uvt[1, 0] >= vi[0], uvt[1, 0] <= vi[1]): + if np.logical_and(uvt[2, 0] >= ti[0], uvt[2, 0] <= ti[1]): test = 1 return test, uvt - def _test_intesection_tolerance(self,uvt, iu, iv, it,abs_tol): + def _test_intesection_tolerance(self, uvt, iu, iv, it, abs_tol): """ Test of the intersections in R3 :param uvt: @@ -174,8 +193,8 @@ def _test_intesection_tolerance(self,uvt, iu, iv, it,abs_tol): conv = 0 - surf_R3 = self.surf.eval_local(uvt[0,0],uvt[1,0],iu, iv) - curv_R3 = self.curv.eval_local(uvt[2,0],it) + surf_R3 = self.surf.eval_local(uvt[0, 0], uvt[1, 0], iu, iv) + curv_R3 = self.curv.eval_local(uvt[2, 0], it) dist = la.norm(surf_R3 - curv_R3) if dist <= abs_tol: @@ -183,9 +202,46 @@ def _test_intesection_tolerance(self,uvt, iu, iv, it,abs_tol): return conv + def get_curve_n_intervals(self): + """ + Computes bounds (lower and upper) of the patch (in parametric space) + :param knots: knot vector + :param idx: index of tha patch + :return: bounds as array 2x1 (lower bound, upper bound) + """ + n_intervals = self.curv.basis.n_intervals + + return n_intervals + + def get_initial_condition(self, iu, iv, it): + """ + Computes bounds (lower and upper) of the patch (in parametric space) + :param knots: knot vector + :param idx: index of tha patch + :return: bounds as array 2x1 (lower bound, upper bound) + """ + uvt = np.zeros([3, 1]) + + uvt[0, 0] = self._get_mean_value(self.surf.u_basis, iu) + uvt[1, 0] = self._get_mean_value(self.surf.v_basis, iv) + uvt[2, 0] = self._get_mean_value(self.curv.basis, it) + + return uvt @staticmethod - def _energetic_inner_product(u,v,surf_poles): + def _get_mean_value(knots, int): + """ + Computes bounds (lower and upper) of the patch (in parametric space) + :param knots: knot vector + :param idx: index of tha patch + :return: bounds as array 2x1 (lower bound, upper bound) + """ + mean = (knots[int + 2] + knots[int + 3])/2 + + return mean + + @staticmethod + def _energetic_inner_product(u, v, surf_poles): """ Computes energetic inner product u^T X v :param u: vector of nonzero basis function in u @@ -193,69 +249,105 @@ def _energetic_inner_product(u,v,surf_poles): :param X: tensor of poles in x,y,z :return: xyz """ - #xyz = np.zeros([3,1]) + # xyz = np.zeros([3,1]) uX = np.tensordot(u, surf_poles, axes=([0], [0])) xyz = np.tensordot(uX, v, axes=([0], [0])) return xyz class IsecSurfSurf: - - def __init__(self, surf1, surf2, nt=2,nit=10): + def __init__(self, surf1, surf2, nt=2, nit=10, rel_tol = 1e-4, abs_tol = 1e-6): self.surf1 = surf1 self.surf2 = surf2 self.box1, self.tree1 = self.bounding_boxes(self.surf1) self.box2, self.tree2 = self.bounding_boxes(self.surf2) self.nt = nt self.nit = nit - self._ipoint_list = [] # append - #tolerance + self.abs_tol = abs_tol + self.rel_tol = rel_tol - def bounding_boxes(self,surf): + self._ipoint_list = [] # append + # tolerance + + def bounding_boxes(self, surf): """ Compute bounding boxes and construct BIH tree for a given surface :param surf: :return: """ tree = bih.BIH() - n_patch = (surf.u_basis.n_intervals)*(surf.v_basis.n_intervals) - + n_patch = (surf.u_basis.n_intervals) * (surf.v_basis.n_intervals) patch_poles = np.zeros([9, 3, n_patch]) i_patch = 0 for iu in range(surf.u_basis.n_intervals): for iv in range(surf.v_basis.n_intervals): n_points = 0 - for i in range(0,3): - for j in range(0,3): - patch_poles[n_points,:,i_patch] = surf.poles[iu+i, iv+j, :] + for i in range(0, 3): + for j in range(0, 3): + patch_poles[n_points, :, i_patch] = surf.poles[iu + i, iv + j, :] n_points += 1 assert i_patch == (iu * surf.v_basis.n_intervals + iv) i_patch += 1 - boxes = [bih.AABB(patch_poles[:,:,p].tolist()) for p in range(n_patch)] - #print(patch_poles[:, :, 0]) - tree.add_boxes( boxes ) + boxes = [bih.AABB(patch_poles[:, :, p].tolist()) for p in range(n_patch)] + # print(patch_poles[:, :, 0]) + tree.add_boxes(boxes) tree.construct() - #print(boxes) - #for b in boxes: + # print(boxes) + # for b in boxes: # print(b.min()[0:2],b.max()[0:2]) return boxes, tree - def get_intersection(self): """ Main method to get intersection points :return: """ - point_list = self._intersection(self.surf1,self.surf2,self.tree1,self.box2) # patches of surf 1 with respect threads of the surface 2 - point_list2 = self._intersection(self.surf2,self.surf1,self.tree2,self.box1) + point_list = self._intersection(self.surf1, self.surf2, self.tree2, + self.box1) # patches of surf 1 with respect threads of the surface 2 + point_list2 = self._intersection(self.surf2, self.surf1, self.tree1, self.box2) print(point_list.__len__()) print(point_list2.__len__()) - def _intersection(self,surf1,surf2,tree1,box2): + + + + @staticmethod + def _main_threads_intersection(surf,poles,curv_basis,fix_basis,sum_idx): + + isec_curv_surf = [] + boxes = [] + + #sum_idx = 0 --> u fixed + for iw in range(fix_basis.n_intervals): + #print(iw) + #print(fix_basis.knots) + w1f = fix_basis.eval_vector(iw, fix_basis.knots[iw + 2]) + if sum_idx == 0: + surf_pol = poles[iw:iw + 3, :, :] + elif sum_idx == 1: + surf_pol = poles[:,iw:iw + 3, :].transpose() + curv_pol = np.tensordot(w1f, surf_pol, axes=([0], [sum_idx])) + curv = bs.Curve(curv_basis, curv_pol) + #isec = IsecCurveSurf(surf, curv) + isec = IsecCurveSurf(surf, curv, sum_idx, iw, fix_basis.knots[iw + 2] ) + isec_curv_surf.append(isec) + + if sum_idx == 0: + curv_pol = poles[fix_basis.n_intervals+1, :, :] + elif sum_idx == 1: + curv_pol = poles[:, fix_basis.n_intervals+1, :].transpose() + + curv = bs.Curve(curv_basis, curv_pol) + isec = IsecCurveSurf(surf, curv, sum_idx, fix_basis.n_intervals, 1) # 1 !! + isec_curv_surf.append(isec) + + return isec_curv_surf + + def _intersection(self, surf1, surf2, tree2, box1): """ Tries to compute intersection for every patches which does not have empty intersection of the bounding boxes :param surf1: surface which will be intersected @@ -263,37 +355,83 @@ def _intersection(self,surf1,surf2,tree1,box2): :param tree1: BIH tree of the bounding boxes of the patches of the surface 1 :param box2: coordinates of the bounding boxes of the surface 2 :return: list of points (intersections) + """ + #for u in range() + + #generate main curves + sum_idx = 0 # u fixed + isec_curv_surf = self._main_threads_intersection(surf2,surf1.poles,surf1.v_basis,surf1.u_basis,sum_idx) + + point_list = [] + for isec in isec_curv_surf : + for it in range(isec.get_curve_n_intervals()): + intersectioned_patches2 = tree2.find_box(isec.get_curve_interval_poles(it)) + for ipatch2 in intersectioned_patches2: + iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) + iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) + uvt = isec._get_initial_condition(iu2, iv2, it) + uv2,uv1, iw, conv = isec.get_intersection(uvt, iu2, iv2, it, self.nit, self.rel_tol, self.abs_tol) + if conv == 1: + point = IsecPoint(surf1, iw, it, uv1, surf2, iu2, iv2, uv2) + point_list.append(point) + + """ point_list = [] for iu1 in range(surf1.u_basis.n_intervals): + surf1.poles[iu1:iu1+3,:,:] for iv1 in range(surf1.v_basis.n_intervals): - s=0 box_id = iv1 + surf1.v_basis.n_intervals * iu1 - intersectioned_patches1 = tree1.find_box(box2[box_id]) + intersectioned_patches1 = tree2.find_box(box1[box_id]) # !!! for ipatch2 in intersectioned_patches1: - iu2 = int(np.floor(ipatch2/ surf2.v_basis.n_intervals)) + iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) assert ipatch2 == iu2 * surf2.v_basis.n_intervals + iv2 - points = self._patch_patch_intersection(surf1,iu1, iv1, surf2, iu2, iv2) + points = self._patch_patch_intersection(surf1, iu1, iv1, surf2, iu2, iv2) if points.__len__() != 0: point_list.append(points) return point_list + """ + def _make_point(self, surf, iu, iv, curv, it): + """ + Intersection of two patches and its reduction to curve patch intersection + + :param surf1: intersected surface + :param iu1: u coordinate of the patch + :param iv1: v coordinate of the patch + :param surf2: surface which is broken into main curves + :param iu2: u coordinate of the patch + :param iv2: v coordinate of the patc + :return: + """ + + nit = self.nit + # nt = self.nt - move higher + + r3_coor = np.zeros([3, 1]) + uv1 = np.zeros([2, 1]) + uv2 = np.zeros([2, 1]) + + return points + + + ##### Obsolete @staticmethod - def _compute_bounds(knots,idx): + def _compute_bounds(knots, idx): """ Computes bounds of the intervals of the knot vector :param knots: knot vector :param idx: index of the interval :return: s,e,c (lower bound, upper bound, center) """ - s = knots[idx +2] - e = knots[idx +3] - c = (s + e)/2 - return s,e,c + s = knots[idx + 2] + e = knots[idx + 3] + c = (s + e) / 2 + return s, e, c - def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): + def _patch_patch_intersection(self, surf1, iu1, iv1, surf2, iu2, iv2): """ Intersection of two patches and its reduction to curve patch intersection @@ -313,18 +451,18 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): uv1 = np.zeros([2, 1]) uv2 = np.zeros([2, 1]) - abs_tol = 1e-6 # in x,y,z - rel_tol = 1e-4 # in u,v + abs_tol = 1e-6 # in x,y,z + rel_tol = 1e-4 # in u,v u2s, u2e, u2c = self._compute_bounds(surf2.u_basis.knots, iu2) v2s, v2e, v2c = self._compute_bounds(surf2.v_basis.knots, iv2) - u1c = (surf1.u_basis.knots[iu1+2] + surf1.u_basis.knots[iu1+3])/2 - v1c = (surf1.v_basis.knots[iv1+2] + surf1.v_basis.knots[iv1+3])/2 + u1c = (surf1.u_basis.knots[iu1 + 2] + surf1.u_basis.knots[iu1 + 3]) / 2 + v1c = (surf1.v_basis.knots[iv1 + 2] + surf1.v_basis.knots[iv1 + 3]) / 2 points = [] - sum_idx = 0 # u2_fixed - for w in np.linspace(u2s,u2e,nt): + sum_idx = 0 # u2_fixed + for w in np.linspace(u2s, u2e, nt): # initial condition uvt = np.zeros([3, 1]) @@ -333,25 +471,25 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): uvt[2, 0] = v2c # curve init - u2f = surf2.u_basis.eval_vector(iu2,w) - surf_pol = surf2.poles[iu2:iu2 + 3, :,:] + u2f = surf2.u_basis.eval_vector(iu2, w) + surf_pol = surf2.poles[iu2:iu2 + 3, :, :] curv_pol = np.tensordot(u2f, surf_pol, axes=([0], [sum_idx])) curv_basis = surf2.v_basis it = iv2 - curv = bs.Curve(curv_basis,curv_pol) - curve_surf_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) - uvt, conv = curve_surf_isec.get_intersection(uvt,nit,rel_tol,abs_tol) + curv = bs.Curve(curv_basis, curv_pol) + curve_surf_isec = IsecCurveSurf(surf1, iu1, iv1, curv, it) + uvt, conv = curve_surf_isec.get_intersection(uvt, nit, rel_tol, abs_tol) if conv == 1: uv1 = uvt[0:1, :] uv2[0, 0] = w uv2[1, 0] = uvt[2, 0] - point = IsecPoint(surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2) + point = IsecPoint(surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2) points.append(point) - sum_idx = 1 # v2_fixed - for w in np.linspace(v2s,v2e,nt): + sum_idx = 1 # v2_fixed + for w in np.linspace(v2s, v2e, nt): # initial condition uvt = np.zeros([3, 1]) @@ -360,27 +498,27 @@ def _patch_patch_intersection( self,surf1,iu1, iv1, surf2, iu2, iv2): uvt[2, 0] = u2c # curve init - v2f = surf2.v_basis.eval_vector(iv2,w) - surf_pol = surf2.poles[:,iv2:iv2 + 3,:] + v2f = surf2.v_basis.eval_vector(iv2, w) + surf_pol = surf2.poles[:, iv2:iv2 + 3, :] curv_pol = np.tensordot(v2f, surf_pol, axes=([0], [sum_idx])) it = iu2 - curv = bs.Curve(curv_basis,curv_pol) - patch_curve_isec = IsecCurveSurf(surf1,iu1,iv1,curv,it) - uvt, conv = patch_curve_isec.get_intersection(uvt,nit,rel_tol,abs_tol) + curv = bs.Curve(curv_basis, curv_pol) + patch_curve_isec = IsecCurveSurf(surf1, iu1, iv1, curv, it) + uvt, conv = patch_curve_isec.get_intersection(uvt, nit, rel_tol, abs_tol) if conv == 1: uv1 = uvt[0:1, :] uv2[0, 0] = uvt[2, 0] uv2[1, 0] = w - point = IsecPoint(surf1,iu1,iv1,uv1,surf2,iu2,iv2,uv2) + point = IsecPoint(surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2) points.append(point) return points + ''' Calculation and representation of intersection of two B-spline surfaces. - + Result is set of B-spline segments approximating the intersection branches. Every segment consists of: a 3D curve and two 2D curves in parametric UV space of the surfaces. ''' - \ No newline at end of file From 2ba7047c718b924c3298026e164f8ae7c2cb5575 Mon Sep 17 00:00:00 2001 From: jiri Date: Tue, 23 Jan 2018 12:28:58 +0100 Subject: [PATCH 64/67] intersection main_threads-patches: done --- src/isec_surf_surf.py | 406 +++++++++++++++--------------------------- 1 file changed, 139 insertions(+), 267 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 441ed5f..cda5f96 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -12,6 +12,9 @@ class IsecPoint: + """ + Point as the result of intersection with correspondind coordinates on both surfaces + """ def __init__(self, surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2): self.surf1 = surf1 self.iu1 = iu1 @@ -24,32 +27,29 @@ def __init__(self, surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2): self.uv2 = uv2 self.tol = 1 - - R3_coor = 1 - - # self.curv = curv - # self.it = it + self.R3_coor = 1 class IsecCurveSurf: """ - Class which provides intersection of the surface (patch with given interval iu,iv) and curve with a given - interval it + Class which provides intersection of the given surface and curve """ - def __init__(self, surf, curv, sum_idx = -1, iw = -1, w = -1): + def __init__(self, surf, curv): self.surf = surf self.curv = curv - self.sum_idx = sum_idx - self.iw = iw - self.w = w + def _compute_jacobian_and_delta(self, uvt, iu, iv, it): """ Computes Jacobian matrix and delta vector of the function - :param uvt: vector of unknowns [u,v,t] (array 3x1) - :return: J: jacobian , deltaXYZ: vector of deltas (array 3x3) + :param uvt: vector of local coordinates [u,v,t] (array 3x1) + :param iu: index of the knot interval of the coordinate u + :param iv: index of the knot interval of the coordinate v + :param it: index of the knot interval of the coordinate t + :return: J: jacobian matrix (array 3x3) , deltaXYZ: vector of deltas in R^3 space (array 3x1) """ + surf = self.surf curv = self.curv @@ -75,35 +75,27 @@ def _compute_jacobian_and_delta(self, uvt, iu, iv, it): return J, deltaXYZ - def get_intersection(self, uvt, iu, iv, it, nit, rel_tol, abs_tol): + def get_intersection(self, uvt, iu, iv, it, max_it, rel_tol, abs_tol): """ - computes intersection of the patch and curve - :param uvt: vector of initial condition (array 3x1) - :param nit: number of iteration (scalar) + Main solving method for solving + :param uvt: vector of initial guess of local coordinates [u,v,t] (array 3x1) + :param iu: index of the knot interval of the coordinate u + :param iv: index of the knot interval of the coordinate v + :param it: index of the knot interval of the coordinate t + :param max_it: maximum number of iteration :param rel_tol: relative tolerance (absolute in parametric space) :param abs_tol: absolute tolerance (in R3 space) - :return: point as array (3x1) [u,v,t] + :return: uvt: vector of initial guess of local coordinates [u,v,t] (array 3x1), conv as "0" if the methog does not achive desired accuracy + or "1" if the methog achive desired accuracy """ - conv = 0 - - uv = np.zeros([2,1]) - if self.sum_idx == 0: - t = np.zeros([2,1]) - t[0,0] = self.w - elif self.sum_idx == 1: - t = np.zeros([2, 1]) - t[1, 0] = self.w - else: - t = np.zeros([1, 1]) - - it = self.iw + conv = 0 ui = self._compute_bounds(self.surf.u_basis.knots, iu) vi = self._compute_bounds(self.surf.v_basis.knots, iv) ti = self._compute_bounds(self.curv.basis.knots, it) - for i in range(nit): + for i in range(max_it): J, deltaXYZ = self._compute_jacobian_and_delta(uvt, iu, iv, it) uvt = uvt - la.solve(J, deltaXYZ) test, uvt = self._range_test(uvt, ui, vi, ti, 0.0) @@ -112,47 +104,33 @@ def get_intersection(self, uvt, iu, iv, it, nit, rel_tol, abs_tol): if test == 1: conv = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) - if self.sum_idx == 0: - t[1, 0] = uvt[2, 0] - elif self.sum_idx == 1: - t[0, 0] = uvt[2, 0] - else: - t[0, 0] = uvt[2, 0] - - - return uv, t, self.iw, conv + return uvt, conv @staticmethod def _compute_bounds(knots, idx): """ - Computes bounds (lower and upper) of the patch (in parametric space) + Computes boundary of the given area (lower and upper) in parametric space :param knots: knot vector - :param idx: index of tha patch - :return: bounds as array 2x1 (lower bound, upper bound) + :param idx: index of the interval + :return: bounds as (array 2x1) (lower bound, upper bound) """ s = knots[idx + 2] e = knots[idx + 3] bounds = np.array([s, e]) return bounds - def get_curve_interval_poles(self,it): - - poles = self.curv.poles[it:it + 3, :] - - return poles - @staticmethod def _range_test(uvt, ui, vi, ti, rel_tol): """ Test of the entries of uvt, lies in given intervals with a given tolerance - :param uvt: - :param ui: - :param vi: - :param ti: - :param rel_tol: - :return: + :param uvt: vector of local coordinates [u,v,t] (array 3x1) + :param ui: knot interval of the coordinate u (array 2x1) + :param vi: knot interval of the coordinate v (array 2x1) + :param ti: knot interval of the coordinate t (array 2x1) + :param rel_tol: given relative tolerance (absolute in [u,v,t]) + :return: uvt: vector of local coordinates [u,v,t] (array 3x1) """ test = 0 @@ -182,13 +160,14 @@ def _range_test(uvt, ui, vi, ti, rel_tol): def _test_intesection_tolerance(self, uvt, iu, iv, it, abs_tol): """ - Test of the intersections in R3 - :param uvt: - :param iu: - :param iv: - :param it: - :param abs_tol: - :return: + Test of the tolerance of the intersections in R3 + :param uvt: vector of local coordinates [u,v,t] (array 3x1) + :param iu: index of the knot interval of the coordinate u + :param iv: index of the knot interval of the coordinate v + :param it: index of the knot interval of the coordinate t + :param abs_tol: given absolute tolerance in R^3 space + :return: conv as "0" if the methog does not achive desired accuracy + or "1" if the methog achive desired accuracy """ conv = 0 @@ -202,39 +181,30 @@ def _test_intesection_tolerance(self, uvt, iu, iv, it, abs_tol): return conv - def get_curve_n_intervals(self): - """ - Computes bounds (lower and upper) of the patch (in parametric space) - :param knots: knot vector - :param idx: index of tha patch - :return: bounds as array 2x1 (lower bound, upper bound) - """ - n_intervals = self.curv.basis.n_intervals - - return n_intervals - def get_initial_condition(self, iu, iv, it): """ - Computes bounds (lower and upper) of the patch (in parametric space) - :param knots: knot vector - :param idx: index of tha patch - :return: bounds as array 2x1 (lower bound, upper bound) + Computes initial guess as mean value of the considered area + :param iu: index of the knot interval of the coordinate u + :param iv: index of the knot interval of the coordinate v + :param it: index of the knot interval of the coordinate t + :return: uvt as array (3x1) """ + uvt = np.zeros([3, 1]) - uvt[0, 0] = self._get_mean_value(self.surf.u_basis, iu) - uvt[1, 0] = self._get_mean_value(self.surf.v_basis, iv) - uvt[2, 0] = self._get_mean_value(self.curv.basis, it) + uvt[0, 0] = self._get_mean_value(self.surf.u_basis.knots, iu) + uvt[1, 0] = self._get_mean_value(self.surf.v_basis.knots, iv) + uvt[2, 0] = self._get_mean_value(self.curv.basis.knots, it) return uvt @staticmethod def _get_mean_value(knots, int): """ - Computes bounds (lower and upper) of the patch (in parametric space) + Computes mean value of the local coordinate in the given interval :param knots: knot vector - :param idx: index of tha patch - :return: bounds as array 2x1 (lower bound, upper bound) + :param idx: index of the patch + :return: mean """ mean = (knots[int + 2] + knots[int + 3])/2 @@ -247,21 +217,20 @@ def _energetic_inner_product(u, v, surf_poles): :param u: vector of nonzero basis function in u :param v: vector of nonzero basis function in v :param X: tensor of poles in x,y,z - :return: xyz + :return: xyz as array (3x1) """ - # xyz = np.zeros([3,1]) uX = np.tensordot(u, surf_poles, axes=([0], [0])) xyz = np.tensordot(uX, v, axes=([0], [0])) return xyz class IsecSurfSurf: - def __init__(self, surf1, surf2, nt=2, nit=10, rel_tol = 1e-4, abs_tol = 1e-6): + def __init__(self, surf1, surf2, nt=2, max_it=10, rel_tol = 1e-4, abs_tol = 1e-6): self.surf1 = surf1 self.surf2 = surf2 self.box1, self.tree1 = self.bounding_boxes(self.surf1) self.box2, self.tree2 = self.bounding_boxes(self.surf2) self.nt = nt - self.nit = nit + self.max_it = max_it self.abs_tol = abs_tol self.rel_tol = rel_tol @@ -304,9 +273,8 @@ def get_intersection(self): :return: """ - point_list = self._intersection(self.surf1, self.surf2, self.tree2, - self.box1) # patches of surf 1 with respect threads of the surface 2 - point_list2 = self._intersection(self.surf2, self.surf1, self.tree1, self.box2) + point_list = self.get_intersections(self.surf1, self.surf2, self.tree2,) # patches of surf 2 with respect threads of the surface 1 + point_list2 = self.get_intersections(self.surf2, self.surf1, self.tree1) # patches of surf 1 with respect threads of the surface 2 print(point_list.__len__()) print(point_list2.__len__()) @@ -316,205 +284,109 @@ def get_intersection(self): @staticmethod - def _main_threads_intersection(surf,poles,curv_basis,fix_basis,sum_idx): - - isec_curv_surf = [] - boxes = [] + def _main_threads(surf,sum_idx): + """ + Construction of the main threads + :param surf: surface which is used to construction of the main threads + :param sum_idx: sum_idx == 0 --> u fixed, sum_idx == 1 --> v fixed + :return: curves as list of curves, w_val as list of value of the fixed local coordinates , patches as list of neighbour patches + """ - #sum_idx = 0 --> u fixed - for iw in range(fix_basis.n_intervals): - #print(iw) - #print(fix_basis.knots) - w1f = fix_basis.eval_vector(iw, fix_basis.knots[iw + 2]) - if sum_idx == 0: - surf_pol = poles[iw:iw + 3, :, :] - elif sum_idx == 1: - surf_pol = poles[:,iw:iw + 3, :].transpose() - curv_pol = np.tensordot(w1f, surf_pol, axes=([0], [sum_idx])) - curv = bs.Curve(curv_basis, curv_pol) - #isec = IsecCurveSurf(surf, curv) - isec = IsecCurveSurf(surf, curv, sum_idx, iw, fix_basis.knots[iw + 2] ) - isec_curv_surf.append(isec) + poles = surf.poles if sum_idx == 0: - curv_pol = poles[fix_basis.n_intervals+1, :, :] + fix_basis = surf.u_basis + curv_basis = surf.v_basis elif sum_idx == 1: - curv_pol = poles[:, fix_basis.n_intervals+1, :].transpose() - - curv = bs.Curve(curv_basis, curv_pol) - isec = IsecCurveSurf(surf, curv, sum_idx, fix_basis.n_intervals, 1) # 1 !! - isec_curv_surf.append(isec) + fix_basis = surf.v_basis + curv_basis = surf.u_basis - return isec_curv_surf + curves = [] + w_val = [] + patches = [] - def _intersection(self, surf1, surf2, tree2, box1): - """ - Tries to compute intersection for every patches which does not have empty intersection of the bounding boxes - :param surf1: surface which will be intersected - :param surf2: surface which patches are attempted - :param tree1: BIH tree of the bounding boxes of the patches of the surface 1 - :param box2: coordinates of the bounding boxes of the surface 2 - :return: list of points (intersections) - """ - #for u in range() - - #generate main curves - sum_idx = 0 # u fixed - isec_curv_surf = self._main_threads_intersection(surf2,surf1.poles,surf1.v_basis,surf1.u_basis,sum_idx) + if sum_idx == 0: + curv_pol = poles[0, :, :] + elif sum_idx == 1: + curv_pol = poles[:, 0, :] - point_list = [] - for isec in isec_curv_surf : - for it in range(isec.get_curve_n_intervals()): - intersectioned_patches2 = tree2.find_box(isec.get_curve_interval_poles(it)) - for ipatch2 in intersectioned_patches2: - iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) - iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) - uvt = isec._get_initial_condition(iu2, iv2, it) - uv2,uv1, iw, conv = isec.get_intersection(uvt, iu2, iv2, it, self.nit, self.rel_tol, self.abs_tol) - if conv == 1: - point = IsecPoint(surf1, iw, it, uv1, surf2, iu2, iv2, uv2) - point_list.append(point) + iw = [0] + w_val.append(0.0) + patches.append(iw) + curv = bs.Curve(curv_basis, curv_pol) + curves.append(curv) - """ - point_list = [] - for iu1 in range(surf1.u_basis.n_intervals): - surf1.poles[iu1:iu1+3,:,:] - for iv1 in range(surf1.v_basis.n_intervals): - box_id = iv1 + surf1.v_basis.n_intervals * iu1 - intersectioned_patches1 = tree2.find_box(box1[box_id]) # !!! - for ipatch2 in intersectioned_patches1: - iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) - iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) - assert ipatch2 == iu2 * surf2.v_basis.n_intervals + iv2 - points = self._patch_patch_intersection(surf1, iu1, iv1, surf2, iu2, iv2) - if points.__len__() != 0: - point_list.append(points) - return point_list - """ - def _make_point(self, surf, iu, iv, curv, it): - """ - Intersection of two patches and its reduction to curve patch intersection - - :param surf1: intersected surface - :param iu1: u coordinate of the patch - :param iv1: v coordinate of the patch - :param surf2: surface which is broken into main curves - :param iu2: u coordinate of the patch - :param iv2: v coordinate of the patc - :return: - """ + for iw in range(1,fix_basis.n_intervals): + w1f = fix_basis.eval_vector(iw, fix_basis.knots[iw + 2]) + if sum_idx == 0: + surf_pol = poles[iw:iw + 3, :, :] + curv_pol = np.tensordot(w1f, surf_pol, axes=([0], [sum_idx])) + elif sum_idx == 1: + surf_pol = poles[:,iw:iw + 3, :] + curv_pol = np.tensordot(w1f, surf_pol, axes=([0], [sum_idx])) + w_val.append(fix_basis.knots[iw + 2]) + patches.append([iw-1, iw]) - nit = self.nit - # nt = self.nt - move higher + curv = bs.Curve(curv_basis, curv_pol) + curves.append(curv) - r3_coor = np.zeros([3, 1]) - uv1 = np.zeros([2, 1]) - uv2 = np.zeros([2, 1]) + if sum_idx == 0: + curv_pol = poles[fix_basis.n_intervals+1, :, :] + elif sum_idx == 1: + curv_pol = poles[:, fix_basis.n_intervals+1, :] - return points + w_val.append(1.0) + patches.append(fix_basis.n_intervals-1) + curv = bs.Curve(curv_basis, curv_pol) + curves.append(curv) - ##### Obsolete + return curves, w_val, patches - @staticmethod - def _compute_bounds(knots, idx): - """ - Computes bounds of the intervals of the knot vector - :param knots: knot vector - :param idx: index of the interval - :return: s,e,c (lower bound, upper bound, center) - """ - s = knots[idx + 2] - e = knots[idx + 3] - c = (s + e) / 2 - return s, e, c - def _patch_patch_intersection(self, surf1, iu1, iv1, surf2, iu2, iv2): + def get_intersections(self,surf1,surf2,tree2): """ - Intersection of two patches and its reduction to curve patch intersection - - :param surf1: intersected surface - :param iu1: u coordinate of the patch - :param iv1: v coordinate of the patch - :param surf2: surface which is broken into main curves - :param iu2: u coordinate of the patch - :param iv2: v coordinate of the patc - :return: + Tries to compute intersection of the main curves from surface1 and patches of the surface2 which have a + non-empty intersection of corresponding bonding boxes + :param surf1: Surface used to construction of the main threads + :param surf2: Intersected surface + :param tree2: Bih tree of the patchces of the surface 2 + :return: point_list as list of points of intersection """ - nit = self.nit - nt = self.nt - - r3_coor = np.zeros([3, 1]) - uv1 = np.zeros([2, 1]) - uv2 = np.zeros([2, 1]) - - abs_tol = 1e-6 # in x,y,z - rel_tol = 1e-4 # in u,v - - u2s, u2e, u2c = self._compute_bounds(surf2.u_basis.knots, iu2) - v2s, v2e, v2c = self._compute_bounds(surf2.v_basis.knots, iv2) - u1c = (surf1.u_basis.knots[iu1 + 2] + surf1.u_basis.knots[iu1 + 3]) / 2 - v1c = (surf1.v_basis.knots[iv1 + 2] + surf1.v_basis.knots[iv1 + 3]) / 2 - - points = [] - - sum_idx = 0 # u2_fixed - for w in np.linspace(u2s, u2e, nt): - - # initial condition - uvt = np.zeros([3, 1]) - uvt[0, 0] = u1c - uvt[1, 0] = v1c - uvt[2, 0] = v2c - - # curve init - u2f = surf2.u_basis.eval_vector(iu2, w) - surf_pol = surf2.poles[iu2:iu2 + 3, :, :] - curv_pol = np.tensordot(u2f, surf_pol, axes=([0], [sum_idx])) - curv_basis = surf2.v_basis - it = iv2 + point_list = [] - curv = bs.Curve(curv_basis, curv_pol) - curve_surf_isec = IsecCurveSurf(surf1, iu1, iv1, curv, it) - uvt, conv = curve_surf_isec.get_intersection(uvt, nit, rel_tol, abs_tol) - - if conv == 1: - uv1 = uvt[0:1, :] - uv2[0, 0] = w - uv2[1, 0] = uvt[2, 0] - point = IsecPoint(surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2) - points.append(point) - - sum_idx = 1 # v2_fixed - for w in np.linspace(v2s, v2e, nt): - - # initial condition - uvt = np.zeros([3, 1]) - uvt[0, 0] = u1c - uvt[1, 0] = v1c - uvt[2, 0] = u2c - - # curve init - v2f = surf2.v_basis.eval_vector(iv2, w) - surf_pol = surf2.poles[:, iv2:iv2 + 3, :] - curv_pol = np.tensordot(v2f, surf_pol, axes=([0], [sum_idx])) - it = iu2 + for sum_idx in range(2): + curves, w_val, patches = self._main_threads(surf1, sum_idx) + curve_id = -1 + for curve in curves: + curve_id += 1 + for it in range(curve.basis.n_intervals): + curv_surf_isec = IsecCurveSurf(surf2, curve) + boxes = bih.AABB(curve.poles[it:it+3, :].tolist()) + uv1 = np.zeros([2,1]) + uv1[sum_idx,0] = w_val[curve_id] + intersectioned_patches2 = tree2.find_box(boxes) + for ipatch2 in intersectioned_patches2: + iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) + iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) + uvt = curv_surf_isec.get_initial_condition(iu2, iv2, it) + uvt, conv = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, self.rel_tol, self.abs_tol) + # boundary flag set + if conv == 1: + #distance control + uv1[1-sum_idx,0] = uvt[2,0] + if sum_idx == 0: + point = IsecPoint(surf1, patches[curve_id], it, uv1, surf2, iu2, iv2, uvt[0:1,:]) + elif sum_idx == 1: + point = IsecPoint(surf1, it,patches[curve_id], uv1, surf2, iu2, iv2, uvt[0:1, :]) + + point_list.append(point) - curv = bs.Curve(curv_basis, curv_pol) - patch_curve_isec = IsecCurveSurf(surf1, iu1, iv1, curv, it) - uvt, conv = patch_curve_isec.get_intersection(uvt, nit, rel_tol, abs_tol) - - if conv == 1: - uv1 = uvt[0:1, :] - uv2[0, 0] = uvt[2, 0] - uv2[1, 0] = w - point = IsecPoint(surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2) - points.append(point) + return point_list - return points ''' Calculation and representation of intersection of two B-spline surfaces. From 17c3520665b1e237f4043057ad52610a937dd36f Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 12 Feb 2018 08:02:33 +0100 Subject: [PATCH 65/67] minor fix in nonlinear solver number of duplicities reduced --- src/isec_surf_surf.py | 231 ++++++++++++++++++++++++++++++++++++------ tests/test_isec.py | 39 ++++++- 2 files changed, 233 insertions(+), 37 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index cda5f96..07b096d 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -15,20 +15,37 @@ class IsecPoint: """ Point as the result of intersection with correspondind coordinates on both surfaces """ - def __init__(self, surf1, iu1, iv1, uv1, surf2, iu2, iv2, uv2): + def __init__(self, surf1, iu1, iv1, uv1, boundary_flag,surf2, iu2, iv2, uv2,XYZ): + self.surf1 = surf1 self.iu1 = iu1 self.iv1 = iv1 self.uv1 = uv1 + self.boundary_flag = boundary_flag + self.surf2 = surf2 self.iu2 = iu2 self.iv2 = iv2 self.uv2 = uv2 + self.tol = 1 - self.R3_coor = 1 + self.R3_coor = XYZ + + def add_patches(self, sum_idx): + + n = self.iu1.__len__() + + if sum_idx == 0: + for i in range(n): + self.iu1.append(self.iu1[0]+1) + self.iv1.append(self.iv1[i]) + elif sum_idx == 1: + for i in range(n): + self.iu1.append(self.iu1[i]) + self.iv1.append(self.iv1[0]+1) class IsecCurveSurf: """ @@ -56,6 +73,7 @@ def _compute_jacobian_and_delta(self, uvt, iu, iv, it): surf_poles = surf.poles[iu:iu + 3, iv:iv + 3, :] t_poles = curv.poles[it:it + 3, :] + uf = surf.u_basis.eval_vector(iu, uvt[0, 0]) vf = surf.v_basis.eval_vector(iv, uvt[1, 0]) ufd = surf.u_basis.eval_diff_vector(iu, uvt[0, 0]) @@ -64,13 +82,18 @@ def _compute_jacobian_and_delta(self, uvt, iu, iv, it): tfd = curv.basis.eval_diff_vector(it, uvt[2, 0]) dXYZt = np.tensordot(tfd, t_poles, axes=([0], [0])) + #print(dXYZt) dXYZu1 = self._energetic_inner_product(ufd, vf, surf_poles) dXYZv1 = self._energetic_inner_product(uf, vfd, surf_poles) J = np.column_stack((dXYZu1, dXYZv1, -dXYZt)) XYZ1 = self._energetic_inner_product(uf, vf, surf_poles) - XYZ2 = np.tensordot(tf, t_poles, axes=([0], [1])) + XYZ2 = np.tensordot(tf, t_poles, axes=([0], [0])) + #print(XYZ2) + #return + XYZ2 = XYZ2[:, None] XYZ1 = XYZ1[:, None] + deltaXYZ = XYZ1 - XYZ2 return J, deltaXYZ @@ -85,27 +108,70 @@ def get_intersection(self, uvt, iu, iv, it, max_it, rel_tol, abs_tol): :param max_it: maximum number of iteration :param rel_tol: relative tolerance (absolute in parametric space) :param abs_tol: absolute tolerance (in R3 space) - :return: uvt: vector of initial guess of local coordinates [u,v,t] (array 3x1), conv as "0" if the methog does not achive desired accuracy - or "1" if the methog achive desired accuracy + :return: uvt: vector of initial guess of local coordinates [u,v,t] (array 3x1), + conv as "0" if the methog does not achive desired accuracy + "1" if the methog achive desired accuracy + flag as intersection specification """ conv = 0 + flag = -1 ui = self._compute_bounds(self.surf.u_basis.knots, iu) vi = self._compute_bounds(self.surf.v_basis.knots, iv) ti = self._compute_bounds(self.curv.basis.knots, it) + #print('IC=',uvt) for i in range(max_it): J, deltaXYZ = self._compute_jacobian_and_delta(uvt, iu, iv, it) uvt = uvt - la.solve(J, deltaXYZ) + #print('new') + #print(uvt) test, uvt = self._range_test(uvt, ui, vi, ti, 0.0) - test, uvt = self._range_test(uvt, ui, vi, ti, rel_tol) + #print(uvt) + #print( test) + + + #print(ui,vi,ti) + #print(test) + #print(uvt) + test, uvt = self._range_test(uvt, ui, vi, ti, rel_tol) + #print(test) + #print(uvt) + # abs_tol if test == 1: - conv = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) + conv, XYZ = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) + #print(conv) + + if conv == 1: + flag = self._boundary_intersection(uvt[2,0],ti) + + #print('conv,flag =',conv,flag) + #print('end') + return uvt, conv, flag, XYZ + + @staticmethod + def _boundary_intersection(t,ti): + """ + + :param t: parameter value + :param it: interval array (2x1) + :return: + flag as "0" corresponds to lower bound of the curve + "1" corresponds to upper bound of the curve + "-1" corresponds to the boundary points of the curve + """ - return uvt, conv + if ti[0] == t: + flag = 0 + elif ti[1] == t: + flag = 1 + else: + flag = -1 + + return flag @staticmethod def _compute_bounds(knots, idx): @@ -174,12 +240,13 @@ def _test_intesection_tolerance(self, uvt, iu, iv, it, abs_tol): surf_R3 = self.surf.eval_local(uvt[0, 0], uvt[1, 0], iu, iv) curv_R3 = self.curv.eval_local(uvt[2, 0], it) + XYZ = (surf_R3 + curv_R3)/2 dist = la.norm(surf_R3 - curv_R3) - + #print('distance =', dist) if dist <= abs_tol: conv = 1 - return conv + return conv, XYZ def get_initial_condition(self, iu, iv, it): """ @@ -224,7 +291,7 @@ def _energetic_inner_product(u, v, surf_poles): return xyz class IsecSurfSurf: - def __init__(self, surf1, surf2, nt=2, max_it=10, rel_tol = 1e-4, abs_tol = 1e-6): + def __init__(self, surf1, surf2, nt=2, max_it=10, rel_tol = 1e-6, abs_tol = 1e-4): self.surf1 = surf1 self.surf2 = surf2 self.box1, self.tree1 = self.bounding_boxes(self.surf1) @@ -255,7 +322,8 @@ def bounding_boxes(self, surf): for j in range(0, 3): patch_poles[n_points, :, i_patch] = surf.poles[iu + i, iv + j, :] n_points += 1 - assert i_patch == (iu * surf.v_basis.n_intervals + iv) + #assert i_patch == (iu * surf.v_basis.n_intervals + iv) + assert i_patch == self._patch_pos2id(surf,iu,iv) i_patch += 1 boxes = [bih.AABB(patch_poles[:, :, p].tolist()) for p in range(n_patch)] @@ -273,15 +341,64 @@ def get_intersection(self): :return: """ - point_list = self.get_intersections(self.surf1, self.surf2, self.tree2,) # patches of surf 2 with respect threads of the surface 1 + point_list1 = self.get_intersections(self.surf1, self.surf2, self.tree2,) # patches of surf 2 with respect threads of the surface 1 point_list2 = self.get_intersections(self.surf2, self.surf1, self.tree1) # patches of surf 1 with respect threads of the surface 2 - print(point_list.__len__()) - print(point_list2.__len__()) + print('points') + for point in point_list1: + print(point.uv1) + print('points2') + for point in point_list2: + print(point.uv2) + print(point_list1.__len__(),point_list2.__len__()) + + + return point_list1, point_list2 + + #print(point_list1.__len__(),point_list2.__len__()) + #connected_points = self.connect_points(point_list1,point_list2) + def connect_points(self,point_list1,point_list2): + patch_point_list1, boundary_points1 = self.make_patch_point_list(point_list1,point_list2) + patch_point_list2, boundary_points2 = self.make_patch_point_list(point_list2,point_list1) + print(boundary_points2) + + #self._make_orderings(patch_point_list1, boundary_points1,boundary_points2) + + + return patch_point_list2 + + + #@staticmethod + def make_patch_point_list(self,point_list,point_list2): + + surf1 = point_list[0].surf1 + + list_len = surf1.u_basis.n_intervals * surf1.v_basis.n_intervals + patch_points = [] + boundary_points = [] + + for i in range(list_len): + patch_points.append([[],[]]) + + + for point in point_list: + for i_patch in range(point.iu1.__len__()): + id = self._patch_pos2id(surf1,point.iu1[i_patch],point.iv1[i_patch]) + patch_points[id][0].append(point) + if point.boundary_flag == 1: + boundary_points.append(point) + + for point in point_list2: + for i_patch in range(point.iu2.__len__()): + id = self._patch_pos2id(surf1, point.iu2[i_patch], point.iv2[i_patch]) + patch_points[id][1].append(point) + + + return patch_points, boundary_points @staticmethod def _main_threads(surf,sum_idx): @@ -310,9 +427,9 @@ def _main_threads(surf,sum_idx): elif sum_idx == 1: curv_pol = poles[:, 0, :] - iw = [0] + w_val.append(0.0) - patches.append(iw) + patches.append([0]) curv = bs.Curve(curv_basis, curv_pol) curves.append(curv) @@ -333,12 +450,14 @@ def _main_threads(surf,sum_idx): curves.append(curv) if sum_idx == 0: - curv_pol = poles[fix_basis.n_intervals+1, :, :] + curv_pol = poles[poles.shape[sum_idx]-1,:, :] + #print(poles.shape[sum_idx]-1,fix_basis.n_intervals - 1) elif sum_idx == 1: - curv_pol = poles[:, fix_basis.n_intervals+1, :] + curv_pol = poles[:,poles.shape[sum_idx]-1 , :] # fix_basis.n_intervals - 1 + #print(poles.shape[sum_idx] - 1,fix_basis.n_intervals - 1) w_val.append(1.0) - patches.append(fix_basis.n_intervals-1) + patches.append([fix_basis.n_intervals-1]) curv = bs.Curve(curv_basis, curv_pol) curves.append(curv) @@ -346,6 +465,20 @@ def _main_threads(surf,sum_idx): return curves, w_val, patches + @staticmethod + def _patch_pos2id(surf,iu,iv): + + id = iu * surf.v_basis.n_intervals + iv + return id + + @staticmethod + def _patch_id2pos(surf,id): + + iu = int(np.floor(id / surf.v_basis.n_intervals)) + iv = int(id - (iu * surf.v_basis.n_intervals)) + + return iu, iv + def get_intersections(self,surf1,surf2,tree2): """ Tries to compute intersection of the main curves from surface1 and patches of the surface2 which have a @@ -357,37 +490,71 @@ def get_intersections(self,surf1,surf2,tree2): """ point_list = [] + crossing = np.zeros([surf1.u_basis.n_intervals+1,surf1.v_basis.n_intervals+1]) + patch_points = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals,4]) + for sum_idx in range(2): curves, w_val, patches = self._main_threads(surf1, sum_idx) curve_id = -1 for curve in curves: + lastflag = 0 curve_id += 1 + interval_id = -1 + #conv = 0 for it in range(curve.basis.n_intervals): + interval_id += 1 curv_surf_isec = IsecCurveSurf(surf2, curve) boxes = bih.AABB(curve.poles[it:it+3, :].tolist()) uv1 = np.zeros([2,1]) + #print(w_val[curve_id]) uv1[sum_idx,0] = w_val[curve_id] intersectioned_patches2 = tree2.find_box(boxes) + print("curve_id=", curve_id) + print("sum_idx=", sum_idx) + #print("intersectioned_patches2=",intersectioned_patches2) for ipatch2 in intersectioned_patches2: - iu2 = int(np.floor(ipatch2 / surf2.v_basis.n_intervals)) - iv2 = int(ipatch2 - (iu2 * surf2.v_basis.n_intervals)) + iu2, iv2 = self._patch_id2pos(surf2,ipatch2) uvt = curv_surf_isec.get_initial_condition(iu2, iv2, it) - uvt, conv = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, self.rel_tol, self.abs_tol) - # boundary flag set + uvt, conv, flag, XYZ = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, self.rel_tol, self.abs_tol) if conv == 1: - #distance control - uv1[1-sum_idx,0] = uvt[2,0] - if sum_idx == 0: - point = IsecPoint(surf1, patches[curve_id], it, uv1, surf2, iu2, iv2, uvt[0:1,:]) - elif sum_idx == 1: - point = IsecPoint(surf1, it,patches[curve_id], uv1, surf2, iu2, iv2, uvt[0:1, :]) + if self._already_found(crossing,interval_id,flag,curve_id,sum_idx) == 0: # not to add the same point duplicitly (when v is fixed) + print('uvt=', uv1[sum_idx,0],uvt[2,0]) + print('flag,lastflag', flag,lastflag) + print('it=',it) + if np.logical_and(flag == 0, lastflag == 1) == 1: # not to add the same point duplicitly (when u is fixed) + print('not add') + point_list[- 1].add_patches(sum_idx) # extend point data + crossing[curve_id,interval_id+flag] = 1 # crossing intersection mark + else: # add point + uv1[1-sum_idx,0] = uvt[2,0] + if np.logical_or(np.logical_and(flag == 0, it == 0),np.logical_and(flag == 1, it == curve.basis.n_intervals-1)): + boundary_flag = 1 + else: + boundary_flag = 0 + if sum_idx == 0: + # [it] + point = IsecPoint(surf1, patches[curve_id], [it,it], uv1, boundary_flag,surf2, [iu2], [iv2], uvt[0:2,:],XYZ) + elif sum_idx == 1: + point = IsecPoint(surf1, [it,it],patches[curve_id], uv1, boundary_flag,surf2, [iu2], [iv2], uvt[0:2, :],XYZ) + point_list.append(point) + lastflag = flag + return point_list - point_list.append(point) + @staticmethod + def _already_found(crossing,interval_id,flag,curve_id,sum_idx): + + found = 0 + + if np.logical_or(flag ==0,flag == 1) == 1: + if np.logical_and(crossing[interval_id+flag,curve_id] == 1,sum_idx == 1) == 1: + found = 1 + + return found - return point_list +# np.logical_not(np.logical_and(crossing[interval_id+flag,curve_id] == 1,sum_idx == 1)) ''' Calculation and representation of intersection of two B-spline surfaces. diff --git a/tests/test_isec.py b/tests/test_isec.py index d012b18..bc5de20 100644 --- a/tests/test_isec.py +++ b/tests/test_isec.py @@ -31,18 +31,20 @@ def function2(x): u_basis = bs.SplineBasis.make_equidistant(2, u1_int) #10 v_basis = bs.SplineBasis.make_equidistant(2, v1_int) #15 + u2_basis = bs.SplineBasis.make_equidistant(2, u2_int) #10 + v2_basis = bs.SplineBasis.make_equidistant(2, v2_int) #15 poles = bs.make_function_grid(function, u1_int + 2, v1_int + 2) #12, 17 surface_extrude = bs.Surface((u_basis, v_basis), poles) myplot = bp.Plotting((bp.PlottingPlotly())) myplot.plot_surface_3d(surface_extrude, poles = False) poles2 = bs.make_function_grid(function2, u2_int + 2, v2_int + 2) #12, 17 - surface_extrude2 = bs.Surface((u_basis, v_basis), poles2) + surface_extrude2 = bs.Surface((u2_basis, v2_basis), poles2) myplot.plot_surface_3d(surface_extrude2, poles=False) - #myplot.show() # view - return surface_extrude, surface_extrude2 + + return surface_extrude, surface_extrude2, myplot @@ -70,12 +72,39 @@ def function2(x): def test_isec(self): - surf1, surf2 = self.plot_extrude() + surf1, surf2, myplot = self.plot_extrude() isec = iss.IsecSurfSurf(surf1, surf2) #box1, tree1 = isec.bounding_boxes(surf1) #box2, tree2 = isec.bounding_boxes(surf2) #isec.get_intersection(surf1,surf2,tree1,tree2,box1,box2,isec.nt,isec.nit) # surf1,surf2,tree1,tree2 - isec.get_intersection() + point_list1, point_list2 = isec.get_intersection() + + m = point_list1.__len__() + point_list2.__len__() + + X= np.zeros([m]) + Y = np.zeros([m]) + Z = np.zeros([m]) + + i = -1 + + for point in point_list1: + i += 1 + X[i] = point.R3_coor[0] + Y[i]= point.R3_coor[1] + Z[i]= point.R3_coor[2] + + for point in point_list2: + i += 1 + X[i] = point.R3_coor[0] + Y[i]= point.R3_coor[1] + Z[i]= point.R3_coor[2] + + + + myplot.scatter_3d(X, Y, Z) + + + myplot.show() # view #print(tree1.find_box(boxes2[0])) #print(surf1.poles[:,:,1]) From fbd41228e93d6aa5a20dd943dc7c2b0a1e032166 Mon Sep 17 00:00:00 2001 From: jiri Date: Thu, 15 Feb 2018 00:48:52 +0100 Subject: [PATCH 66/67] working version, intersection duplicities reduced (still some minor fix needed), computational cost reduced --- src/isec_surf_surf.py | 89 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 07b096d..4e0d429 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -34,18 +34,20 @@ def __init__(self, surf1, iu1, iv1, uv1, boundary_flag,surf2, iu2, iv2, uv2,XYZ) self.R3_coor = XYZ - def add_patches(self, sum_idx): + def add_patches(self, sum_idx,flag): n = self.iu1.__len__() + direction = 2*flag -1 + if sum_idx == 0: for i in range(n): - self.iu1.append(self.iu1[0]+1) + self.iu1.append(self.iu1[0]+direction) self.iv1.append(self.iv1[i]) elif sum_idx == 1: for i in range(n): self.iu1.append(self.iu1[i]) - self.iv1.append(self.iv1[0]+1) + self.iv1.append(self.iv1[0]+direction) class IsecCurveSurf: """ @@ -159,9 +161,9 @@ def _boundary_intersection(t,ti): :param t: parameter value :param it: interval array (2x1) :return: - flag as "0" corresponds to lower bound of the curve + flag as "-1" corresponds to lower bound of the curve "1" corresponds to upper bound of the curve - "-1" corresponds to the boundary points of the curve + "0" corresponds to the boundary points of the curve """ if ti[0] == t: @@ -491,23 +493,28 @@ def get_intersections(self,surf1,surf2,tree2): point_list = [] crossing = np.zeros([surf1.u_basis.n_intervals+1,surf1.v_basis.n_intervals+1]) - patch_points = np.zeros([surf1.u_basis.n_intervals,surf1.v_basis.n_intervals,4]) for sum_idx in range(2): + if sum_idx == 1: + print(crossing) curves, w_val, patches = self._main_threads(surf1, sum_idx) curve_id = -1 for curve in curves: - lastflag = 0 curve_id += 1 interval_id = -1 - #conv = 0 for it in range(curve.basis.n_intervals): interval_id += 1 + if self._already_found(crossing,interval_id,curve_id,sum_idx) == 1: + #print('continue') + continue curv_surf_isec = IsecCurveSurf(surf2, curve) boxes = bih.AABB(curve.poles[it:it+3, :].tolist()) uv1 = np.zeros([2,1]) - #print(w_val[curve_id]) + if np.logical_or(it == 0, it == curve.basis.n_intervals - 1): + interval_list = [it] + else: + interval_list = [it, it] uv1[sum_idx,0] = w_val[curve_id] intersectioned_patches2 = tree2.find_box(boxes) print("curve_id=", curve_id) @@ -518,37 +525,51 @@ def get_intersections(self,surf1,surf2,tree2): uvt = curv_surf_isec.get_initial_condition(iu2, iv2, it) uvt, conv, flag, XYZ = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, self.rel_tol, self.abs_tol) if conv == 1: - if self._already_found(crossing,interval_id,flag,curve_id,sum_idx) == 0: # not to add the same point duplicitly (when v is fixed) - print('uvt=', uv1[sum_idx,0],uvt[2,0]) - print('flag,lastflag', flag,lastflag) - print('it=',it) - if np.logical_and(flag == 0, lastflag == 1) == 1: # not to add the same point duplicitly (when u is fixed) - print('not add') - point_list[- 1].add_patches(sum_idx) # extend point data - crossing[curve_id,interval_id+flag] = 1 # crossing intersection mark - else: # add point - uv1[1-sum_idx,0] = uvt[2,0] - if np.logical_or(np.logical_and(flag == 0, it == 0),np.logical_and(flag == 1, it == curve.basis.n_intervals-1)): - boundary_flag = 1 - else: - boundary_flag = 0 - if sum_idx == 0: - # [it] - point = IsecPoint(surf1, patches[curve_id], [it,it], uv1, boundary_flag,surf2, [iu2], [iv2], uvt[0:2,:],XYZ) - elif sum_idx == 1: - point = IsecPoint(surf1, [it,it],patches[curve_id], uv1, boundary_flag,surf2, [iu2], [iv2], uvt[0:2, :],XYZ) - point_list.append(point) - lastflag = flag + #check second surf + #else break + if np.logical_or(np.logical_and(flag == 0, it == 0), + np.logical_and(flag == 1, it == curve.basis.n_intervals - 1)): + boundary_flag = 1 + else: + boundary_flag = 0 + + uv1[1 - sum_idx, 0] = uvt[2, 0] + if sum_idx == 0: + point = IsecPoint(surf1, patches[curve_id], interval_list, uv1, boundary_flag, surf2, + [iu2], [iv2], uvt[0:2, :], XYZ) + elif sum_idx == 1: + point = IsecPoint(surf1, interval_list, patches[curve_id], uv1, boundary_flag, surf2, + [iu2], [iv2], uvt[0:2, :], XYZ) + point_list.append(point) + + if boundary_flag == 0: + point_list[- 1].add_patches(sum_idx, flag) # move to constructor + + if np.logical_or(flag == 0,flag == 1): + if sum_idx == 0: + crossing[curve_id, interval_id + flag] = 1 + break + elif sum_idx == 1: + crossing[interval_id + flag,curve_id] = 1 # xxx + break + + return point_list @staticmethod - def _already_found(crossing,interval_id,flag,curve_id,sum_idx): + def _already_found(crossing,interval_id,curve_id,sum_idx): found = 0 - if np.logical_or(flag ==0,flag == 1) == 1: - if np.logical_and(crossing[interval_id+flag,curve_id] == 1,sum_idx == 1) == 1: - found = 1 + + if sum_idx == 0: + for i in range(2): + if crossing[interval_id + i,curve_id] == 1: + found = 1 + elif sum_idx == 1: + for i in range(2): + if crossing[curve_id, interval_id + i] == 1: + found = 1 return found From 3752fd60418fc1b413b866f9a6ee6d8cdf3a30d9 Mon Sep 17 00:00:00 2001 From: jiri Date: Mon, 9 Jul 2018 14:26:58 +0200 Subject: [PATCH 67/67] working version, connection of the points in progress --- src/isec_surf_surf.py | 316 ++++++++++++++++++++++++++++++++---------- 1 file changed, 239 insertions(+), 77 deletions(-) diff --git a/src/isec_surf_surf.py b/src/isec_surf_surf.py index 4e0d429..ee49352 100644 --- a/src/isec_surf_surf.py +++ b/src/isec_surf_surf.py @@ -15,7 +15,7 @@ class IsecPoint: """ Point as the result of intersection with correspondind coordinates on both surfaces """ - def __init__(self, surf1, iu1, iv1, uv1, boundary_flag,surf2, iu2, iv2, uv2,XYZ): + def __init__(self, surf1, iu1, iv1, uv1, boundary_flag, flag ,sum_idx, surf2, iu2, iv2, uv2,XYZ): self.surf1 = surf1 self.iu1 = iu1 @@ -30,9 +30,63 @@ def __init__(self, surf1, iu1, iv1, uv1, boundary_flag,surf2, iu2, iv2, uv2,XYZ) self.uv2 = uv2 - self.tol = 1 + self.tol = 1 # implement self.R3_coor = XYZ + self.connected = 0 + + + if boundary_flag == 1: + self.add_patches(sum_idx,flag[2]) + + if np.logical_or(flag[0]!=-1,flag[1]!=-1): + self.extend_patches( flag[0:2]) + + # flag + + def belongs_to(self,iu,iv,isurf): + + belongs = 0 + if isurf == 0: + for i in range(self.iu1.__len__): + if (np.logical_and(self.iu1[i] == iu),np.logical_and(self.iv1[i] == iv)): + belongs = 1 + break + + return belongs + + #def duplicite(self,point): + # duplicite = 0 + + # if point.iu1.__len__ + + + + + + + # return duplicite + + + def connect(self): + self.connected = 1 + + def extend_patches(self, flag): + + if flag[0]!=-1: + direction_u = 2 * flag[0] - 1 + self.iu2.append(self.iu2[0] + direction_u) + self.iv2.append(self.iv2[0]) + + if flag[1]!=-1: + direction_v = 2 * flag[1] - 1 + self.iu2.append(self.iu2[0]) + self.iv2.append(self.iv2[0]+ direction_v) + + if np.logical_and(flag[0]!=-1,flag[1]!=-1): + self.iu2.append(self.iu2[0]+ direction_u) + self.iv2.append(self.iv2[0]+ direction_v) + def add_patches(self, sum_idx,flag): @@ -115,66 +169,79 @@ def get_intersection(self, uvt, iu, iv, it, max_it, rel_tol, abs_tol): "1" if the methog achive desired accuracy flag as intersection specification """ - conv = 0 - flag = -1 + #flag = -1 + flag = -np.ones([3],'int') ui = self._compute_bounds(self.surf.u_basis.knots, iu) vi = self._compute_bounds(self.surf.v_basis.knots, iv) ti = self._compute_bounds(self.curv.basis.knots, it) - #print('IC=',uvt) for i in range(max_it): J, deltaXYZ = self._compute_jacobian_and_delta(uvt, iu, iv, it) + if la.norm(deltaXYZ) < abs_tol: + break uvt = uvt - la.solve(J, deltaXYZ) - #print('new') - #print(uvt) - test, uvt = self._range_test(uvt, ui, vi, ti, 0.0) - - #print(uvt) - #print( test) + uvt = self._range_test(uvt, ui, vi, ti, 0.0) - - - #print(ui,vi,ti) - #print(test) - #print(uvt) - test, uvt = self._range_test(uvt, ui, vi, ti, rel_tol) - #print(test) - #print(uvt) - # abs_tol - if test == 1: - conv, XYZ = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) - #print(conv) + conv, XYZ = self._test_intesection_tolerance(uvt, iu, iv, it, abs_tol) if conv == 1: - flag = self._boundary_intersection(uvt[2,0],ti) + flag[0] = self._patch_boundary_intersection(uvt[0, 0], ui, rel_tol) + flag[1] = self._patch_boundary_intersection(uvt[1, 0], vi, rel_tol) + flag[2] = self._curve_boundary_intersection(uvt[2, 0], ti, rel_tol) + - #print('conv,flag =',conv,flag) - #print('end') return uvt, conv, flag, XYZ @staticmethod - def _boundary_intersection(t,ti): + def _curve_boundary_intersection(t,ti,rel_tol): """ :param t: parameter value :param it: interval array (2x1) :return: - flag as "-1" corresponds to lower bound of the curve + flag as "0" corresponds to lower bound of the curve "1" corresponds to upper bound of the curve - "0" corresponds to the boundary points of the curve + "-1" corresponds to the interior points of the curve """ - - if ti[0] == t: + # interior boundary + if abs(ti[0] - t) < rel_tol: flag = 0 - elif ti[1] == t: + elif abs(ti[1] - t) < rel_tol: flag = 1 else: flag = -1 return flag + @staticmethod + def _patch_boundary_intersection(t,ti,rel_tol): + """ + + :param t: parameter value + :param it: interval array (2x1) + :return: + flag as "-1" corresponds to lower bound of the curve + "1" corresponds to upper bound of the curve + "0" corresponds to the boundary points of the curve + """ + # interior boundary + + flag = -1 + + if ti[0] != 0: + if abs(ti[0] - t) < rel_tol: + flag = 0 + elif ti[1] != 1: + if abs(ti[1] - t) < rel_tol: + flag = 1 + #else: + # flag = -1 + + return flag + + @staticmethod def _compute_bounds(knots, idx): """ @@ -203,28 +270,28 @@ def _range_test(uvt, ui, vi, ti, rel_tol): test = 0 - du = np.array([uvt[0, 0] - ui[0], ui[1] - uvt[0, 0]]) - dv = np.array([uvt[1, 0] - vi[0], vi[1] - uvt[1, 0]]) - dt = np.array([uvt[2, 0] - ti[0], ti[1] - uvt[2, 0]]) + du = np.array([ ui[0] - uvt[0, 0],uvt[0, 0] - ui[1]]) + dv = np.array([ vi[0] - uvt[1, 0],uvt[1, 0] - vi[1]]) + dt = np.array([ ti[0] - uvt[2, 0],uvt[2, 0] - ti[1]]) for i in range(0, 2): - if (du[i] < -rel_tol): + if (du[i] > rel_tol): uvt[0, 0] = ui[i] for i in range(0, 2): - if (dv[i] < -rel_tol): + if (dv[i] > rel_tol): uvt[1, 0] = vi[i] for i in range(0, 2): - if (dt[i] < -rel_tol): + if (dt[i] > rel_tol): uvt[2, 0] = ti[i] - if np.logical_and(uvt[0, 0] >= ui[0], uvt[0, 0] <= ui[1]): - if np.logical_and(uvt[1, 0] >= vi[0], uvt[1, 0] <= vi[1]): - if np.logical_and(uvt[2, 0] >= ti[0], uvt[2, 0] <= ti[1]): - test = 1 + #if np.logical_and(uvt[0, 0] >= ui[0], uvt[0, 0] <= ui[1]): + # if np.logical_and(uvt[1, 0] >= vi[0], uvt[1, 0] <= vi[1]): + # if np.logical_and(uvt[2, 0] >= ti[0], uvt[2, 0] <= ti[1]): + # test = 1 - return test, uvt + return uvt def _test_intesection_tolerance(self, uvt, iu, iv, it, abs_tol): """ @@ -293,7 +360,7 @@ def _energetic_inner_product(u, v, surf_poles): return xyz class IsecSurfSurf: - def __init__(self, surf1, surf2, nt=2, max_it=10, rel_tol = 1e-6, abs_tol = 1e-4): + def __init__(self, surf1, surf2, nt=2, max_it=10, rel_tol = 1e-16, abs_tol = 1e-14): self.surf1 = surf1 self.surf2 = surf2 self.box1, self.tree1 = self.bounding_boxes(self.surf1) @@ -343,7 +410,8 @@ def get_intersection(self): :return: """ - point_list1 = self.get_intersections(self.surf1, self.surf2, self.tree2,) # patches of surf 2 with respect threads of the surface 1 + + point_list1 = self.get_intersections(self.surf1, self.surf2, self.tree2) # patches of surf 2 with respect threads of the surface 1 point_list2 = self.get_intersections(self.surf2, self.surf1, self.tree1) # patches of surf 1 with respect threads of the surface 2 print('points') @@ -354,26 +422,111 @@ def get_intersection(self): print(point.uv2) print(point_list1.__len__(),point_list2.__len__()) + assert point_list1.__len__() == 9 + assert point_list2.__len__() == 9 - return point_list1, point_list2 + return point_list1, point_list2 - #print(point_list1.__len__(),point_list2.__len__()) - #connected_points = self.connect_points(point_list1,point_list2) + connected_points = self._connect_points(point_list1,point_list2) - def connect_points(self,point_list1,point_list2): + def _connect_points(self,point_list1,point_list2): patch_point_list1, boundary_points1 = self.make_patch_point_list(point_list1,point_list2) patch_point_list2, boundary_points2 = self.make_patch_point_list(point_list2,point_list1) print(boundary_points2) - #self._make_orderings(patch_point_list1, boundary_points1,boundary_points2) + patch_point_list = [[],[]] + boundary_points = [[],[]] + + patch_point_list[0] = patch_point_list1 # append + patch_point_list[1] = patch_point_list2 + boundary_points[0] = boundary_points1 + boundary_points[1] = boundary_points2 + + + #line = self._make_orderings(patch_point_list, boundary_points) + + + return patch_point_list2 + def _make_orderings(self,patch_point_list, boundary_points): + + n_curves = 0 + + line = [] + + n_intersections = patch_point_list[0].__len__() + patch_point_list[1].__len__() + #get_start + #connect + #test end + +# while n_intersections != 0: +# for i in range(0,2): +# for patchpoint in boundary_points[i]: +# if patchpoint.connected == 0: +# patchpoint.connect() +# n_intersections -= 1 +# line[n_curves].append(patchpoint) +# while end_found == 0: +# #id = self._patch_pos2id(patchpoint.surf2,patchpoint.iu2[k],patchpoint.iv2[k]) +# pt = np.zeros([patchpoint.iu1.___len___]) +# for k in range(patchpoint.iu1.___len___): +# ids = self._patch_pos2id(patchpoint.surf1,patchpoint.iu1[k],patchpoint.iv1[k]) +# for pp in patch_point_list[1-i][ids]: +# if pp.connected == 0: +# pt[k] += 1 +# if np.sum(pt) > 1: +# print('problem') +# else: +# +# #found_start +# end_found = 0 +# while end_found == 0: +# #search next +# check_duplicities + + + + while n_intersections != 0: + for i in range(0,2): + for patchpoint in patch_point_list[i]: + if np.logical_and(patchpoint.connected == 0, patchpoint.boundary_flag == 1 ): + patchpoint.connect() + n_intersections -= 1 + line[n_curves].append(patchpoint) + while end_found == 0: + #id = self._patch_pos2id(patchpoint.surf2,patchpoint.iu2[k],patchpoint.iv2[k]) + pt = np.zeros([patchpoint.iu1.___len___]) + for k in range(patchpoint.iu1.___len___): + ids = self._patch_pos2id(patchpoint.surf1,patchpoint.iu1[k],patchpoint.iv1[k]) + for pp in patch_point_list[1-i][ids]: + if pp.connected == 0: + pt[k] += 1 + if np.sum(pt) > 1: + print('problem') + else: + + #found_start + end_found = 0 + while end_found == 0: + #search next + check_duplicities + + + + + n_curves += 1 + + + + return line + #@staticmethod def make_patch_point_list(self,point_list,point_list2): @@ -383,22 +536,29 @@ def make_patch_point_list(self,point_list,point_list2): patch_points = [] boundary_points = [] + boundary_points.append([[],[]]) + + for i in range(list_len): patch_points.append([[],[]]) - + idp = -1 for point in point_list: + idp += 1 for i_patch in range(point.iu1.__len__()): id = self._patch_pos2id(surf1,point.iu1[i_patch],point.iv1[i_patch]) - patch_points[id][0].append(point) + patch_points[id][0].append(idp) if point.boundary_flag == 1: - boundary_points.append(point) + boundary_points[0].append(idp) + idp = -1 for point in point_list2: + idp += 1 for i_patch in range(point.iu2.__len__()): id = self._patch_pos2id(surf1, point.iu2[i_patch], point.iv2[i_patch]) - patch_points[id][1].append(point) - + patch_points[id][1].append(idp) + if point.boundary_flag == 1: + boundary_points[1].append(idp) return patch_points, boundary_points @@ -429,14 +589,12 @@ def _main_threads(surf,sum_idx): elif sum_idx == 1: curv_pol = poles[:, 0, :] - w_val.append(0.0) patches.append([0]) curv = bs.Curve(curv_basis, curv_pol) curves.append(curv) - for iw in range(1,fix_basis.n_intervals): w1f = fix_basis.eval_vector(iw, fix_basis.knots[iw + 2]) if sum_idx == 0: @@ -466,7 +624,6 @@ def _main_threads(surf,sum_idx): return curves, w_val, patches - @staticmethod def _patch_pos2id(surf,iu,iv): @@ -487,14 +644,13 @@ def get_intersections(self,surf1,surf2,tree2): non-empty intersection of corresponding bonding boxes :param surf1: Surface used to construction of the main threads :param surf2: Intersected surface - :param tree2: Bih tree of the patchces of the surface 2 + :param tree2: Bih tree of the patches of the surface 2 :return: point_list as list of points of intersection """ point_list = [] crossing = np.zeros([surf1.u_basis.n_intervals+1,surf1.v_basis.n_intervals+1]) - for sum_idx in range(2): if sum_idx == 1: print(crossing) @@ -507,6 +663,8 @@ def get_intersections(self,surf1,surf2,tree2): interval_id += 1 if self._already_found(crossing,interval_id,curve_id,sum_idx) == 1: #print('continue') + #print(crossing) + #print(point.R3_coor) continue curv_surf_isec = IsecCurveSurf(surf2, curve) boxes = bih.AABB(curve.poles[it:it+3, :].tolist()) @@ -517,41 +675,45 @@ def get_intersections(self,surf1,surf2,tree2): interval_list = [it, it] uv1[sum_idx,0] = w_val[curve_id] intersectioned_patches2 = tree2.find_box(boxes) - print("curve_id=", curve_id) - print("sum_idx=", sum_idx) + #print("curve_id=", curve_id) + #print("sum_idx=", sum_idx) #print("intersectioned_patches2=",intersectioned_patches2) for ipatch2 in intersectioned_patches2: iu2, iv2 = self._patch_id2pos(surf2,ipatch2) uvt = curv_surf_isec.get_initial_condition(iu2, iv2, it) - uvt, conv, flag, XYZ = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, self.rel_tol, self.abs_tol) + uvt, conv, flag, XYZ = curv_surf_isec.get_intersection(uvt, iu2, iv2, it, self.max_it, + self.rel_tol, self.abs_tol) if conv == 1: #check second surf #else break - if np.logical_or(np.logical_and(flag == 0, it == 0), - np.logical_and(flag == 1, it == curve.basis.n_intervals - 1)): + if np.logical_or(np.logical_and(flag[2] == 0, it == 0), + np.logical_and(flag[2] == 1, it == curve.basis.n_intervals - 1)): boundary_flag = 1 else: boundary_flag = 0 uv1[1 - sum_idx, 0] = uvt[2, 0] if sum_idx == 0: - point = IsecPoint(surf1, patches[curve_id], interval_list, uv1, boundary_flag, surf2, - [iu2], [iv2], uvt[0:2, :], XYZ) + point = IsecPoint(surf1, patches[curve_id], interval_list, uv1, boundary_flag, flag, + sum_idx, surf2, [iu2], [iv2], uvt[0:2, :], XYZ) elif sum_idx == 1: - point = IsecPoint(surf1, interval_list, patches[curve_id], uv1, boundary_flag, surf2, - [iu2], [iv2], uvt[0:2, :], XYZ) - point_list.append(point) + point = IsecPoint(surf1, interval_list, patches[curve_id], uv1, boundary_flag, flag, + sum_idx,surf2, [iu2], [iv2], uvt[0:2, :], XYZ) - if boundary_flag == 0: - point_list[- 1].add_patches(sum_idx, flag) # move to constructor + point_list.append(point) - if np.logical_or(flag == 0,flag == 1): + #if boundary_flag == 0: + # point_list[- 1].add_patches(sum_idx, flag[2]) # move to constructor + #print('flag') + #print(flag.shape) + #print(flag[2]) + if np.logical_or(flag[2] == 0,flag[2] == 1): if sum_idx == 0: - crossing[curve_id, interval_id + flag] = 1 - break + crossing[curve_id, interval_id + flag[2]] = 1 elif sum_idx == 1: - crossing[interval_id + flag,curve_id] = 1 # xxx - break + crossing[interval_id + flag[2],curve_id] = 1 # xxx + break + return point_list