Skip to content

Commit 159d9c9

Browse files
committed
moved offseting functions to madcad.offseting according to #114
1 parent c294ba4 commit 159d9c9

10 files changed

+10
-212
lines changed

Diff for: docs/reference/generation.rst

-18
Original file line numberDiff line numberDiff line change
@@ -80,24 +80,6 @@ Generation of common meshes
8080

8181
.. image:: /screenshots/generation-uvsphere.png
8282

83-
84-
Offsetting
85-
----------
86-
87-
.. autofunction:: inflate_offsets
88-
.. autofunction:: inflate
89-
90-
.. image:: /screenshots/generation-inflate.png
91-
92-
.. autofunction:: thicken
93-
94-
.. image:: /screenshots/generation-thicken.png
95-
96-
.. autofunction:: expand
97-
98-
.. image:: /screenshots/generation-expand.png
99-
100-
10183
Repeating
10284
---------
10385

Diff for: docs/reference/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ References
2323
constraints.rst
2424

2525
generation.rst
26+
offseting.rst
2627
blending.rst
2728
bevel.rst
2829
boolean.rst
File renamed without changes.
File renamed without changes.
File renamed without changes.

Diff for: madcad/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
from .boolean import pierce, difference, union, intersection
153153
from .bevel import chamfer, filet, edgecut, planeoffsets
154154
from .generation import *
155+
from .offseting import *
155156
from .blending import junction, multijunction, blend, blendloop, blendpair, blenditer
156157
from .primitives import isprimitive, Point, Axis, Segment, ArcThrough, ArcCentered, ArcTangent, TangentEllipsis, Ellipsis, Circle, Interpolated, Softened, isaxis
157158
from .constraints import isconstraint, SolveError, Tangent, Distance, Angle, Parallel, Radius, PointOn, OnPlane, solve

Diff for: madcad/generation.py

+1-192
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
__all__ = [
2121
'extrans', 'extrusion', 'revolution', 'helix', 'screw', 'saddle', 'tube',
22-
'repeat', 'repeataround', 'thicken', 'inflate', 'inflate_offsets', 'expand',
22+
'repeat', 'repeataround',
2323
'flatsurface', 'icosurface',
2424
'square', 'brick', 'parallelogram', 'cylinder', 'cone', 'pyramid', 'icosahedron', 'icosphere', 'uvsphere', 'regon',
2525
]
@@ -318,197 +318,6 @@ def extrans(section, transformations:iter, links=None) -> Mesh:
318318

319319
return mesh
320320

