"""Map-first material grading helpers for architected structures."""
# Third-party
from numba import cfunc, types
# OpenVCAD
import pyvcad as pv
# Local
from .lattices import _unit_cell, graph_lattice
from .tpms import tpms
_SIGNATURE = types.float64(types.float64, types.float64, types.float64, types.float64)
[docs]
def skin_core_tpms(
cell_map,
skin_material,
core_material,
kind="gyroid",
wall_thickness=2.0,
skin_ratio=0.35,
transition=0.15,
):
"""Build a mapped TPMS with material graded through each wall's depth."""
if not 0.0 <= skin_ratio <= 1.0:
raise ValueError("skin_ratio must lie between zero and one")
if not isinstance(wall_thickness, (int, float)):
raise TypeError("skin_core_tpms currently requires constant wall_thickness")
node = tpms(cell_map, kind, wall_thickness=wall_thickness)
threshold = -0.5 * float(wall_thickness) * float(skin_ratio)
band = max(float(transition), 1e-6)
@cfunc(_SIGNATURE)
def skin_fraction(x, y, z, d):
value = (d - threshold) / band + 0.5
return min(1.0, max(0.0, value))
@cfunc(_SIGNATURE)
def core_fraction(x, y, z, d):
value = (d - threshold) / band + 0.5
return 1.0 - min(1.0, max(0.0, value))
node.set_attribute(
pv.DefaultAttributes.VOLUME_FRACTIONS,
pv.VolumeFractionsAttribute([
(skin_fraction, int(skin_material)),
(core_fraction, int(core_material)),
]),
)
return node
[docs]
def per_strut_material(cell_map, unit_cell, selector, beam_radius=0.8, node_radius=None):
"""Assign native material tags to unit-cell edges selected by orientation or position."""
graph = _unit_cell(unit_cell)
tags = [int(selector(graph.vertices[a], graph.vertices[b])) for a, b in graph.edges]
tagged = pv.GraphUnitCell(
graph.vertices,
graph.edges,
vertex_tags=graph.vertex_tags,
edge_tags=graph.edge_tags,
orientation_tags=graph.orientation_tags,
material_tags=tags,
)
return graph_lattice(cell_map, tagged, beam_radius=beam_radius, node_radius=node_radius)