Repository: pyvista/fast-simplification
Branch: main
Commit: 05315c72f401
Files: 36
Total size: 119.9 KB
Directory structure:
gitextract_q5jmbpat/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── testing-and-deployment.yml
├── .gitignore
├── .pre-commit-config.yaml
├── LICENSE
├── MANIFEST.in
├── README.rst
├── doc/
│ ├── Makefile
│ ├── _templates/
│ │ └── autosummary/
│ │ └── class.rst
│ ├── api.rst
│ ├── conf.py
│ └── index.rst
├── examples/
│ ├── README.txt
│ ├── replay.py
│ └── simplify.py
├── fast_simplification/
│ ├── Replay.h
│ ├── Simplify.h
│ ├── __init__.py
│ ├── _replay.pyx
│ ├── _simplify.pyx
│ ├── _version.py
│ ├── fast_simplification.py
│ ├── replay.py
│ ├── simplify.py
│ ├── utils.py
│ ├── wrapper.h
│ └── wrapper_replay.h
├── pyproject.toml
├── pytest.ini
├── requirements_docs.txt
├── requirements_test.txt
├── setup.py
├── tests/
│ ├── test_map_isolated_points.py
│ ├── test_replay.py
│ └── test_simplify.py
└── tools/
└── audit_wheel.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
directory: /
insecure-external-code-execution: allow
schedule:
interval: monthly
open-pull-requests-limit: 100
labels:
- maintenance
- dependencies
groups:
pip:
patterns:
- '*'
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
open-pull-requests-limit: 100
labels:
- maintenance
- dependencies
groups:
actions:
patterns:
- '*'
================================================
FILE: .github/workflows/testing-and-deployment.yml
================================================
name: Build
on:
pull_request:
push:
tags:
- v*
branches:
- main
# disable concurrent runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# fail fast and early to avoid clogging GH Actions
smoke_testing:
runs-on: ubuntu-latest
name: Source distribution testing
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Setup headless display
uses: pyvista/setup-headless-display-action@v4
- name: Build and validate wheel
run: |
pip install build twine
python -m build
twine check dist/*
- name: Install
run: pip install dist/*.whl
- name: Test
run: |
pip install -r requirements_test.txt
cd tests && python -m pytest -v
- name: Upload sdist
uses: actions/upload-artifact@v7
with:
path: ./dist/*.tar.gz
name: fast-simplification-sdist
docs_build:
name: Build Documentation
runs-on: ubuntu-latest
needs: smoke_testing
steps:
- uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
- name: Setup headless display
uses: pyvista/setup-headless-display-action@v4
- name: Install library
run: pip install .
- name: Build Documentation
run: |
pip install -r requirements_docs.txt
make -C doc html
- name: Deploy on tag
uses: JamesIves/github-pages-deploy-action@v4
if: startsWith(github.ref, 'refs/tags/')
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: gh-pages
folder: doc/_build/html
build_wheels:
needs: smoke_testing
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-latest, macos-15-intel]
steps:
- uses: actions/checkout@v6
- name: Build wheels
uses: pypa/cibuildwheel@v3.4.0
- name: List generated wheels
run: ls ./wheelhouse/*
- uses: actions/upload-artifact@v7
with:
path: ./wheelhouse/*.whl
name: fast-simplification-wheel-${{ matrix.os }}
release:
name: Release
if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
needs: [build_wheels, docs_build]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/fast-simplification
permissions:
id-token: write # this permission is mandatory for trusted publishing
contents: write # required to create a release
steps:
- uses: actions/download-artifact@v8
- name: Flatten directory structure
run: |
mkdir -p dist/
find . -name '*.whl' -exec mv {} dist/ \;
find . -name '*.tar.gz' -exec mv {} dist/ \;
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
./**/*.whl
================================================
FILE: .gitignore
================================================
# Compiled source #
###################
*.pyc
*.pyd
*.so
*.o
__pycache__/
# Pip
*.egg-info
# Cython generated files #
fast_simplification/_simplify.cpp
fast_simplification/_replay.cpp
# documentation
doc/_build/
doc/examples/
doc/_autosummary/
dist/
venv/
build/
# IDE
.vscode/
================================================
FILE: .pre-commit-config.yaml
================================================
# Integration with GitHub Actions
# See https://pre-commit.ci/
ci:
autoupdate_schedule: monthly
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.9
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
exclude: ^(docs/|tests)
- id: ruff-format
- repo: https://github.com/pycqa/isort
rev: 8.0.1
hooks:
- id: isort
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
args: [--toml, pyproject.toml]
additional_dependencies: [tomli]
- repo: https://github.com/keewis/blackdoc
rev: v0.4.6
hooks:
- id: blackdoc
files: \.py$
# - repo: https://github.com/pycqa/pydocstyle
# rev: 6.1.1
# hooks:
# - id: pydocstyle
# additional_dependencies: [toml]
# exclude: "tests/"
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-merge-conflict
- id: debug-statements
- id: trailing-whitespace
- id: no-commit-to-branch
args: [--branch, main]
- id: requirements-txt-fixer
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.16.0
hooks:
- id: pretty-format-toml
args: [--autofix]
- id: pretty-format-yaml
args: [--autofix, --indent, '2']
# this validates our github workflow files
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.1
hooks:
- id: check-github-workflows
================================================
FILE: LICENSE
================================================
The MIT License
Copyright (c) 2017-2021 The PyVista Developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include fast_simplification/*.h
================================================
FILE: README.rst
================================================
Python Fast-Quadric-Mesh-Simplification Wrapper
===============================================
This is a python wrapping of the `Fast-Quadric-Mesh-Simplification Library
`_. Having
arrived at the same problem as the original author, but needing a Python
library, this project seeks to extend the work of the original library while
adding integration to Python and the `PyVista
`_ project.
For the full documentation visit: https://pyvista.github.io/fast-simplification/
.. image:: https://github.com/pyvista/fast-simplification/raw/main/doc/images/simplify_demo.png
Installation
------------
Fast Simplification can be installed from PyPI using pip on Python >= 3.7::
pip install fast-simplification
See the `Contributing `_ for more details regarding development or if the installation through pip doesn't work out.
Basic Usage
-----------
The basic interface is quite straightforward and can work directly
with arrays of points and triangles:
.. code:: python
points = [[ 0.5, -0.5, 0.0],
[ 0.0, -0.5, 0.0],
[-0.5, -0.5, 0.0],
[ 0.5, 0.0, 0.0],
[ 0.0, 0.0, 0.0],
[-0.5, 0.0, 0.0],
[ 0.5, 0.5, 0.0],
[ 0.0, 0.5, 0.0],
[-0.5, 0.5, 0.0]]
faces = [[0, 1, 3],
[4, 3, 1],
[1, 2, 4],
[5, 4, 2],
[3, 4, 6],
[7, 6, 4],
[4, 5, 7],
[8, 7, 5]]
points_out, faces_out = fast_simplification.simplify(points, faces, 0.5)
Advanced Usage
--------------
This library supports direct integration with VTK through PyVista to
provide a simplistic interface to the library. As this library
provides a 4-5x improvement to the VTK decimation algorithms.
.. code:: python
>>> from pyvista import examples
>>> mesh = examples.download_nefertiti()
>>> out = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)
Compare with built-in VTK/PyVista methods:
>>> fas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=0.9)
>>> dec_std = mesh.decimate(0.9) # vtkQuadricDecimation
>>> dec_pro = mesh.decimate_pro(0.9) # vtkDecimatePro
>>> pv.set_plot_theme('document')
>>> pl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000))
>>> pl.add_text('Original', 'upper_right', color='w')
>>> pl.add_mesh(mesh, show_edges=True)
>>> pl.camera_position = cpos
>>> pl.subplot(0, 1)
>>> pl.add_text(
... 'Fast-Quadric-Mesh-Simplification\n~2.2 seconds', 'upper_right', color='w'
... )
>>> pl.add_mesh(fas_sim, show_edges=True)
>>> pl.camera_position = cpos
>>> pl.subplot(1, 0)
>>> pl.add_mesh(dec_std, show_edges=True)
>>> pl.add_text(
... 'vtkQuadricDecimation\n~9.5 seconds', 'upper_right', color='w'
... )
>>> pl.camera_position = cpos
>>> pl.subplot(1, 1)
>>> pl.add_mesh(dec_pro, show_edges=True)
>>> pl.add_text(
... 'vtkDecimatePro\n11.4~ seconds', 'upper_right', color='w'
... )
>>> pl.camera_position = cpos
>>> pl.show()
Comparison to other libraries
-----------------------------
The `pyfqmr `_
library wraps the same header file as this library and has similar capabilities.
In this library, the decision was made to write the Cython layer on top of an
additional C++ layer rather than directly interfacing with wrapper from Cython.
This results in a mild performance improvement.
Reusing the example above:
.. code:: python
Set up a timing function.
>>> import pyfqmr
>>> vertices = mesh.points
>>> faces = mesh.faces.reshape(-1, 4)[:, 1:]
>>> def time_pyfqmr():
... mesh_simplifier = pyfqmr.Simplify()
... mesh_simplifier.setMesh(vertices, faces)
... mesh_simplifier.simplify_mesh(
... target_count=out.n_faces, aggressiveness=7, verbose=0
... )
... vertices_out, faces_out, normals_out = mesh_simplifier.getMesh()
... return vertices_out, faces_out, normals_out
Now, time it and compare with the non-VTK API of this library:
.. code:: python
>>> timeit time_pyfqmr()
2.75 s ± 5.35 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> timeit vout, fout = fast_simplification.simplify(vertices, faces, 0.9)
2.05 s ± 3.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Additionally, the ``fast-simplification`` library has direct plugins
to the ``pyvista`` library, making it easy to read and write meshes:
.. code:: python
>>> import pyvista
>>> import fast_simplification
>>> mesh = pyvista.read('my_mesh.stl')
>>> simple = fast_simplification.simplify_mesh(mesh)
>>> simple.save('my_simple_mesh.stl')
Since both libraries are based on the same core C++ code, feel free to
use whichever gives you the best performance and interoperability.
Replay decimation functionality
-------------------------------
This library also provides an interface to keep track of the successive
collapses that occur during the decimation process and to replay the
decimation process. This can be useful for different applications, such
as:
* applying the same decimation to a collection of meshes that share the
same topology
* computing a correspondence map between the vertices of the original
mesh and the vertices of the decimated mesh, to transfer field data from
one to the other for example
* replaying the decimation process with a smaller target reduction than
the original one, faster than decimating the original mesh with the
smaller target reduction
To use this functionality, you need to set the ``return_collapses``
parameter to ``True`` when calling ``simplify``. This will return the
successive collapses of the decimation process in addition to points
and faces.
.. code:: python
>>> import fast_simplification
>>> import pyvista
>>> mesh = pyvista.Sphere()
>>> points, faces = mesh.points, mesh.faces.reshape(-1, 4)[:, 1:]
>>> points_out, faces_out, collapses = fast_simplification.simplify(points, faces, 0.9, return_collapses=True)
Now you can call ``replay_simplification`` to replay the decimation process
and obtain the mapping between the vertices of the original mesh and the
vertices of the decimated mesh.
.. code:: python
>>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses)
>>> i = 3
>>> print(f'Vertex {i} of the original mesh is mapped to {indice_mapping[i]} of the decimated mesh')
You can also use the ``replay_simplification`` function to replay the
decimation process with a smaller target reduction than the original one.
This is faster than decimating the original mesh with the smaller target
reduction. To do so, you need to pass a subset of the collapses to the
``replay_simplification`` function. For example, to replay the decimation
process with a target reduction of 50% the initial rate, you can run:
.. code:: python
>>> import numpy as np
>>> collapses_half = collapses[:int(0.5 * len(collapses))]
>>> points_out, faces_out, indice_mapping = fast_simplification.replay_simplification(points, faces, collapses_half)
If you have a collection of meshes that share the same topology, you can
apply the same decimation to all of them by calling ``replay_simplification``
with the same collapses for each mesh. This ensure that the decimated meshes
will share the same topology.
.. code:: python
>>> import numpy as np
>>> # Assume that you have a collection of meshes stored in a list meshes
>>> _, _, collapses = fast_simplification.simplify(meshes[0].points, meshes[0].faces,
... 0.9, return_collapses=True)
>>> decimated_meshes = []
>>> for mesh in meshes:
... points_out, faces_out, _ = fast_simplification.replay_simplification(mesh.points, mesh.faces, collapses)
... decimated_meshes.append(pyvista.PolyData(points_out, faces_out))
Contributing
------------
Contribute to this repository by forking this repository and installing in
development mode with::
git clone https://github.com//fast-simplification
pip install -e .
pip install -r requirements_test.txt
You can then add your feature or commit your bug fix and then run your unit
testing with::
pytest
Unit testing will automatically enforce minimum code coverage standards.
Next, to ensure your code meets minimum code styling standards, run::
pip install pre-commit
pre-commit run --all-files
Finally, `create a pull request`_ from your fork and I'll be sure to review it.
.. _create a pull request: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request
================================================
FILE: doc/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = pymeshfix
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
rm -rf $(BUILDDIR)/*
rm -rf examples/
find . -type d -name "_autosummary" -exec rm -rf {} +
================================================
FILE: doc/_templates/autosummary/class.rst
================================================
{{ fullname | escape | underline}}
.. currentmodule:: {{ module }}
.. autoclass:: {{ objname }}
{% block methods %}
{% if methods %}
.. rubric:: {{ _('Methods') }}
.. autosummary::
:toctree:
{% for item in methods %}
{% if item != "__init__" %}
{{ name }}.{{ item }}
{% endif %}
{%- endfor %}
{% endif %}
{% endblock %}
{% block attributes %}
{% if attributes %}
.. rubric:: {{ _('Attributes') }}
.. autosummary::
:toctree:
{% for item in attributes %}
{% if item.0 != item.upper().0 %}
{{ name }}.{{ item }}
{% endif %}
{%- endfor %}
{% endif %}
{% endblock %}
================================================
FILE: doc/api.rst
================================================
API Reference
=============
These are the three public methods that expose the fast-simplification API to
Python.
.. currentmodule:: fast_simplification
.. autosummary::
:toctree: _autosummary
simplify
simplify_mesh
replay_simplification
================================================
FILE: doc/conf.py
================================================
import datetime
import os
import numpy as np
import pyvista
from sphinx_gallery.sorting import FileNameSortKey
from fast_simplification import __version__
# Manage errors
pyvista.set_error_output_file("errors.txt")
# Ensure that offscreen rendering is used for docs generation
pyvista.OFF_SCREEN = True # Not necessary - simply an insurance policy
# Preferred plotting style for documentation
pyvista.set_plot_theme("document")
pyvista.global_theme.window_size = np.array([1024, 768]) * 2
# Save figures in specified directory
pyvista.FIGURE_PATH = os.path.abspath("./images/")
if not os.path.exists(pyvista.FIGURE_PATH):
os.makedirs(pyvista.FIGURE_PATH)
pyvista.BUILDING_GALLERY = True
# -- Project information -----------------------------------------------------
project = "fast-simplification"
year = datetime.date.today().year
copyright = f"2017-{year}, The PyVista Developers"
author = "Alex Kaszynski"
# The short X.Y version
version = release = __version__
# -- General configuration ---------------------------------------------------
html_logo = "./_static/pyvista_logo_sm.png"
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
# 'sphinx.ext.doctest',
"sphinx.ext.autosummary",
"notfound.extension",
"sphinx_copybutton",
"sphinx_gallery.gen_gallery",
"sphinx.ext.extlinks",
"numpydoc",
]
numpydoc_show_class_members = False
# numpydoc_class_members_toctree = False
html_static_path = ["_static"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ".rst"
# The master toctree document.
master_doc = "index"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# Copy button customization ---------------------------------------------------
# exclude traditional Python prompts from the copied code
copybutton_prompt_text = r">>> ?|\.\.\. "
copybutton_prompt_is_regexp = True
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "pydata_sphinx_theme"
html_context = {
# Enable the "Edit in GitHub link within the header of each page.
"display_github": True,
"github_user": "pyvista",
"github_repo": "fast-simplification",
"github_version": "master",
"menu_links_name": "Getting Connected",
"menu_links": [
(
' Slack Community',
"http://slack.pyvista.org",
),
(
' Support',
"https://github.com/pyvista/pyvista-support",
),
(
' Source Code',
"https://github.com/pyvista/pymeshfix",
),
],
}
html_theme_options = {
"show_prev_next": False,
"github_url": "https://github.com/pyvista/pymeshfix",
}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "PyMeshFix"
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"pymeshfix.tex",
"PyMeshFix Documentation",
"Alex Kaszynski",
"manual",
),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, "PyMeshFix", "PyMeshFix Documentation", [author], 1)]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
"PyMeshFix",
"PyMeshFix Documentation",
author,
"PyMeshFix",
"One line description of project.",
"Miscellaneous",
),
]
# -- Extension configuration -------------------------------------------------
# -- Options for intersphinx extension ---------------------------------------
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"https://docs.python.org/": None,
"https://docs.pyvista.org": None,
}
# -- Options for todo extension ----------------------------------------------
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Sphinx Gallery Options
sphinx_gallery_conf = {
# path to your examples scripts
"examples_dirs": [
"../examples/",
],
# path where to save gallery generated examples
"gallery_dirs": ["examples"],
# pattern to search for example files
"filename_pattern": r"\.py",
# Remove the "Download all examples" button from the top level gallery
"download_all_examples": False,
# Sort gallery example by file name instead of number of lines (default)
"within_subsection_order": FileNameSortKey,
# directory where function granular galleries are stored
"backreferences_dir": None,
# Modules for which function level galleries are created. In
"doc_module": "pymeshfix",
"image_scrapers": (pyvista.Scraper(), "matplotlib"),
"thumbnail_size": (350, 350),
}
# -- Custom 404 page
notfound_no_urls_prefix = True
================================================
FILE: doc/index.rst
================================================
.. include:: ../README.rst
.. toctree::
:hidden:
self
.. toctree::
:maxdepth: 2
:caption: Examples
:hidden:
examples/index
.. toctree::
:maxdepth: 2
:caption: API Reference
:hidden:
api
================================================
FILE: examples/README.txt
================================================
Fast-simplification Examples
============================
The following examples demonstrate ``fast-simplification``
functionality using `pyvista `__.
================================================
FILE: examples/replay.py
================================================
"""
Replay Decimation
-----------------
This example shows how to replay a decimation sequence with replay.
"""
from time import time
import numpy as np
import pyvista as pv
from pyvista import examples
import fast_simplification
# Ancillary function to convert triangles to padded faces
def triangles_to_faces(triangles):
tmp = 3 * np.ones((len(triangles), 4), dtype=triangles.dtype)
tmp[:, 1:] = triangles
return tmp.copy().reshape(-1)
# mesh = examples.download_cow().triangulate().clean()
# cpos = [(12.81184076782852, 0.2698100334791761, -10.82840852844307),
# (-5.767085129340097, -0.45822321783537723, 6.935179459234972),
# (-0.031039486276564762, 0.99948202301343, 0.008499174352116386)]
# load an example mesh
mesh = examples.download_louis_louvre()
# nice camera angle
cpos = [
(5.428820015861438, -10.151721995577468, 15.902198956656623),
(1.4405146331169636, 2.897371104075222, 10.951469667556948),
(-0.01001925846458282, 0.3520252491158569, 0.9359368773826251),
]
points = mesh.points
triangles = mesh.faces.reshape(-1, 4)[:, 1:]
# Decimate the mesh with fast_simplification
# and record the collapses
start = time()
dec_points, dec_triangles, collapses = fast_simplification.simplify(
points, triangles, 0.995, return_collapses=True
)
time_simplify = time() - start
# Replay the decimation sequence and record the mapping between
# the original points and the decimated points
start = time()
(
dec_points_replay,
dec_triangles_replay,
indice_mapping_replay,
) = fast_simplification.replay_simplification(
points=points,
triangles=triangles,
collapses=collapses,
)
time_replay_new = time() - start
# Partially replay the decimation sequence (90% of the collapses are replayed)
partial_collapses = collapses[0 : int(0.9 * len(collapses))]
start = time()
(
dec_points_replay2,
dec_triangles_replay2,
indice_mapping_replay2,
) = fast_simplification.replay_simplification(
points=points, triangles=triangles, collapses=partial_collapses
)
time_replay_new = time() - start
# Randomly select two points on the original mesh
np.random.seed(1)
i, j = np.random.randint(0, len(mesh.points), 2)
# Map the indices of the original points to the indices of the decimated points
m_i = indice_mapping_replay[i]
m_j = indice_mapping_replay[j]
m_i2 = indice_mapping_replay2[i]
m_j2 = indice_mapping_replay2[j]
p = pv.Plotter(shape=(2, 2), theme=pv.themes.DocumentTheme())
p.subplot(0, 0)
# Plot the original mesh with the two highlighted points
p.add_mesh(mesh, show_edges=True, color="tan")
p.add_points(mesh.points[i], color="red", point_size=10, render_points_as_spheres=True)
p.add_points(mesh.points[j], color="blue", point_size=10, render_points_as_spheres=True)
p.add_text(
f"Original mesh, {mesh.points.shape[0]} vertices, {triangles.shape[0]} triangles",
font_size=10,
)
p.camera_position = cpos
p.subplot(0, 1)
# Plot the decimated mesh
p.add_mesh(
pv.PolyData(dec_points, faces=triangles_to_faces(dec_triangles)),
show_edges=True,
color="tan",
)
p.add_text(
f"Decimated mesh, {dec_points.shape[0]} vertices, {dec_triangles.shape[0]} triangles, took {time_simplify:.2f}s",
font_size=10,
)
p.camera_position = cpos
p.subplot(1, 0)
# Plot the mesh decimated with replay with the two highlighted points
p.add_mesh(
pv.PolyData(dec_points_replay, faces=triangles_to_faces(dec_triangles_replay)),
show_edges=True,
color="tan",
)
p.add_points(
dec_points_replay[m_i],
color="red",
point_size=10,
render_points_as_spheres=True,
)
p.add_points(
dec_points_replay[m_j],
color="blue",
point_size=10,
render_points_as_spheres=True,
)
n_points, n_triangles = dec_points_replay.shape[0], dec_triangles_replay.shape[0]
p.add_text(
f"Replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s",
font_size=10,
)
p.camera_position = cpos
p.subplot(1, 1)
# Plot the mesh partially decimated with replay with the two highlighted points
p.add_mesh(
pv.PolyData(dec_points_replay2, faces=triangles_to_faces(dec_triangles_replay2)),
show_edges=True,
color="tan",
)
p.add_points(
dec_points_replay2[m_i2],
color="red",
point_size=10,
render_points_as_spheres=True,
)
p.add_points(
dec_points_replay2[m_j2],
color="blue",
point_size=10,
render_points_as_spheres=True,
)
n_points, n_triangles = dec_points_replay2.shape[0], dec_triangles_replay2.shape[0]
p.add_text(
f"Partial replay, {n_points} vertices, {n_triangles} triangles, took {time_replay_new:.2f}s",
font_size=10,
)
p.camera_position = cpos
p.show()
================================================
FILE: examples/simplify.py
================================================
"""
Compare Decimation Methods
--------------------------
This example compares various decimation methods
"""
import time
import pyvista as pv
from pyvista import examples
import fast_simplification
# load an example mesh
mesh = examples.download_louis_louvre()
# nice camera angle
cpos = [
(6.264157141857314, -6.959267635766402, 11.71668951132694),
(1.3291685457683413, 2.267162128740896, 12.263240938610595),
(0.0023825740958850136, -0.05786378450796799, 0.9983216444528751),
]
###############################################################################
# Compare decimation times
reduction = 0.9
print("Approach Time Elapsed")
tstart = time.time()
fas_sim = fast_simplification.simplify_mesh(mesh, target_reduction=reduction)
fast_sim_time = time.time() - tstart
print(f"Fast Quadratic Simplification {fast_sim_time:8.4f} seconds")
tstart = time.time()
dec_std = mesh.decimate(reduction)
dec_std_time = time.time() - tstart
print(f"vtkQuadricDecimation {dec_std_time:8.4f} seconds")
tstart = time.time()
dec_pro = mesh.decimate_pro(reduction)
dec_pro_time = time.time() - tstart
print(f"vtkDecimatePro {dec_pro_time:8.4f} seconds")
pl = pv.Plotter(shape=(2, 2), window_size=(1000, 1000), theme=pv.themes.DocumentTheme())
pl.add_text("Original", "upper_right", color="k")
pl.add_mesh(mesh, show_edges=True)
pl.camera_position = cpos
pl.subplot(0, 1)
pl.add_text(
f"Fast-Quadric-Mesh-Simplification\n{fast_sim_time:8.4f} seconds",
"upper_right",
color="k",
)
pl.add_mesh(fas_sim, show_edges=True)
pl.camera_position = cpos
pl.subplot(1, 0)
pl.add_mesh(dec_std, show_edges=True)
pl.add_text(f"vtkQuadricDecimation\n{dec_std_time:8.4f} seconds", "upper_right", color="k")
pl.camera_position = cpos
pl.subplot(1, 1)
pl.add_mesh(dec_pro, show_edges=True)
pl.add_text(f"vtkDecimatePro\n{dec_pro_time:8.4f} seconds", "upper_right", color="k")
pl.camera_position = cpos
pl.show()
================================================
FILE: fast_simplification/Replay.h
================================================
#include "Simplify.h"
namespace Replay{
// Global Variables & Structures (same as for Simplify)
enum Attributes {
NONE,
NORMAL = 2,
TEXCOORD = 4,
COLOR = 8
};
struct Triangle { int v[3];double err[4];int deleted,dirty,attr;vec3f n;vec3f uvs[3];int material; };
struct Vertex { vec3f p;int tstart,tcount;SymetricMatrix q;int border;};
struct Ref { int tid,tvertex; };
std::vector triangles;
std::vector vertices;
std::vector[ refs;
std::string mtllib;
std::vector materials;
std::vector> collapses;
// Helper functions
double vertex_error(SymetricMatrix q, double x, double y, double z);
double calculate_error(int id_v1, int id_v2, vec3f &p_result);
void initialize_quadrics();
void replay_simplification()
{
// init
for(int i=0; i vcount,vids;
loopi(0,vertices.size())
vertices[i].border=0;
loopi(0,vertices.size())
{
Vertex &v=vertices[i];
vcount.clear();
vids.clear();
loopj(0,v.tcount)
{
int k=refs[v.tstart+j].tid;
Triangle &t=triangles[k];
loopk(0,3)
{
int ofs=0,id=t.v[k];
while(ofs try to find best result
vec3f p1=vertices[id_v1].p;
vec3f p2=vertices[id_v2].p;
vec3f p3=(p1+p2)/2;
double error1 = vertex_error(q, p1.x,p1.y,p1.z);
double error2 = vertex_error(q, p2.x,p2.y,p2.z);
double error3 = vertex_error(q, p3.x,p3.y,p3.z);
error = min(error1, min(error2, error3));
if (error1 == error) p_result=p1;
if (error2 == error) p_result=p2;
if (error3 == error) p_result=p3;
}
return error;
}
char *trimwhitespace(char *str)
{
char *end;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator
*(end+1) = 0;
return str;
}
//Option : Load OBJ
void load_obj(const char* filename, bool process_uv=false){
vertices.clear();
triangles.clear();
// printf ( "Loading Objects %s ... \n",filename);
FILE* fn;
if(filename==NULL) return ;
if((char)filename[0]==0) return ;
if ((fn = fopen(filename, "rb")) == NULL)
{
printf ( "File %s not found!\n" ,filename );
return;
}
char line[1000];
memset ( line,0,1000 );
int vertex_cnt = 0;
int material = -1;
std::map material_map;
std::vector uvs;
std::vector > uvMap;
while(fgets( line, 1000, fn ) != NULL)
{
Vertex v;
vec3f uv;
if (strncmp(line, "mtllib", 6) == 0)
{
mtllib = trimwhitespace(&line[7]);
}
if (strncmp(line, "usemtl", 6) == 0)
{
std::string usemtl = trimwhitespace(&line[7]);
if (material_map.find(usemtl) == material_map.end())
{
material_map[usemtl] = materials.size();
materials.push_back(usemtl);
}
material = material_map[usemtl];
}
if ( line[0] == 'v' && line[1] == 't' )
{
if ( line[2] == ' ' )
if(sscanf(line,"vt %lf %lf",
&uv.x,&uv.y)==2)
{
uv.z = 0;
uvs.push_back(uv);
} else
if(sscanf(line,"vt %lf %lf %lf",
&uv.x,&uv.y,&uv.z)==3)
{
uvs.push_back(uv);
}
}
else if ( line[0] == 'v' )
{
if ( line[1] == ' ' )
if(sscanf(line,"v %lf %lf %lf",
&v.p.x, &v.p.y, &v.p.z)==3)
{
vertices.push_back(v);
}
}
int integers[9];
if ( line[0] == 'f' )
{
Triangle t;
bool tri_ok = false;
bool has_uv = false;
if(sscanf(line,"f %d %d %d",
&integers[0],&integers[1],&integers[2])==3)
{
tri_ok = true;
}else
if(sscanf(line,"f %d// %d// %d//",
&integers[0],&integers[1],&integers[2])==3)
{
tri_ok = true;
}else
if(sscanf(line,"f %d//%d %d//%d %d//%d",
&integers[0],&integers[3],
&integers[1],&integers[4],
&integers[2],&integers[5])==6)
{
tri_ok = true;
}else
if(sscanf(line,"f %d/%d/%d %d/%d/%d %d/%d/%d",
&integers[0],&integers[6],&integers[3],
&integers[1],&integers[7],&integers[4],
&integers[2],&integers[8],&integers[5])==9)
{
tri_ok = true;
has_uv = true;
}else // Add Support for v/vt only meshes
if (sscanf(line, "f %d/%d %d/%d %d/%d",
&integers[0], &integers[6],
&integers[1], &integers[7],
&integers[2], &integers[8]) == 6)
{
tri_ok = true;
has_uv = true;
}
else
{
printf("unrecognized sequence\n");
printf("%s\n",line);
while(1);
}
if ( tri_ok )
{
t.v[0] = integers[0]-1-vertex_cnt;
t.v[1] = integers[1]-1-vertex_cnt;
t.v[2] = integers[2]-1-vertex_cnt;
t.attr = 0;
if ( process_uv && has_uv )
{
std::vector indices;
indices.push_back(integers[6]-1-vertex_cnt);
indices.push_back(integers[7]-1-vertex_cnt);
indices.push_back(integers[8]-1-vertex_cnt);
uvMap.push_back(indices);
t.attr |= TEXCOORD;
}
t.material = material;
//geo.triangles.push_back ( tri );
triangles.push_back(t);
//state_before = state;
//state ='f';
}
}
}
if ( process_uv && uvs.size() )
{
loopi(0,triangles.size())
{
loopj(0,3)
triangles[i].uvs[j] = uvs[uvMap[i][j]];
}
}
fclose(fn);
//printf("load_obj: vertices = %lu, triangles = %lu, uvs = %lu\n", vertices.size(), triangles.size(), uvs.size() );
} // load_obj()
// Optional : Store as OBJ
void write_obj(const char* filename)
{
FILE *file=fopen(filename, "w");
int cur_material = -1;
bool has_uv = (triangles.size() && (triangles[0].attr & TEXCOORD) == TEXCOORD);
if (!file)
{
printf("write_obj: can't write data file \"%s\".\n", filename);
exit(0);
}
if (!mtllib.empty())
{
fprintf(file, "mtllib %s\n", mtllib.c_str());
}
loopi(0,vertices.size())
{
//fprintf(file, "v %lf %lf %lf\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z);
fprintf(file, "v %g %g %g\n", vertices[i].p.x,vertices[i].p.y,vertices[i].p.z); //more compact: remove trailing zeros
}
if (has_uv)
{
loopi(0,triangles.size()) if(!triangles[i].deleted)
{
fprintf(file, "vt %g %g\n", triangles[i].uvs[0].x, triangles[i].uvs[0].y);
fprintf(file, "vt %g %g\n", triangles[i].uvs[1].x, triangles[i].uvs[1].y);
fprintf(file, "vt %g %g\n", triangles[i].uvs[2].x, triangles[i].uvs[2].y);
}
}
int uv = 1;
loopi(0,triangles.size()) if(!triangles[i].deleted)
{
if (triangles[i].material != cur_material)
{
cur_material = triangles[i].material;
fprintf(file, "usemtl %s\n", materials[triangles[i].material].c_str());
}
if (has_uv)
{
fprintf(file, "f %d/%d %d/%d %d/%d\n", triangles[i].v[0]+1, uv, triangles[i].v[1]+1, uv+1, triangles[i].v[2]+1, uv+2);
uv += 3;
}
else
{
fprintf(file, "f %d %d %d\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1);
}
//fprintf(file, "f %d// %d// %d//\n", triangles[i].v[0]+1, triangles[i].v[1]+1, triangles[i].v[2]+1); //more compact: remove trailing zeros
}
fclose(file);
}
}
================================================
FILE: fast_simplification/Simplify.h
================================================
/////////////////////////////////////////////
//
// Mesh Simplification Tutorial
//
// (C) by Sven Forstmann in 2014
//
// License : MIT
// http://opensource.org/licenses/MIT
//
//https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
//
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
#include
//#include
//#include
//#include
//#include
#include
//#include
//#include
#include
#include
#include ]