321-
def linstep(start, stop, x):
322-
if x <= start: return 0
323-
if x >= stop: return 1
324-
return (x-start)/(stop-start)
325-
326-
def inflate_offsets(surface: Mesh, offset: float, method='face') -> '[vec3]':
327-
''' Displacements vectors for points of a surface we want to inflate.
328-
329-
Parameters:
330-
offset:
331-
the distance from the surface to the offset surface. Its meaning depends on `method`
332-
method:
333-
determines if the distance is from the old to the new faces, edges or points
334-
possible values: `'face', 'edge', 'point'`
335-
'''
336-
pnormals = surface.vertexnormals()
337-
338-
# smooth normal offsets laterally when they are closer than `offset`
339-
outlines = surface.outlines_oriented()
340-
l = len(pnormals)
341-
normals = deepcopy(pnormals)
342-
for i in range(5): # the number of steps is the diffusion distance through the mesh
343-
for a,b in outlines:
344-
d = surface.points[a] - surface.points[b] # edge direction
345-
t = cross(pnormals[a]+pnormals[b], d) # surface tangent normal to the edge
346-
# contribution stars when the offset points are closer than `offset`
347-
contrib = 1 - smoothstep(0, offset, length(offset*(pnormals[a]-pnormals[b])+d))
348-
normals[a] += contrib * 0.5*project(pnormals[b]-pnormals[a], t)
349-
normals[b] += contrib * 0.5*project(pnormals[a]-pnormals[b], t)
350-
# renormalize
351-
for i in range(l):
352-
pnormals[i] = normals[i] = normalize(normals[i])
353-
354-
# compute offset length depending on the method
355-
if method == 'face':
356-
lengths = [inf]*len(pnormals)
357-
for face in surface.faces:
358-
fnormal = surface.facenormal(face)
359-
for p in face:
360-
lengths[p] = min(lengths[p], 1/dot(pnormals[p], fnormal))
361-
return typedlist((pnormals[p]*lengths[p]*offset for p in range(len(pnormals))), dtype=vec3)
362-
363-
elif method == 'edge':
364-
lengths = [inf]*len(pnormals)
365-
for edge,enormal in surface.edgenormals().items():
366-
for p in edge:
367-
lengths[p] = min(lengths[p], 1/dot(pnormals[p], enormal))
368-
return typedlist((pnormals[p]*lengths[p]*offset for p in range(len(pnormals))), dtype=vec3)
369-
370-
elif method == 'point':
371-
return typedlist((pnormals[p]*offset for p in range(len(pnormals))), dtype=vec3)
372-
373-
def inflate(surface:Mesh, offset:float, method='face') -> 'Mesh':
374-
''' Move all points of the surface to make a new one at a certain distance of the last one
375-
376-
Parameters:
377-
offset: the distance from the surface to the offseted surface. its meaning depends on `method`
378-
method: determines if the distance is from the old to the new faces, edges or points
379-
'''
380-
return Mesh(
381-
typedlist((p+d for p,d in zip(surface.points, inflate_offsets(surface, offset, method))), dtype=vec3),
382-
surface.faces,
383-
surface.tracks,
384-
surface.groups)
385-
386-
def thicken(surface: Mesh, thickness: float, alignment:float=0, method='face') -> 'Mesh':
387-
''' Thicken a surface by extruding it, points displacements are made along normal.
388-
389-
Parameters:
390-
thickness: determines the distance between the two surfaces (can be negative to go the opposite direction to the normal).
391-
alignment: specifies which side is the given surface: 0 is for the first, 1 for the second side, 0.5 thicken all apart the given surface.
392-
method: determines if the thickness is from the old to the new faces, edges or points
393-
'''
394-
displts = inflate_offsets(surface, thickness, method)
395-
396-
a = alignment
397-
b = alignment-1
398-
m = ( Mesh(
399-
typedlist((p+d*a for p,d in zip(surface.points,displts)), dtype=vec3),
400-
surface.faces[:],
401-
surface.tracks[:],
402-
surface.groups)
403-
+ Mesh(
404-
typedlist((p+d*b for p,d in zip(surface.points,displts)), dtype=vec3),
405-
surface.faces,
406-
surface.tracks,
407-
surface.groups[:]
408-
) .flip()
409-
)
410-
t = len(m.groups)
411-
l = len(surface.points)
412-
m.groups.append(None)
413-
for e in surface.outlines_oriented():
414-
mkquad(m, (e[0], e[1], e[1]+l, e[0]+l), t)
415-
return m
416-
417-
418-
def expand(surface: Mesh, offset: float, collapse=True) -> Mesh:
419-
''' Generate a surface expanding the input mesh on the tangent of the ouline neighboring faces
420-
421-
Parameters:
422-
offset: distance from the outline point to the expanded outline points
423-
collapse: if True, expanded points leading to crossing edges will collapse into one
424-
'''
425-
# outline with associated face normals
426-
pts = surface.points
427-
edges = {}
428-
for face in surface.faces:
429-
for e in ((face[0], face[1]), (face[1], face[2]), (face[2],face[0])):
430-
if e in edges: del edges[e]
431-
else: edges[(e[1], e[0])] = surface.facenormal(face)
432-
433-
# return the point on tangent for a couple of edges from the frontier
434-
def tangent(e0, e1):
435-
mid = axis_midpoint(
436-
(pts[e0[1]], pts[e0[0]] - pts[e0[1]]),
437-
(pts[e1[0]], pts[e1[1]] - pts[e1[0]]),
438-
)
439-
d0 = pts[e0[1]] - pts[e0[0]]
440-
d1 = pts[e1[1]] - pts[e1[0]]
441-
n0, n1 = edges[e0], edges[e1]
442-
t = normalize(cross(n0, n1) + NUMPREC*(d0*length(d1)-d1*length(d0)) + NUMPREC**2 * (cross(n0, d0) + cross(n1, d1)))
443-
if dot(t, cross(n0, d0)) < 0:
444-
t = -t
445-
return mid + t * offset
446-
447-
# cross neighbooring normals
448-
for loop in suites(edges, cut=False):
449-
assert loop[-1] == loop[0], "non-manifold input mesh"
450-
loop.pop()
451-
# compute the offsets, and remove anticipated overlapping geometries
452-
extended = [None]*len(loop)
453-
for i in range(len(loop)):
454-
# consecutive edges around i-1
455-
ei0 = (loop[i-2], loop[i-1])
456-
ei1 = (loop[i-1], loop[i])
457-
ti = tangent(ei0, ei1)
458-
if collapse:
459-
tk = deepcopy(ti)
460-
weight = 1
461-
# j is moving to find how much points to gather
462-
ej0 = ei0
463-
for j in reversed(range(i-len(loop)+1, i-1)):
464-
# consecutive edges aroung j
465-
ej0, ej1 = (loop[j-1], loop[j]), ej0
466-
tj = tangent(ej0, ej1)
467-
468-
if dot(ti - tj, pts[ei1[0]] - pts[ej0[1]]) <= NUMPREC * length2(pts[ei1[0]] - pts[ej0[1]]):
469-
tk += tj
470-
weight += 1
471-
else:
472-
break
473-
# store tangents
474-
for k in range(j+1, i):
475-
extended[k] = tk/weight
476-
else:
477-
extended[i-1] = ti
478-
479-
# insert the new points
480-
j = l = len(pts)
481-
g = len(surface.groups)
482-
surface.groups.append(None)
483-
for i in range(len(extended)):
484-
if extended[i] != extended[i-1]:
485-
pts.append(extended[i])
486-
487-
# create the faces
488-
for i in range(len(extended)):
489-
if extended[i] != extended[i-1]:
490-
mkquad(surface, (loop[i-1], loop[i], j, (j if j > l else len(pts)) -1), g)
491-
j += 1
492-
else:
493-
mktri(surface, (loop[i-1], loop[i], (j if j > l else len(pts)) -1), g)
494-
495-
return surface
496-
497-
def axis_midpoint(a0: Axis, a1: Axis, x=0.5) -> vec3:
498-
''' Return the midpoint of two axis.
499-
`x` is the blending factor between `a0` and `a1`
500-
501-
- `x = 0` gives the point of `a0` the closest to `a1`
502-
- `x = 1` gives the point of `a1` the closest to `a0`
503-
'''
504-
p0, d0 = a0
505-
p1, d1 = a1
506-
if dot(d0,d1)**2 == length2(d0)*length2(d1):
507-
return mix(p0, p1, 0.5)
508-
return mix(
509-
p0 + unproject(project(p1-p0, noproject(d0, d1)), d0),
510-
p1 + unproject(project(p0-p1, noproject(d1, d0)), d1),
511-
x)
512321

513322

514323
# --- filling things ---

Diff for: madcad/mathutils.py

+5
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ def imax(iterable, default=None):
353353
if best is None: raise IndexError('iterable is empty')
354354
return best
355355

356+
def linstep(start, stop, x):
357+
''' like smoothstep but with a linear ramp between `start` and `stop` '''
358+
if x <= start: return 0
359+
if x >= stop: return 1
360+
return (x-start)/(stop-start)
356361

357362
def linrange(start, stop=None, step=None, div=0, end=True):
358363
''' Yield successive intermediate values between start and stop

Diff for: tests/test_expand.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from madcad import *
2-
from madcad.generation import expand
2+
from madcad.offseting import expand
33

44
results = []
55

Diff for: tests/test_inflate.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from madcad import *
2-
from madcad.generation import *
2+
from madcad.offseting import thicken, inflate
33

44
results = []
55

0 commit comments

Comments
 (0)