DI-PCG / core /assets /chair.py
thuzhaowang's picture
init
b6a9b6d
raw
history blame
30.4 kB
# Copyright (C) 2024, Princeton University.
# This source code is licensed under the BSD 3-Clause license found in the LICENSE file in the root directory of this source tree.
# Authors: Lingjie Mei
import bpy
import numpy as np
from numpy.random import uniform
import infinigen
from infinigen.assets.material_assignments import AssetList
from infinigen.assets.utils.decorate import (
read_co,
read_edge_center,
read_edge_direction,
remove_edges,
remove_vertices,
select_edges,
solidify,
subsurf,
write_attribute,
write_co,
)
from infinigen.assets.utils.draw import align_bezier, bezier_curve
from infinigen.assets.utils.nodegroup import geo_radius
from infinigen.assets.utils.object import join_objects, new_bbox
from infinigen.core import surface
from infinigen.core.placement.factory import AssetFactory
from infinigen.core.surface import NoApply
from infinigen.core.util import blender as butil
from infinigen.core.util.blender import deep_clone_obj
from infinigen.core.util.math import FixedSeed
from infinigen.core.util.random import log_uniform
from infinigen.core.util.random import random_general as rg
class ChairFactory(AssetFactory):
back_types = {
0: "whole",
1: "partial",
2: "horizontal-bar",
3: "vertical-bar",
}
leg_types = {
0: "vertical",
1: "straight",
2: "up-curved",
3: "down-curved",
}
def __init__(self, factory_seed, coarse=False):
super().__init__(factory_seed, coarse)
self.get_params_dict()
# random init with seed
with FixedSeed(self.factory_seed):
self.width = uniform(0.4, 0.5)
self.size = uniform(0.38, 0.45)
self.thickness = uniform(0.04, 0.08)
self.bevel_width = self.thickness * (0.1 if uniform() < 0.4 else 0.5)
self.seat_back = uniform(0.7, 1.0) if uniform() < 0.75 else 1.0
self.seat_mid = uniform(0.7, 0.8)
self.seat_mid_x = uniform(
self.seat_back + self.seat_mid * (1 - self.seat_back), 1
)
self.seat_mid_z = uniform(0, 0.5)
self.seat_front = uniform(1.0, 1.2)
self.is_seat_round = uniform() < 0.6
self.is_seat_subsurf = uniform() < 0.5
self.leg_thickness = uniform(0.04, 0.06)
self.limb_profile = uniform(1.5, 2.5)
self.leg_height = uniform(0.45, 0.5)
self.back_height = uniform(0.4, 0.5)
self.is_leg_round = uniform() < 0.5
self.leg_type = np.random.choice(
["vertical", "straight", "up-curved", "down-curved"]
)
self.leg_x_offset = 0
self.leg_y_offset = 0, 0
self.back_x_offset = 0
self.back_y_offset = 0
self.has_leg_x_bar = uniform() < 0.6
self.has_leg_y_bar = uniform() < 0.6
self.leg_offset_bar = uniform(0.2, 0.4), uniform(0.6, 0.8)
self.has_arm = uniform() < 0.7
self.arm_thickness = uniform(0.04, 0.06)
self.arm_height = self.arm_thickness * uniform(0.6, 1)
self.arm_y = uniform(0.8, 1) * self.size
self.arm_z = uniform(0.3, 0.6) * self.back_height
self.arm_mid = np.array(
[uniform(-0.03, 0.03), uniform(-0.03, 0.09), uniform(-0.09, 0.03)]
)
self.arm_profile = log_uniform(0.1, 3, 2)
self.back_thickness = uniform(0.04, 0.05)
self.back_type = rg(self.back_types)
self.back_profile = [(0, 1)]
self.back_vertical_cuts = np.random.randint(1, 4)
self.back_partial_scale = uniform(1, 1.4)
materials = AssetList["ChairFactory"]()
self.limb_surface = materials["limb"].assign_material()
self.surface = materials["surface"].assign_material()
if uniform() < 0.3:
self.panel_surface = self.surface
else:
self.panel_surface = materials["panel"].assign_material()
scratch_prob, edge_wear_prob = materials["wear_tear_prob"]
self.scratch, self.edge_wear = materials["wear_tear"]
is_scratch = uniform() < scratch_prob
is_edge_wear = uniform() < edge_wear_prob
if not is_scratch:
self.scratch = None
if not is_edge_wear:
self.edge_wear = None
# from infinigen.assets.clothes import blanket
# from infinigen.assets.scatters.clothes import ClothesCover
# self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(.8, 1.2),
# size=uniform(.8, 1.2)) if uniform() < .3 else NoApply()
self.clothes_scatter = NoApply()
self.post_init()
def get_params_dict(self):
# all the parameters (key:name, value: [type, range]) used in this generator
self.params_dict = {
"width": ['continuous', [0.3, 0.8]], # seat width
"size": ['continuous', [0.35, 0.5]], # seat length
"thickness": ['continuous', [0.02, 0.1]], # seat thickness
"bevel_width": ['discrete', [0.1, 0.5]],
"seat_back": ['continuous', [0.6, 1.0]], # seat back width
"seat_mid": ['continuous', [0.7, 0.8]],
"seat_mid_z": ['continuous', [0.0, 0.7]], # seat mid point height
"seat_front": ['continuous', [1.0, 1.2]], # seat front point
"is_seat_round": ['discrete', [0, 1]],
"is_seat_subsurf": ['discrete', [0, 1]],
"leg_thickness": ['continuous', [0.02, 0.07]], # leg thickness
"limb_profile": ['continuous', [1.5, 2.5]],
"leg_height": ['continuous', [0.2, 1.0]], # leg height
"is_leg_round": ['discrete', [0, 1]],
"leg_type": ['discrete', [0,1,2,3]],
"has_leg_x_bar": ['discrete', [0, 1]],
"has_leg_y_bar": ['discrete', [0, 1]],
"leg_offset_bar0": ['continuous', [0.1, 0.9]], # leg y bar offset, only for has_leg_y_bar is 1
"leg_offset_bar1": ['continuous', [0.1, 0.9]], # leg x bar offset, only for has_leg_x_bar is 1
"leg_x_offset": ['continuous', [0.0, 0.2]], # leg end point x offset
"leg_y_offset0": ['continuous', [0.0, 0.2]], # leg end point y offset
"leg_y_offset1": ['continuous', [0.0, 0.2]], # leg end point y offset
"has_arm": ['discrete', [0, 1]],
"arm_thickness": ['continuous', [0.02, 0.07]], # arm thickness, only for has_arm is 1
"arm_height": ['continuous', [0.6, 1]], # only for has_arm is 1
"arm_y": ['continuous', [0.5, 1]], # arm y end point, only for has_arm is 1
"arm_z": ['continuous', [0.25, 0.6]], # arm z end point, only for has_arm is 1
"arm_mid0": ['continuous', [-0.03, 0.03]], # arm mid point x coord, only for has_arm is 1
"arm_mid1": ['continuous', [-0.03, 0.2]], # arm mid point y coord, only for has_arm is 1
"arm_mid2": ['continuous', [-0.09, 0.03]], # arm mid point z coord, only for has_arm is 1
"arm_profile0": ['continuous', [0.0, 2.0]], # arm curve control, only for has_arm is 1
"arm_profile1": ['continuous', [0.0, 2]], # arm curve control, only for has_arm is 1
"back_height": ['continuous', [0.3, 0.6]], # back height
"back_thickness": ['continuous', [0.02, 0.07]], # back thickness
"back_type": ['discrete', [0, 1, 2, 3]],
"back_vertical_cuts": ['discrete', [1,2,3,4]], # only for back type 3
"back_partial_scale": ['continuous', [1.0, 1.4]], # only for back type 1
"back_x_offset": ['continuous', [-0.1, 0.15]], # back top x length
"back_y_offset": ['continuous', [0.0, 0.4]], # back top y coord
"back_profile_partial": ['continuous', [0.4, 0.8]], # only for back type 1
"back_profile_horizontal_ncuts": ['discrete', [2, 3, 4]], # only for back type 2
"back_profile_horizontal_locs0": ['continuous', [1, 2]], # only for back type 2
"back_profile_horizontal_locs1": ['continuous', [1, 2]], # only for back type 2
"back_profile_horizontal_locs2": ['continuous', [1, 2]], # only for back type 2
"back_profile_horizontal_locs3": ['continuous', [1, 2]], # only for back type 2
"back_profile_horizontal_ratio": ['continuous', [0.2, 0.8]], # only for back type 2
"back_profile_horizontal_lowest": ['continuous', [0, 0.4]], # only for back type 2
"back_profile_vertical": ['continuous', [0.8, 0.9]], # only for back type 3
}
def fix_unused_params(self, params):
# check unused parameters inside a given parameter set, and fix them into mid value - for training
if params['leg_type'] != 2 and params['leg_type'] != 3:
params['limb_profile'] = (self.params_dict['limb_profile'][1][0] + self.params_dict['limb_profile'][1][-1]) / 2
if params['has_leg_x_bar'] == 0:
params['leg_offset_bar1'] = (self.params_dict['leg_offset_bar1'][1][0] + self.params_dict['leg_offset_bar1'][1][-1]) / 2
if params['has_leg_y_bar'] == 0:
params['leg_offset_bar0'] = (self.params_dict['leg_offset_bar0'][1][0] + self.params_dict['leg_offset_bar0'][1][-1]) / 2
if params['has_arm'] == 0:
params['arm_thickness'] = (self.params_dict['arm_thickness'][1][0] + self.params_dict['arm_thickness'][1][-1]) / 2
params['arm_height'] = (self.params_dict['arm_height'][1][0] + self.params_dict['arm_height'][1][-1]) / 2
params['arm_y'] = (self.params_dict['arm_y'][1][0] + self.params_dict['arm_y'][1][-1]) / 2
params['arm_z'] = (self.params_dict['arm_z'][1][0] + self.params_dict['arm_z'][1][-1]) / 2
params['arm_mid0'] = (self.params_dict['arm_mid0'][1][0] + self.params_dict['arm_mid0'][1][-1]) / 2
params['arm_mid1'] = (self.params_dict['arm_mid1'][1][0] + self.params_dict['arm_mid1'][1][-1]) / 2
params['arm_mid2'] = (self.params_dict['arm_mid2'][1][0] + self.params_dict['arm_mid2'][1][-1]) / 2
params['arm_profile0'] = (self.params_dict['arm_profile0'][1][0] + self.params_dict['arm_profile0'][1][-1]) / 2
params['arm_profile1'] = (self.params_dict['arm_profile1'][1][0] + self.params_dict['arm_profile1'][1][-1]) / 2
if params['back_type'] != 3:
params['back_vertical_cuts'] = (self.params_dict['back_vertical_cuts'][1][0] + self.params_dict['back_vertical_cuts'][1][-1]) / 2
params['back_profile_vertical'] = (self.params_dict['back_profile_vertical'][1][0] + self.params_dict['back_profile_vertical'][1][-1]) / 2
if params['back_type'] != 2:
params['back_profile_horizontal_ncuts'] = (self.params_dict['back_profile_horizontal_ncuts'][1][0] + self.params_dict['back_profile_horizontal_ncuts'][1][-1]) / 2
params['back_profile_horizontal_locs0'] = (self.params_dict['back_profile_horizontal_locs0'][1][0] + self.params_dict['back_profile_horizontal_locs0'][1][-1]) / 2
params['back_profile_horizontal_locs1'] = (self.params_dict['back_profile_horizontal_locs1'][1][0] + self.params_dict['back_profile_horizontal_locs1'][1][-1]) / 2
params['back_profile_horizontal_locs2'] = (self.params_dict['back_profile_horizontal_locs2'][1][0] + self.params_dict['back_profile_horizontal_locs2'][1][-1]) / 2
params['back_profile_horizontal_ratio'] = (self.params_dict['back_profile_horizontal_ratio'][1][0] + self.params_dict['back_profile_horizontal_ratio'][1][-1]) / 2
params['back_profile_horizontal_lowest'] = (self.params_dict['back_profile_horizontal_lowest'][1][0] + self.params_dict['back_profile_horizontal_lowest'][1][-1]) / 2
if params['back_type'] != 1:
params['back_partial_scale'] = (self.params_dict['back_partial_scale'][1][0] + self.params_dict['back_partial_scale'][1][-1]) / 2
params['back_profile_partial'] = (self.params_dict['back_profile_partial'][1][0] + self.params_dict['back_profile_partial'][1][-1]) / 2
return params
def update_params(self, new_params):
# replace the parameters and calculate all the new values
self.width = new_params["width"]
self.size = new_params["size"]
self.thickness = new_params["thickness"]
self.bevel_width = self.thickness * new_params["bevel_width"]
self.seat_back = new_params["seat_back"]
self.seat_mid = new_params["seat_mid"]
self.seat_mid_x = uniform(
self.seat_back + self.seat_mid * (1 - self.seat_back), 1
)
self.seat_mid_z = new_params["seat_mid_z"]
self.seat_front = new_params["seat_front"]
self.is_seat_round = new_params["is_seat_round"]
self.is_seat_subsurf = new_params["is_seat_subsurf"]
self.leg_thickness = new_params["leg_thickness"]
self.limb_profile = new_params["limb_profile"]
self.leg_height = new_params["leg_height"]
self.back_height = new_params["back_height"]
self.is_leg_round = new_params["is_leg_round"]
self.leg_type = self.leg_types[new_params["leg_type"]]
self.leg_x_offset = 0
self.leg_y_offset = 0, 0
self.back_x_offset = 0
self.back_y_offset = 0
self.has_leg_x_bar = new_params["has_leg_x_bar"]
self.has_leg_y_bar = new_params["has_leg_y_bar"]
self.leg_offset_bar = new_params["leg_offset_bar0"], new_params["leg_offset_bar1"]
self.has_arm = new_params["has_arm"]
self.arm_thickness = new_params["arm_thickness"]
self.arm_height = self.arm_thickness * new_params["arm_height"]
self.arm_y = new_params["arm_y"] * self.size
self.arm_z = new_params["arm_z"] * self.back_height
self.arm_mid = np.array(
[new_params["arm_mid0"], new_params["arm_mid1"], new_params["arm_mid2"]]
)
self.arm_profile = (new_params["arm_profile0"], new_params["arm_profile1"])
self.back_thickness = new_params["back_thickness"]
self.back_type = self.back_types[new_params["back_type"]]
self.back_profile = [(0, 1)]
self.back_vertical_cuts = new_params["back_vertical_cuts"]
self.back_partial_scale = new_params["back_partial_scale"]
if self.leg_type == "vertical":
self.leg_x_offset = 0
self.leg_y_offset = 0, 0
self.back_x_offset = 0
self.back_y_offset = 0
else:
self.leg_x_offset = self.width * new_params["leg_x_offset"]
self.leg_y_offset = self.size * np.array([new_params["leg_y_offset0"], new_params["leg_y_offset1"]])
self.back_x_offset = self.width * new_params["back_x_offset"]
self.back_y_offset = self.size * new_params["back_y_offset"]
match self.back_type:
case "partial":
self.back_profile = ((new_params["back_profile_partial"], 1),)
case "horizontal-bar":
n_cuts = int(new_params["back_profile_horizontal_ncuts"])
locs = np.array([new_params["back_profile_horizontal_locs0"], new_params["back_profile_horizontal_locs1"],
new_params["back_profile_horizontal_locs2"], new_params["back_profile_horizontal_locs3"]])[:n_cuts].cumsum()
locs = locs / locs[-1]
ratio = new_params["back_profile_horizontal_ratio"]
locs = np.array(
[
(p + ratio * (l - p), l)
for p, l in zip([0, *locs[:-1]], locs)
]
)
lowest = new_params["back_profile_horizontal_lowest"]
self.back_profile = locs * (1 - lowest) + lowest
case "vertical-bar":
self.back_profile = ((new_params["back_profile_vertical"], 1),)
case _:
self.back_profile = [(0, 1)]
# TODO: handle the material into the optimization loop
materials = AssetList["ChairFactory"]()
self.limb_surface = materials["limb"].assign_material()
self.surface = materials["surface"].assign_material()
if uniform() < 0.3:
self.panel_surface = self.surface
else:
self.panel_surface = materials["panel"].assign_material()
scratch_prob, edge_wear_prob = materials["wear_tear_prob"]
self.scratch, self.edge_wear = materials["wear_tear"]
is_scratch = uniform() < scratch_prob
is_edge_wear = uniform() < edge_wear_prob
if not is_scratch:
self.scratch = None
if not is_edge_wear:
self.edge_wear = None
# from infinigen.assets.clothes import blanket
# from infinigen.assets.scatters.clothes import ClothesCover
# self.clothes_scatter = ClothesCover(factory_fn=blanket.BlanketFactory, width=log_uniform(.8, 1.2),
# size=uniform(.8, 1.2)) if uniform() < .3 else NoApply()
self.clothes_scatter = NoApply()
def post_init(self):
with FixedSeed(self.factory_seed):
if self.leg_type == "vertical":
self.leg_x_offset = 0
self.leg_y_offset = 0, 0
self.back_x_offset = 0
self.back_y_offset = 0
else:
self.leg_x_offset = self.width * uniform(0.05, 0.2)
self.leg_y_offset = self.size * uniform(0.05, 0.2, 2)
self.back_x_offset = self.width * uniform(-0.1, 0.15)
self.back_y_offset = self.size * uniform(0.1, 0.25)
match self.back_type:
case "partial":
self.back_profile = ((uniform(0.4, 0.8), 1),)
case "horizontal-bar":
n_cuts = np.random.randint(2, 4)
locs = uniform(1, 2, n_cuts).cumsum()
locs = locs / locs[-1]
ratio = uniform(0.5, 0.75)
locs = np.array(
[
(p + ratio * (l - p), l)
for p, l in zip([0, *locs[:-1]], locs)
]
)
lowest = uniform(0, 0.4)
self.back_profile = locs * (1 - lowest) + lowest
case "vertical-bar":
self.back_profile = ((uniform(0.8, 0.9), 1),)
case _:
self.back_profile = [(0, 1)]
def create_placeholder(self, **kwargs) -> bpy.types.Object:
obj = new_bbox(
-self.width / 2 - max(self.leg_x_offset, self.back_x_offset),
self.width / 2 + max(self.leg_x_offset, self.back_x_offset),
-self.size - self.leg_y_offset[1] - self.leg_thickness * 0.5,
max(self.leg_y_offset[0], self.back_y_offset),
-self.leg_height,
self.back_height * 1.2,
)
obj.rotation_euler.z += np.pi / 2
butil.apply_transform(obj)
return obj
def create_asset(self, **params) -> bpy.types.Object:
obj = self.make_seat()
legs = self.make_legs()
backs = self.make_backs()
parts = [obj] + legs + backs
parts.extend(self.make_leg_decors(legs))
if self.has_arm:
parts.extend(self.make_arms(obj, backs))
parts.extend(self.make_back_decors(backs))
for obj in legs:
self.solidify(obj, 2)
for obj in backs:
self.solidify(obj, 2, self.back_thickness)
obj = join_objects(parts)
obj.rotation_euler.z += np.pi / 2
butil.apply_transform(obj)
with FixedSeed(self.factory_seed):
# TODO: wasteful to create unique materials for each individual asset
self.surface.apply(obj)
self.panel_surface.apply(obj, selection="panel")
self.limb_surface.apply(obj, selection="limb")
return obj
def finalize_assets(self, assets):
if self.scratch:
self.scratch.apply(assets)
if self.edge_wear:
self.edge_wear.apply(assets)
def make_seat(self):
x_anchors = (
np.array(
[
0,
-self.seat_back,
-self.seat_mid_x,
-1,
0,
1,
self.seat_mid_x,
self.seat_back,
0,
]
)
* self.width
/ 2
)
y_anchors = (
np.array(
[0, 0, -self.seat_mid, -1, -self.seat_front, -1, -self.seat_mid, 0, 0]
)
* self.size
)
z_anchors = (
np.array([0, 0, self.seat_mid_z, 0, 0, 0, self.seat_mid_z, 0, 0])
* self.thickness
)
vector_locations = [1, 7] if self.is_seat_round else [1, 3, 5, 7]
obj = bezier_curve((x_anchors, y_anchors, z_anchors), vector_locations, 8)
with butil.ViewportMode(obj, "EDIT"):
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.fill_grid(use_interp_simple=True)
butil.modify_mesh(obj, "SOLIDIFY", thickness=self.thickness, offset=0)
subsurf(obj, 1, not self.is_seat_subsurf)
butil.modify_mesh(obj, "BEVEL", width=self.bevel_width, segments=8)
return obj
def make_legs(self):
leg_starts = np.array(
[[-self.seat_back, 0, 0], [-1, -1, 0], [1, -1, 0], [self.seat_back, 0, 0]]
) * np.array([[self.width / 2, self.size, 0]])
leg_ends = leg_starts.copy()
leg_ends[[0, 1], 0] -= self.leg_x_offset
leg_ends[[2, 3], 0] += self.leg_x_offset
leg_ends[[0, 3], 1] += self.leg_y_offset[0]
leg_ends[[1, 2], 1] -= self.leg_y_offset[1]
leg_ends[:, -1] = -self.leg_height
return self.make_limb(leg_ends, leg_starts)
def make_limb(self, leg_ends, leg_starts):
limbs = []
for leg_start, leg_end in zip(leg_starts, leg_ends):
match self.leg_type:
case "up-curved":
axes = [(0, 0, 1), None]
scale = [self.limb_profile, 1]
case "down-curved":
axes = [None, (0, 0, 1)]
scale = [1, self.limb_profile]
case _:
axes = None
scale = None
limb = align_bezier(
np.stack([leg_start, leg_end], -1), axes, scale, resolution=64
)
limb.location = (
np.array(
[
1 if leg_start[0] < 0 else -1,
1 if leg_start[1] < -self.size / 2 else -1,
0,
]
)
* self.leg_thickness
/ 2
)
butil.apply_transform(limb, True)
limbs.append(limb)
return limbs
def make_backs(self):
back_starts = (
np.array([[-self.seat_back, 0, 0], [self.seat_back, 0, 0]]) * self.width / 2
)
back_ends = back_starts.copy()
back_ends[:, 0] += np.array([self.back_x_offset, -self.back_x_offset])
back_ends[:, 1] = self.back_y_offset
back_ends[:, 2] = self.back_height
return self.make_limb(back_starts, back_ends)
def make_leg_decors(self, legs):
decors = []
if self.has_leg_x_bar:
z_height = -self.leg_height * uniform(*self.leg_offset_bar)
locs = []
for leg in legs:
co = read_co(leg)
locs.append(co[np.argmin(np.abs(co[:, -1] - z_height))])
decors.append(
self.solidify(bezier_curve(np.stack([locs[0], locs[3]], -1)), 0)
)
decors.append(
self.solidify(bezier_curve(np.stack([locs[1], locs[2]], -1)), 0)
)
if self.has_leg_y_bar:
z_height = -self.leg_height * uniform(*self.leg_offset_bar)
locs = []
for leg in legs:
co = read_co(leg)
locs.append(co[np.argmin(np.abs(co[:, -1] - z_height))])
decors.append(
self.solidify(bezier_curve(np.stack([locs[0], locs[1]], -1)), 1)
)
decors.append(
self.solidify(bezier_curve(np.stack([locs[2], locs[3]], -1)), 1)
)
for d in decors:
write_attribute(d, 1, "limb", "FACE")
return decors
def make_back_decors(self, backs, finalize=True):
obj = join_objects([deep_clone_obj(b) for b in backs])
x, y, z = read_co(obj).T
x += np.where(x > 0, self.back_thickness / 2, -self.back_thickness / 2)
write_co(obj, np.stack([x, y, z], -1))
smoothness = uniform(0, 1)
profile_shape_factor = uniform(0, 0.4)
with butil.ViewportMode(obj, "EDIT"):
bpy.ops.mesh.select_mode(type="EDGE")
center = read_edge_center(obj)
for z_min, z_max in self.back_profile:
select_edges(
obj,
(z_min * self.back_height <= center[:, -1])
& (center[:, -1] <= z_max * self.back_height),
)
bpy.ops.mesh.bridge_edge_loops(
number_cuts=32,
interpolation="LINEAR",
smoothness=smoothness,
profile_shape_factor=profile_shape_factor,
)
bpy.ops.mesh.select_loose()
bpy.ops.mesh.delete()
butil.modify_mesh(
obj,
"SOLIDIFY",
thickness=np.minimum(self.thickness, self.back_thickness),
offset=0,
)
if finalize:
butil.modify_mesh(obj, "BEVEL", width=self.bevel_width, segments=8)
parts = [obj]
if self.back_type == "vertical-bar":
other = join_objects([deep_clone_obj(b) for b in backs])
with butil.ViewportMode(other, "EDIT"):
bpy.ops.mesh.select_mode(type="EDGE")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.bridge_edge_loops(
number_cuts=self.back_vertical_cuts,
interpolation="LINEAR",
smoothness=smoothness,
profile_shape_factor=profile_shape_factor,
)
bpy.ops.mesh.select_all(action="INVERT")
bpy.ops.mesh.delete()
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.delete(type="ONLY_FACE")
remove_edges(other, np.abs(read_edge_direction(other)[:, -1]) < 0.5)
remove_vertices(other, lambda x, y, z: z < -self.thickness / 2)
remove_vertices(
other,
lambda x, y, z: z
> (self.back_profile[0][0] + self.back_profile[0][1])
* self.back_height
/ 2,
)
parts.append(self.solidify(other, 2, self.back_thickness))
elif self.back_type == "partial":
co = read_co(obj)
co[:, 1] *= self.back_partial_scale
write_co(obj, co)
for p in parts:
write_attribute(p, 1, "panel", "FACE")
return parts
def make_arms(self, base, backs):
co = read_co(base)
end = co[np.argmin(co[:, 0] - (np.abs(co[:, 1] + self.arm_y) < 0.02))]
end[0] += self.arm_thickness / 4
end_ = end.copy()
end_[0] = -end[0]
arms = []
co = read_co(backs[0])
start = co[np.argmin(co[:, 0] - (np.abs(co[:, -1] - self.arm_z) < 0.02))]
start[0] -= self.arm_thickness / 4
start_ = start.copy()
start_[0] = -start[0]
for start, end in zip([start, start_], [end, end_]):
mid = np.array(
[
end[0] + self.arm_mid[0] * (-1 if end[0] > 0 else 1),
end[1] + self.arm_mid[1],
start[2] + self.arm_mid[2],
]
)
arm = align_bezier(
np.stack([start, mid, end], -1),
np.array(
[
[end[0] - start[0], end[1] - start[1], 0],
[0, 1 / np.sqrt(2), 1 / np.sqrt(2)],
[0, 0, 1],
]
),
[1, *self.arm_profile, 1],
)
if self.is_leg_round:
surface.add_geomod(
arm,
geo_radius,
apply=True,
input_args=[self.arm_thickness / 2, 32],
input_kwargs={"to_align_tilt": False},
)
else:
with butil.ViewportMode(arm, "EDIT"):
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.extrude_edges_move(
TRANSFORM_OT_translate={
"value": (
self.arm_thickness
if end[0] < 0
else -self.arm_thickness,
0,
0,
)
}
)
butil.modify_mesh(arm, "SOLIDIFY", thickness=self.arm_height, offset=0)
write_attribute(arm, 1, "limb", "FACE")
arms.append(arm)
return arms
def solidify(self, obj, axis, thickness=None):
if thickness is None:
thickness = self.leg_thickness
if self.is_leg_round:
solidify(obj, axis, thickness)
butil.modify_mesh(obj, "BEVEL", width=self.bevel_width, segments=8)
else:
surface.add_geomod(
obj, geo_radius, apply=True, input_args=[thickness / 2, 32]
)
write_attribute(obj, 1, "limb", "FACE")
return obj