hub/venv/lib/python3.7/site-packages/trimesh/path/creation.py

316 lines
9.0 KiB
Python

import numpy as np
from . import arc
from .entities import Line, Arc
from ..geometry import plane_transform
from .. import util
from .. import transformations
def circle_pattern(pattern_radius,
circle_radius,
count,
center=[0.0, 0.0],
angle=None,
**kwargs):
"""
Create a Path2D representing a circle pattern.
Parameters
------------
pattern_radius : float
Radius of circle centers
circle_radius : float
The radius of each circle
count : int
Number of circles in the pattern
center : (2,) float
Center of pattern
angle : float
If defined pattern will span this angle
If None, pattern will be evenly spaced
Returns
-------------
pattern : trimesh.path.Path2D
Path containing circular pattern
"""
from .path import Path2D
if angle is None:
angles = np.linspace(0.0, np.pi * 2.0, count + 1)[:-1]
elif isinstance(angle, float) or isinstance(angle, int):
angles = np.linspace(0.0, angle, count)
else:
raise ValueError('angle must be float or int!')
# centers of circles
centers = np.column_stack((
np.cos(angles), np.sin(angles))) * pattern_radius
vert = []
ents = []
for circle_center in centers:
# (3,3) center points of arc
three = arc.to_threepoint(angles=[0, np.pi],
center=circle_center,
radius=circle_radius)
# add a single circle entity
ents.append(
Arc(
points=np.arange(3) + len(vert),
closed=True))
# keep flat array by extend instead of append
vert.extend(three)
# translate vertices to pattern center
vert = np.array(vert) + center
pattern = Path2D(entities=ents,
vertices=vert,
**kwargs)
return pattern
def circle(radius, center=None, **kwargs):
"""
Create a Path2D containing circle with the specified
radius.
Parameters
--------------
radius : float
The radius of the circle
center : None or (2,) float
Center of the circle, origin by default
** kwargs : dict
Passed to trimesh.path.Path2D constructor
Returns
-------------
circle : Path2D
Path containing specified circle
"""
from .path import Path2D
if center is None:
center = [0.0, 0.0]
else:
center = np.asanyarray(center, dtype=np.float64)
# make sure radius is a float
radius = float(radius)
# (3, 2) float, points on arc
three = arc.to_threepoint(angles=[0, np.pi],
center=center,
radius=radius)
# generate the path object
result = Path2D(
entities=[Arc(points=np.arange(3), closed=True)],
vertices=three,
**kwargs)
return result
def rectangle(bounds, **kwargs):
"""
Create a Path2D containing a single or multiple rectangles
with the specified bounds.
Parameters
--------------
bounds : (2, 2) float, or (m, 2, 2) float
Minimum XY, Maximum XY
Returns
-------------
rect : Path2D
Path containing specified rectangles
"""
from .path import Path2D
# data should be float
bounds = np.asanyarray(bounds, dtype=np.float64)
# bounds are extents, re- shape to origin- centered rectangle
if bounds.shape == (2,):
half = np.abs(bounds) / 2.0
bounds = np.array([-half, half])
# should have one bounds or multiple bounds
if not (util.is_shape(bounds, (2, 2)) or
util.is_shape(bounds, (-1, 2, 2))):
raise ValueError('bounds must be (m, 2, 2) or (2, 2)')
# hold Line objects
lines = []
# hold (n, 2) cartesian points
vertices = []
# loop through each rectangle
for lower, upper in bounds.reshape((-1, 2, 2)):
lines.append(Line((np.arange(5) % 4) + len(vertices)))
vertices.extend([lower,
[upper[0], lower[1]],
upper,
[lower[0], upper[1]]])
# create the Path2D with specified rectangles
rect = Path2D(entities=lines,
vertices=vertices,
**kwargs)
return rect
def box_outline(extents=None, transform=None, **kwargs):
"""
Return a cuboid.
Parameters
------------
extents : float, or (3,) float
Edge lengths
transform: (4, 4) float
Transformation matrix
**kwargs:
passed to Trimesh to create box
Returns
------------
geometry : trimesh.Path3D
Path outline of a cuboid geometry
"""
from .exchange.load import load_path
# create vertices for the box
vertices = [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1,
1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1]
vertices = np.array(vertices,
order='C',
dtype=np.float64).reshape((-1, 3))
vertices -= 0.5
# resize the vertices based on passed size
if extents is not None:
extents = np.asanyarray(extents, dtype=np.float64)
if extents.shape != (3,):
raise ValueError('Extents must be (3,)!')
vertices *= extents
# apply transform if passed
if transform is not None:
vertices = transformations.transform_points(
vertices, transform)
# vertex indices
indices = [0, 1, 3, 2, 0, 4, 5, 7, 6, 4, 0, 2, 6, 7, 3, 1, 5]
outline = load_path(vertices[indices])
return outline
def grid(side,
count=5,
transform=None,
plane_origin=None,
plane_normal=None,
include_circle=True,
sections_circle=32):
"""
Create a Path3D for a grid visualization of a plane.
Parameters
-----------
side : float
Length of half of a grid side
count : int
Number of grid lines per grid half
transform : None or (4, 4) float
Transformation matrix to move grid location.
Takes precedence over plane_origin if both are passed.
plane_origin : None or (3,) float
Plane origin
plane_normal : None or (3,) float
Unit normal vector
include_circle : bool
Include a circular pattern inside the grid
sections_circle : int
How many sections should the smallest circle have
Returns
----------
grid : trimesh.path.Path3D
Path containing grid plane visualization
"""
from .path import Path3D
# change full side length to half-side
side = float(side)
# make sure count is an integer
count = int(count)
# get a spaced sequence of radius
radii = np.linspace(0.0, side, count + 1)[1:]
# what's the maximum radius
rmax = radii[-1]
# keep a count of the current vertex count
current = 0
# collect vertices and entities
vertices = []
entities = []
for r in radii:
if include_circle:
# scale the section count by radius
circle_res = int((r / radii[0]) * sections_circle)
# generate a circule pattern
theta = np.linspace(0.0, np.pi * 2, circle_res)
circle = np.column_stack((np.cos(theta),
np.sin(theta))) * r
# append the circle pattern
vertices.append(circle)
entities.append(Line(
points=np.arange(len(circle)) + current))
# keep the vertex count correct
current += len(circle)
# generate a series of grid lines
vertices.append([[-rmax, r],
[rmax, r],
[-rmax, -r],
[rmax, -r],
[r, -rmax],
[r, rmax],
[-r, -rmax],
[-r, rmax]])
# append an entity per grid line
for i in [0, 2, 4, 6]:
entities.append(Line(
points=np.arange(2) + current + i))
current += len(vertices[-1])
# add the middle lines which were skipped
vertices.append([[0, rmax],
[0, -rmax],
[-rmax, 0],
[rmax, 0]])
entities.append(Line(points=np.arange(2) + current))
entities.append(Line(points=np.arange(2) + current + 2))
# stack vertices into clean (n, 3) float
vertices = np.vstack(vertices)
# if plane was passed instead of transform create the matrix here
if (transform is None and plane_origin is not None and plane_normal is not None):
transform = np.linalg.inv(plane_transform(
origin=plane_origin, normal=plane_normal))
# stack vertices to 3D
vertices = np.column_stack((vertices, np.zeros(len(vertices))))
# apply transform if passed
if transform is not None:
vertices = transformations.transform_points(
vertices, matrix=transform)
# combine result into a Path3D object
grid_path = Path3D(entities=entities, vertices=vertices)
return grid_path