Source code for pylib.geometry

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Geometry objects.

:Date: 2019-12-21

.. module:: geometry
  :platform: *nix, Windows
  :synopsis: Geometry objects.

.. moduleauthor:: Daniel Weschke <daniel.weschke@directbox.de>

Affine transforms
-----------------

Functions in augmented space, in homogenous coordinates.
Points are augment to 4 dimensions, by adding a dummy coordinate.
For points the dummy coordinate is always normalized to 1.
With homogenous coordinates translation of points is repesentable
as a linear transformation.
"""
import math
import copy
from .mathematics import vector, matrix

[docs]class Direction(vector): """Direction in local coordinate system""" def __init__(self, x=1, y=0, z=0): super().__init__([x, y, z, 0])
[docs]class Point(vector): """Point in local coordinate system""" def __init__(self, x=0, y=0, z=0): super().__init__([x, y, z, 1]) # TODO
[docs] def projection(self): """Orthographic projection to the xy-plane """ # P = matrix([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) #return (P*self)[:2] return self[:2]
[docs]class CS(matrix): """Coordinate system """ def __init__(self, x=[1, 0, 0], y=[0, 1, 0], z=[0, 0, 1]): super().__init__([[*x, 0], [*y, 0], [*z, 0], [0, 0, 0, 1]])
[docs] @staticmethod def x90(): return CS((1, 0, 0), (0, 0, -1), (0, 1, 0))
[docs] @staticmethod def xm90(): return CS((1, 0, 0), (0, 0, 1), (0, -1, 0))
[docs] @staticmethod def y90(): return CS((0, 0, 1), (0, 1, 0), (-1, 0, 0))
[docs] @staticmethod def ym90(): return CS((0, 0, -1), (0, 1, 0), (1, 0, 0))
[docs] def get_coordinates(self): """Get coordinates in 3d space""" return self[:3,:3]
# TODO: Wireframe(list) or Wireframe(matrix) ? # list of Points
[docs]class Wireframe: """Open and closed wireframe object in local coordinate system This class create its own points (copy). """ def __init__(self, *points, closed=False): self._points = [copy.copy(i) for i in points] self.closed = closed
[docs] def __str__(self): return '[' + ', '.join([str(point) for point in self._points]) + ']'
[docs] def __iter__(self): """Returns the Iterator object""" return iter(self.points())
[docs] def points(self): """Get coordinates in 3d space""" result = [i for i in self._points] return result if not self.closed else result + [result[0]]
[docs] def xy(self): """Get coordinates in 3d space""" return list(zip(*[i[:2] for i in self.points()]))
[docs] def xyz(self): """Get coordinates in 3d space""" return list(zip(*[i[:3] for i in self.points()]))
[docs] def rotate_x(self, theta): self._points = [point.rotate_x(theta) for point in self._points] return self
[docs] def rotate_y(self, theta): self._points = [point.rotate_y(theta) for point in self._points] return self
[docs] def rotate_z(self, theta): self._points = [point.rotate_z(theta) for point in self._points] return self
[docs] def translate(self, tx, ty, tz): self._points = [point.translate(tx, ty, tz) for point in self._points] return self
[docs] def scale(self, sx, sy=None, sz=None): # if not sy is not suitable because 0 is also false if sy is None: sy = sx sz = sx self._points = [point.scale(sx, sy, sz) for point in self._points] return self
[docs] def ch_cs(self, cs): self._points = [point.ch_cs(cs) for point in self._points] return self
[docs]class Line(Wireframe): """Line a open wireframe object in local coordinate system""" def __init__(self, point1=Point(-1, 0, 0), point2=Point(1, 0, 0)): super().__init__(point1, point2)
[docs]class Polygon(Wireframe): """Polygon as closed wireframe object in local coordinate system""" def __init__(self, *points): super().__init__(*points, closed=True)
[docs]class Circle(Polygon): """Circle a closed wireframe object in local coordinate system""" def __init__(self, radius=1, n=10): points = [] for i in range(n): x = radius * math.cos(i*2*math.pi/n) y = radius * math.sin(i*2*math.pi/n) points.append(Point(x, y, 0)) super().__init__(*points)
[docs]class Solid: """Solid object in local coordinate system This class lists Wireframe objects. The Wireframe class create its own points (copy). """ def __init__(self, *wireframes): self._wireframes = wireframes
[docs] def wireframes(self): return self._wireframes
[docs] def translate(self, tx, ty, tz): self._wireframes = [wireframe.translate(tx, ty, tz) for wireframe in self._wireframes] return self
[docs] def scale(self, sx, sy=None, sz=None): # if not sy is not suitable because 0 is also false if sy is None: sy = sx sz = sx self._wireframes = [wireframe.scale(sx, sy, sz) for wireframe in self._wireframes] return self
[docs] def ch_cs(self, cs): self._wireframes = [wireframe.ch_cs(cs) for wireframe in self._wireframes] return self
[docs]class Hexahedron(Solid): """Line a open wireframe object in local coordinate system""" def __init__(self, point1=Point(-1, -1, -1), point2=Point(1, -1, -1), point3=Point(1, 1, -1), point4=Point(-1, 1, -1), point5=Point(-1, -1, 1), point6=Point(1, -1, 1), point7=Point(1, 1, 1), point8=Point(-1, 1, 1)): super().__init__( Polygon(point1, point2, point3, point4), Polygon(point5, point6, point7, point8), Polygon(point1, point2, point6, point5), Polygon(point3, point4, point8, point7) )
[docs]class World: """World-space with world-space coordinates """ def __init__(self): self._cs = CS() # Camera self._objects = [] self._store_init()
[docs] def __iter__(self): """Returns the Iterator object""" return iter(self.objects())
def _store_init(self): """Initialize or reset calculated values, because a new object was added. """ self._bb = None self._sd = None
[docs] def cs(self, cs=None): if cs: self._cs = cs return self._cs
[docs] def ch_cs(self, cs): self._cs = self._cs * cs return self
[docs] def rotate_x(self, theta): self._cs.rotate_x(theta) return self
[docs] def rotate_y(self, theta): self._cs.rotate_y(theta) return self
[docs] def rotate_z(self, theta): self._cs.rotate_z(theta) return self
[docs] def translate(self, tx, ty, tz): self._cs.translate(tx, ty, tz) return self
[docs] def scale(self, sx, sy=None, sz=None): self._cs.scale(sx, sy, sz) return self
[docs] def wireframes(self): result = [] for i in self.objects(): if isinstance(i, Wireframe): result.append(i.points()) elif isinstance(i, Solid): [result.append(j.points()) for j in i.wireframes()] return result
[docs] def wireframes_xy(self): result = [] for i in self.objects(): if isinstance(i, Wireframe): result.append(i.xy()) elif isinstance(i, Solid): [result.append(j.xy()) for j in i.wireframes()] return result
[docs] def wireframes_xyz(self): result = [] for i in self.objects(): if isinstance(i, Wireframe): result.append(i.xyz()) elif isinstance(i, Solid): [result.append(j.xyz()) for j in i.wireframes()] return result
[docs] def objects(self): return [copy.deepcopy(i).ch_cs(self._cs) for i in self._objects]
[docs] def add(self, *objects): self._store_init() # calculated values are not correct anymore [self._objects.append(i) for i in objects] return self
[docs] def bounding_box(self): if self._bb is not None: return self._bb xmin = math.inf ymin = math.inf zmin = math.inf xmax = -math.inf ymax = -math.inf zmax = -math.inf for i in self.wireframes_xyz(): xi, yi, zi = map(min, i) xs, ys, zs = map(max, i) xmin = xi if xi < xmin else xmin ymin = yi if yi < ymin else ymin zmin = zi if zi < zmin else zmin xmax = xs if xs > xmax else xmax ymax = ys if ys > ymax else ymax zmax = zs if zs > zmax else zmax self._bb = xmin, xmax, ymin, ymax, zmin, zmax return self._bb
[docs] def space_diagonal(self): if self._sd is not None: return self._sd bb = self.bounding_box() a, b, c = bb[1]-bb[0], bb[3]-bb[2], bb[5]-bb[4] return math.sqrt(a**2+b**2+c**2)
[docs] def center(self): bb = self.bounding_box() self.ch_cs([[1, 0, 0, -(bb[0]+bb[1])/2], [0, 1, 0, -(bb[2]+bb[3])/2], [0, 0, 1, -(bb[4]+bb[5])/2], [0, 0, 0, 1]]) return self