Source code for candy

#
##
##  SPDX-FileCopyrightText: © 2007-2024 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.5  (Thu Feb  8 19:11:13 CET 2024)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  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 http://www.gnu.org/licenses/.
##

"""Predefined geometries for a special purpose.

This module contains some predefined special purpose geometries functions.

Contents
--------

:func:`Axes`
    A geometrical representation of a coordinate system.

:func:`Horse`
    A surface model of a horse.

"""
import numpy as np
import pyformex as pf
from pyformex.mesh import Mesh
from pyformex.trisurface import TriSurface
from pyformex.coordsys import CoordSys
from pyformex.olist import List


[docs]def Axes(cs=None, *, size=1.0, psize=0.5, reverse=True, color=['red', 'green', 'blue', 'cyan', 'magenta', 'yellow'], linewidth=2, alpha=0.5, **kargs): """A geometry representing the three axes of a coordinate system. The default geometry consists of three colored lines of unit length along the positive directions of the axes of the coordinate system, and three colored triangles representing the coordinate planes. The triangles extend from the origin to half the length of the unit vectors. Default colors for the axes is red, green, blue. Parameters ---------- cs: :class:`coordsys.CoordSys` If provided, the Axes will represent the specified CoordSys. Else, The axes are aligned along the global axes. size: float A scale factor for the unit vectors. psize: float Relative scale factor for the coordinate plane triangles. If 0, no triangles will be drawn. reverse: bool If True, also the negative unit axes are included, with colors 4..6. color: 3 or 6 colors A set of three or six colors to use for x,y,z axes. If `reverse` is True or psize > 0.0, the color set should have 6 colors, else 3 will suffice. **kargs: keyword arguments Any extra keyword arguments will be added as attributes to the geometry. Returns ------- :class:`List` A List containing two :class:`Mesh` instances: the lines along the axes and the triangles in the coordinate planes. """ if cs is None: cs = CoordSys() if not isinstance(cs, CoordSys): raise ValueError( f"cs should be a CoordSys, got {type(cs)=},\n {cs=}") coords = cs.points().reshape(4, 3) # Axes lines = [[3, 0], [3, 1], [3, 2]] M = Mesh(coords.scale(size, center=coords[3]), lines) col = color[:3] if reverse: # Add negative axes M = Mesh.concatenate([M, M.reflectPoint(coords[3])]) col = color[:6] M.attrib( color=col, lighting=False, opak=True, linewidth=linewidth, **kargs ) L = [M] # Coord planes if psize > 0.0: planes = [[3, 1, 2], [3, 2, 0], [3, 0, 1]] M = Mesh(coords.scale(size * psize, center=coords[3]), planes) col = color[:3] bkcol = color[3:6] if reverse else col M.attrib( color=col, bkcolor=bkcol, lighting=False, alpha=alpha, **kargs ) L.append(M) return List(L)
[docs]def Horse(): """A surface model of a horse. Returns ------- TriSurface A surface model of a horse. The model is loaded from a file. """ return TriSurface.read(pf.cfg['datadir'] / 'horse.off')
# TODO: we should add a fuse parameter and make fuse=True the default
[docs]def KleinBottle(nu=128, nv=64, ru=(0., 1.), rv=(0., 1.)): """A Quad4 Mesh representing a Klein bottle. A Klein bottle is a borderless non-orientable surface. In 3D the surface is self-intersecting. Parameters ---------- nu: int Number of elements along the longitudinal axis of the bottle. nv: int Number of elements along the circumference of the bottle. ru: tuple of floats The relative range of the longitudinal parameter. The default covers the full range [0., pi]. rv: tuple of floats The relative range of the circumferential parameter. The default covers the full range [0., 2*np.pi]. Returns ------- :class:`Mesh` A Mesh of eltype Quad4 representing a Klein bottle. The Mesh has nu * nv elements and (nu+1) * (nv+1) nodes. The Mesh is not fused and will contain double nodes at the ends of the full parameter ranges. Use :meth:`Mesh.fuse` and :meth:`Mesh.compact` to remove double nodes. Notes ----- One can check that the surface has no border from:: KleinBottle().fuse().compact().getBorder() which returns an empty list. The non-orientability and self-intersection of the surface can be checked from transforming the Klein bottle to a :class:`TriSurface` and then using the :meth:`~TriSurface.check`. For the non-fused bottle, this will report 'orientable but self-intersecting'. For the fused bottle, the report will say 'not an orientable manifold'. """ from pyformex.elements import Quad4 u = np.linspace(ru[0]*np.pi, ru[1]*np.pi, nu+1) v = np.linspace(rv[0]*2*np.pi, rv[1]*2*np.pi, nv+1) x = np.sum([ np.outer(a * np.cos(u) ** i * np.sin(u) ** j, np.cos(v) ** k) for a, i, j, k in zip([2 / 5, 2 / 3, -4, 12, -8], [1, 2, 1, 5, 7], [0, 1, 1, 1, 1], [1, 1, 0, 0, 0]) ], axis=0) y = np.sum([ np.outer(a * np.cos(u) ** i * np.sin(u) ** j, np.cos(v) ** k) for a, i, j, k in zip([-4, 1/5, -1/5, 1/3, -1/3, -16/5, 16/5, -16/3, 16/3], [0, 0, 2, 1, 3, 4, 6, 5, 7], [2, 1, 1, 2, 2, 1, 1, 2, 2], [0, 1, 1, 1, 1, 1, 1, 1, 1]) ], axis=0) z = np.sum([ np.outer(a * np.cos(u) ** i * np.sin(u) ** j, np.sin(v) ** k) for a, i, j, k in zip([2 / 5, 2 / 3], [0, 1], [0, 1], [1, 1]) ], axis=0) xyz = np.stack([x, y, z], axis=-1).reshape(-1, 3) elems = Quad4.els(nv, nu) return pf.Mesh(xyz, elems)
# End