Spaces:
Running
on
Zero
Running
on
Zero
| # 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 | |