Env stuff
This commit is contained in:
parent
97a025dce7
commit
8736d3f4b7
Binary file not shown.
Binary file not shown.
BIN
AssetSources/Meshes/STM_VolcanicRocks.blend (Stored with Git LFS)
BIN
AssetSources/Meshes/STM_VolcanicRocks.blend (Stored with Git LFS)
Binary file not shown.
BIN
AssetSources/Meshes/STM_VolcanicRocks_01.fbx (Stored with Git LFS)
BIN
AssetSources/Meshes/STM_VolcanicRocks_01.fbx (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,619 @@
|
|||
##
|
||||
# A script to split simple, architectural geometry into convex pieces.
|
||||
#
|
||||
# This script makes use of Blender's built-in "Split Concave Faces" clean-up
|
||||
# algorithm to break-up the faces of an object into convex pieces. The script
|
||||
# attempts to identify all the edges that represent convex boundaries, and then
|
||||
# it splits objects up along those edges. Each resulting piece is then made into
|
||||
# a closed object by converting it into a convex hull.
|
||||
#
|
||||
# Be sure to select the object you wish the split into convex pieces before
|
||||
# running the script.
|
||||
#
|
||||
# NOTE: This script is expecting to work with flat, reasonably clean geometry.
|
||||
# For example, it is expected to be used when generating collision on the
|
||||
# ceiling and walls of an architectural visualization project, but is not
|
||||
# expected to perform well with round or n-gon geometry. Using
|
||||
# create_closed_objects=True and matchup_degenerates=True, in particular, does
|
||||
# not work well with objects that have openings inside.
|
||||
#
|
||||
# If this script doesn't work for you, a plug-in like V-HACD may work better.
|
||||
# This script was written to handle cases V-HACD did not handle well -- flat,
|
||||
# reasonably rectangular arch. vis. geometry.
|
||||
#
|
||||
# @author Guy Elsmore-Paddock <guy.paddock@gmail.com>
|
||||
#
|
||||
|
||||
import bmesh
|
||||
import bpy
|
||||
import operator
|
||||
import re
|
||||
|
||||
from itertools import combinations, count
|
||||
from math import atan2, pi, radians, degrees
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def split_into_convex_pieces(ob, create_closed_objects=True,
|
||||
matchup_degenerates=True):
|
||||
object_name = ob.name
|
||||
|
||||
deselect_all_objects()
|
||||
make_all_faces_convex(ob)
|
||||
|
||||
eliminated_piece_names = \
|
||||
split_on_convex_boundaries(
|
||||
ob,
|
||||
create_closed_objects,
|
||||
matchup_degenerates
|
||||
)
|
||||
|
||||
rename_pieces(object_name, eliminated_piece_names)
|
||||
|
||||
# Deselect everything, for the convenience of the user.
|
||||
deselect_all_objects()
|
||||
|
||||
|
||||
def make_all_faces_convex(ob):
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# This is what actually defines the new geometry -- Blender creates the
|
||||
# convex shapes we need to split by.
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.vert_connect_concave()
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
|
||||
##
|
||||
# Splits an object into smaller pieces by its convex, planar edges.
|
||||
#
|
||||
# In an ideal world, we could just split the object by all the edges that are
|
||||
# attached to -- and are co-planar with -- the faces of the object, since those
|
||||
# edges are most likely to represent the convex boundaries of the object. But,
|
||||
# Blender does not provide an easy way to find such edges.
|
||||
#
|
||||
# Instead, we use several heuristics to simulate this type of selection:
|
||||
# 1. First, we select all the sharp edges of the object, since sharp edges are
|
||||
# only co-planar with one of the faces they connect with and are therefore
|
||||
# unlikely to represent convex boundary edges.
|
||||
# 2. Second, we select all edges that are similar in angle to the sharp edges,
|
||||
# to catch any edges that are almost steep enough to be sharp edges.
|
||||
# 3. Third, we invert the selection, which should (hopefully) cause all the
|
||||
# convex boundary edges we want to be selected.
|
||||
# 4. Fourth, we seek out any sharp edges that connect with the convex boundary
|
||||
# edges, since we will need to split on these edges as well so that our
|
||||
# "cuts" go all the way around the object (e.g. if the convex boundary
|
||||
# edges lay on the top and bottom faces of an object, we need to select
|
||||
# sharp edges to connect the top and bottom edges on the left and right
|
||||
# sides so that each split piece is a complete shape instead of just
|
||||
# disconnected, detached planes).
|
||||
# 4. Next, we split the object by all selected edges, which should result in
|
||||
# creation of each convex piece we seek.
|
||||
#
|
||||
def split_on_convex_boundaries(ob, create_closed_objects=True,
|
||||
matchup_degenerates=True):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
select_convex_boundary_edges(ob)
|
||||
|
||||
# Now perform an vertex + edge split along each selected edge, which should
|
||||
# result in the object being broken-up along each planar edge and connected
|
||||
# sharp edges (e.g. on corners).
|
||||
bpy.ops.mesh.edge_split(type='VERT')
|
||||
|
||||
# Now, just break each loose part off into a separate object.
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.separate(type='LOOSE')
|
||||
|
||||
if create_closed_objects:
|
||||
# And then make each piece fully enclosed.
|
||||
return create_closed_shapes_from_pieces(ob, matchup_degenerates)
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
##
|
||||
# Selects all edges that denote the boundaries of convex pieces.
|
||||
#
|
||||
# This is a multi-step process driven by heuristics:
|
||||
# 1. First, we select all the sharp edges of the object, since sharp edges are
|
||||
# only co-planar with one of the faces they connect with and are therefore
|
||||
# unlikely to represent convex boundary edges.
|
||||
# 2. Second, we select all edges that are similar in length to the sharp
|
||||
# edges, to catch any edges that are almost steep enough to be sharp edges.
|
||||
# 3. Third, we invert the selection, which should (hopefully) cause all the
|
||||
# convex boundary edges we want to be selected.
|
||||
#
|
||||
def select_convex_boundary_edges(ob, max_edge_length_proportion=0.1):
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
mesh = ob.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
|
||||
# Enter "Edge" select mode
|
||||
bpy.context.tool_settings.mesh_select_mode = [False, True, False]
|
||||
|
||||
# Find all sharp edges and edges of similar length
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.edges_select_sharp()
|
||||
bpy.ops.mesh.select_similar(type='LENGTH', threshold=0.01)
|
||||
|
||||
# Invert the selection to find the convex boundary edges.
|
||||
bpy.ops.mesh.select_all(action='INVERT')
|
||||
|
||||
bm.faces.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
|
||||
edges_to_select = []
|
||||
max_edge_length = max(ob.dimensions) * max_edge_length_proportion
|
||||
|
||||
for selected_edge in [e for e in bm.edges if e.select]:
|
||||
edge_bridges =\
|
||||
find_shortest_edge_bridges(
|
||||
selected_edge,
|
||||
max_edge_length=max_edge_length
|
||||
)
|
||||
|
||||
for path in edge_bridges.values():
|
||||
for edge in path:
|
||||
edges_to_select.append(edge)
|
||||
|
||||
# Select the edges after we pick which edges we *want* to select, to ensure
|
||||
# that we only base our decisions on the initial convex boundary edges.
|
||||
for edge in edges_to_select:
|
||||
edge.select = True
|
||||
|
||||
|
||||
##
|
||||
# Locate the shortest path of edges to connect already-selected edges.
|
||||
#
|
||||
# This is used to find the additional edges that must be selected for a cut
|
||||
# along a convex boundary to create a complete, closed object shape.
|
||||
#
|
||||
# The max edge length argument can be provided to avoid trying to find
|
||||
# connections between convex boundaries that are very far apart in the same
|
||||
# object.
|
||||
#
|
||||
def find_shortest_edge_bridges(starting_edge, max_edge_length=None):
|
||||
edge_bridges = find_bridge_edges(starting_edge, max_edge_length)
|
||||
sorted_edge_bridges = sorted(edge_bridges, key=lambda eb: eb[0])
|
||||
edge_solutions = {}
|
||||
|
||||
for edge_bridge in sorted_edge_bridges:
|
||||
path_distance, final_edge, path = edge_bridge
|
||||
|
||||
# Skip edges we've already found a min-length path to
|
||||
if final_edge not in edge_solutions.keys():
|
||||
edge_solutions[final_edge] = path
|
||||
|
||||
print(f"Shortest edge bridges for starting edge '{str(starting_edge.index)}':")
|
||||
|
||||
if len(edge_solutions) > 0:
|
||||
print(
|
||||
" - " +
|
||||
"\n - ".join(map(
|
||||
lambda i: str(
|
||||
(i[0].index, str(list(map(lambda e: e.index, i[1]))))
|
||||
),
|
||||
edge_solutions.items()
|
||||
)))
|
||||
print("")
|
||||
print("")
|
||||
|
||||
return edge_solutions
|
||||
|
||||
|
||||
##
|
||||
# Performs a recursive, depth-first search from a selected edge to other edges.
|
||||
#
|
||||
# This returns all possible paths -- and distances of those paths -- to traverse
|
||||
# the mesh from the starting, selected edge to another selected edge. To avoid
|
||||
# a lengthy search, the max_depth parameter controls how many levels of edges
|
||||
# are searched.
|
||||
#
|
||||
# The result is an array of tuples, where each tuple contains the total distance
|
||||
# of the path, the already-selected edge that the path was able to reach, and
|
||||
# the list of edges that would need to be selected in order to reach that
|
||||
# destination edge.
|
||||
#
|
||||
def find_bridge_edges(edge, max_edge_length=None, max_depth=3, current_depth=0,
|
||||
path_distance=0, edge_path=None, seen_verts=None):
|
||||
if edge_path is None:
|
||||
edge_path = []
|
||||
|
||||
if seen_verts is None:
|
||||
seen_verts = []
|
||||
|
||||
# Don't bother searching edges we've seen
|
||||
if edge in edge_path:
|
||||
return []
|
||||
|
||||
if (current_depth > 0):
|
||||
first_edge = edge_path[0]
|
||||
edge_length = edge.calc_length()
|
||||
|
||||
# Don't bother searching edges along the same normal as the first edge.
|
||||
# We want our cuts to be along convex boundaries that are perpendicular.
|
||||
if have_common_face(first_edge, edge):
|
||||
return []
|
||||
|
||||
if edge.select:
|
||||
return [(path_distance, edge, edge_path)]
|
||||
|
||||
# Disqualify edges that are too long.
|
||||
if max_edge_length is not None and edge_length > max_edge_length:
|
||||
print(
|
||||
f"Disqualifying edge {edge.index} because length [{edge_length}] > [{max_edge_length}"
|
||||
)
|
||||
|
||||
return []
|
||||
|
||||
if current_depth == max_depth:
|
||||
return []
|
||||
|
||||
new_edge_path = edge_path + [edge]
|
||||
bridges = []
|
||||
|
||||
for edge_vert in edge.verts:
|
||||
# Don't bother searching vertices we've already seen (no backtracking).
|
||||
if edge_vert in seen_verts:
|
||||
continue
|
||||
|
||||
new_seen_verts = seen_verts + [edge_vert]
|
||||
|
||||
for linked_edge in edge_vert.link_edges:
|
||||
# Don't bother searching selected edges connected to the starting
|
||||
# edge, since that gets us nowhere.
|
||||
if linked_edge.select and current_depth == 0:
|
||||
continue
|
||||
|
||||
edge_length = linked_edge.calc_length()
|
||||
|
||||
found_bridge_edges = find_bridge_edges(
|
||||
edge=linked_edge,
|
||||
max_edge_length=max_edge_length,
|
||||
max_depth=max_depth,
|
||||
current_depth=current_depth + 1,
|
||||
path_distance=path_distance + edge_length,
|
||||
edge_path=new_edge_path,
|
||||
seen_verts=new_seen_verts
|
||||
)
|
||||
|
||||
bridges.extend(found_bridge_edges)
|
||||
|
||||
return bridges
|
||||
|
||||
|
||||
def create_closed_shapes_from_pieces(ob, matchup_degenerates=True,
|
||||
min_volume=0.1):
|
||||
print("Converting each piece into a closed object...")
|
||||
|
||||
degenerate_piece_names = []
|
||||
|
||||
for piece in name_duplicates_of(ob):
|
||||
if not make_piece_convex(piece):
|
||||
degenerate_piece_names.append(piece.name)
|
||||
|
||||
degenerate_count = len(degenerate_piece_names)
|
||||
|
||||
print("")
|
||||
print(f"Total degenerate (flat) pieces: {degenerate_count}")
|
||||
print("")
|
||||
|
||||
eliminated_piece_names = []
|
||||
|
||||
if matchup_degenerates:
|
||||
if degenerate_count > 10:
|
||||
# TODO: Hopefully, some day, find a good deterministic way to
|
||||
# automatically match up any number of degenerate pieces using a
|
||||
# heuristic that generates sane geometry.
|
||||
print(
|
||||
"There are too many degenerates for reliable auto-matching, so "
|
||||
"it will not be performed. You will need to manually combine "
|
||||
"degenerate pieces.")
|
||||
print("")
|
||||
else:
|
||||
eliminated_piece_names.extend(
|
||||
matchup_degenerate_pieces(degenerate_piece_names, min_volume)
|
||||
)
|
||||
|
||||
eliminated_piece_names.extend(
|
||||
eliminate_tiny_pieces(degenerate_piece_names, min_volume)
|
||||
)
|
||||
|
||||
return eliminated_piece_names
|
||||
|
||||
|
||||
def matchup_degenerate_pieces(degenerate_piece_names, min_volume=0.1):
|
||||
pieces_eliminated = []
|
||||
degenerate_volumes = find_degenerate_combos(degenerate_piece_names)
|
||||
|
||||
print("Searching for a way to match-up degenerates into volumes...")
|
||||
|
||||
for piece1_name, piece1_volumes in degenerate_volumes.items():
|
||||
# Skip pieces already joined with degenerate pieces we've processed
|
||||
if piece1_name not in degenerate_piece_names:
|
||||
continue
|
||||
|
||||
piece1 = bpy.data.objects[piece1_name]
|
||||
|
||||
piece1_volumes_asc = dict(
|
||||
sorted(
|
||||
piece1_volumes.items(),
|
||||
key=operator.itemgetter(1)
|
||||
)
|
||||
)
|
||||
|
||||
piece2 = None
|
||||
|
||||
for piece2_name, combo_volume in piece1_volumes_asc.items():
|
||||
# Skip pieces that would make a volume that's too small, or that
|
||||
# have been joined with degenerate pieces we've processed
|
||||
if combo_volume < min_volume or piece2_name not in degenerate_piece_names:
|
||||
continue
|
||||
else:
|
||||
piece2 = bpy.data.objects[piece2_name]
|
||||
break
|
||||
|
||||
if piece2 is not None:
|
||||
degenerate_piece_names.remove(piece2.name)
|
||||
pieces_eliminated.append(piece2.name)
|
||||
|
||||
print(
|
||||
f" - Combining parallel degenerate '{piece1.name}' with "
|
||||
f"'{piece2.name}' to form complete mesh '{piece1.name}'."
|
||||
)
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
bpy.context.view_layer.objects.active = piece1
|
||||
|
||||
piece1.select_set(True)
|
||||
piece2.select_set(True)
|
||||
|
||||
bpy.ops.object.join()
|
||||
|
||||
make_piece_convex(piece1)
|
||||
|
||||
return pieces_eliminated
|
||||
|
||||
|
||||
def find_degenerate_combos(degenerate_piece_names):
|
||||
volumes = {}
|
||||
|
||||
for piece_combo in combinations(degenerate_piece_names, 2):
|
||||
piece1_name, piece2_name = piece_combo
|
||||
piece1 = bpy.data.objects[piece1_name]
|
||||
piece2 = bpy.data.objects[piece2_name]
|
||||
|
||||
if not volumes.get(piece1_name):
|
||||
volumes[piece1_name] = {}
|
||||
|
||||
piece1_mesh = piece1.data
|
||||
piece1_bm = bmesh.new()
|
||||
piece1_bm.from_mesh(piece1_mesh)
|
||||
|
||||
piece2_mesh = piece2.data
|
||||
piece2_bm = bmesh.new()
|
||||
piece2_bm.from_mesh(piece2_mesh)
|
||||
|
||||
piece1_bm.faces.ensure_lookup_table()
|
||||
piece2_bm.faces.ensure_lookup_table()
|
||||
|
||||
if (len(piece1_bm.faces) == 0) or (len(piece2_bm.faces) == 0):
|
||||
continue
|
||||
|
||||
piece1_face = piece1_bm.faces[0]
|
||||
piece2_face = piece2_bm.faces[0]
|
||||
|
||||
combo_angle_radians = piece1_face.normal.angle(piece2_face.normal)
|
||||
combo_angle_degrees = int(round(degrees(combo_angle_radians)))
|
||||
|
||||
# We only combine faces that are parallel to each other
|
||||
if combo_angle_degrees in [0, 180]:
|
||||
combo_volume = convex_volume(piece1, piece2)
|
||||
|
||||
volumes[piece1.name][piece2.name] = combo_volume
|
||||
|
||||
return volumes
|
||||
|
||||
|
||||
def eliminate_tiny_pieces(degenerate_piece_names, min_volume=0.1):
|
||||
eliminated_piece_names = []
|
||||
|
||||
tiny_piece_names = [
|
||||
n for n in degenerate_piece_names
|
||||
if n not in eliminated_piece_names
|
||||
and convex_volume(bpy.data.objects.get(n)) < min_volume
|
||||
]
|
||||
|
||||
print("")
|
||||
print(f"Total remaining tiny pieces: {len(tiny_piece_names)}")
|
||||
|
||||
# Delete tiny pieces that are too small to be useful
|
||||
for tiny_piece_name in tiny_piece_names:
|
||||
print(f" - Eliminating tiny piece '{tiny_piece_name}'...")
|
||||
|
||||
tiny_piece = bpy.data.objects[tiny_piece_name]
|
||||
|
||||
bpy.data.objects.remove(tiny_piece, do_unlink=True)
|
||||
eliminated_piece_names.append(tiny_piece_name)
|
||||
|
||||
print("")
|
||||
|
||||
return eliminated_piece_names
|
||||
|
||||
|
||||
def make_piece_convex(ob, min_volume=0.1):
|
||||
print(
|
||||
f" - Attempting to make '{ob.name}' into a closed, convex "
|
||||
f"shape."
|
||||
)
|
||||
|
||||
volume_before = convex_volume(ob)
|
||||
|
||||
make_convex_hull(ob)
|
||||
|
||||
volume_after = convex_volume(ob)
|
||||
volume_delta = abs(volume_after - volume_before)
|
||||
|
||||
# If the volume of the piece is very small when we tried making it convex,
|
||||
# then it's degenerate -- it's a plane or something flat that we need to
|
||||
# remove.
|
||||
is_degenerate = (volume_after < min_volume)
|
||||
|
||||
print(f" - Volume before: {volume_before}")
|
||||
print(f" - Volume after: {volume_after}")
|
||||
print(f" - Volume delta: {volume_delta}")
|
||||
print(f" - Is degenerate: {is_degenerate}")
|
||||
|
||||
return not is_degenerate
|
||||
|
||||
|
||||
def make_convex_hull(ob):
|
||||
deselect_all_objects()
|
||||
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
ob.select_set(True)
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.mesh.convex_hull()
|
||||
|
||||
mesh = ob.data
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
|
||||
# Clean-up unnecessary edges
|
||||
bmesh.ops.dissolve_limit(
|
||||
bm,
|
||||
angle_limit=radians(5),
|
||||
verts=bm.verts,
|
||||
edges=bm.edges,
|
||||
)
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
|
||||
def have_common_normal(e1, e2):
|
||||
e1_normals = map(lambda f: f.normal, e1.link_faces)
|
||||
e2_normals = map(lambda f: f.normal, e2.link_faces)
|
||||
|
||||
common_normals = [n for n in e1_normals if n in e2_normals]
|
||||
|
||||
return len(common_normals) > 0
|
||||
|
||||
|
||||
def have_common_face(e1, e2):
|
||||
common_faces = [f for f in e1.link_faces if f in e2.link_faces]
|
||||
|
||||
return len(common_faces) > 0
|
||||
|
||||
|
||||
def convex_volume(*obs):
|
||||
meshes = []
|
||||
verts = []
|
||||
|
||||
for ob in obs:
|
||||
mesh = ob.data
|
||||
bm = bmesh.new()
|
||||
|
||||
bm.from_mesh(mesh)
|
||||
|
||||
bm.verts.ensure_lookup_table()
|
||||
bm.edges.ensure_lookup_table()
|
||||
bm.faces.ensure_lookup_table()
|
||||
|
||||
# Prevent early garbage collection.
|
||||
meshes.append(bm)
|
||||
|
||||
geom = list(bm.verts) + list(bm.edges) + list(bm.faces)
|
||||
|
||||
for g in geom:
|
||||
if hasattr(g, "verts"):
|
||||
verts.extend(v.co for v in g.verts)
|
||||
else:
|
||||
verts.append(g.co)
|
||||
|
||||
return build_volume_from_verts(verts)
|
||||
|
||||
|
||||
def build_volume_from_verts(verts):
|
||||
# Based on code from:
|
||||
# https://blender.stackexchange.com/questions/107357/how-to-find-if-geometry-linked-to-an-edge-is-coplanar
|
||||
origin = sum(verts, Vector((0, 0, 0))) / len(verts)
|
||||
bm = bmesh.new()
|
||||
|
||||
for v in verts:
|
||||
bm.verts.new(v - origin)
|
||||
|
||||
bmesh.ops.convex_hull(bm, input=bm.verts)
|
||||
|
||||
return bm.calc_volume()
|
||||
|
||||
|
||||
def deselect_all_objects():
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def rename_pieces(object_name, name_skiplist=None):
|
||||
if name_skiplist is None:
|
||||
name_skiplist = []
|
||||
|
||||
for duplicate_name, old_index_str, new_index in dupe_name_sequence(object_name, name_skiplist):
|
||||
piece = bpy.data.objects.get(duplicate_name)
|
||||
|
||||
if not piece:
|
||||
break
|
||||
|
||||
old_name = piece.name
|
||||
new_name = re.sub(fr"(?:01)?\.{old_index_str}$", f"{new_index:02d}", piece.name)
|
||||
|
||||
if old_name != new_name:
|
||||
print(f"Renaming piece '{old_name}' to '{new_name}'.")
|
||||
piece.name = new_name
|
||||
|
||||
|
||||
def name_duplicates_of(ob):
|
||||
duplicates = []
|
||||
|
||||
for duplicate_name, _, _ in dupe_name_sequence(ob.name):
|
||||
piece = bpy.data.objects.get(duplicate_name)
|
||||
|
||||
if not piece:
|
||||
break
|
||||
else:
|
||||
duplicates.append(piece)
|
||||
|
||||
return duplicates
|
||||
|
||||
|
||||
def dupe_name_sequence(base_name, skiplist=None):
|
||||
if skiplist is None:
|
||||
skiplist = []
|
||||
|
||||
yield base_name, "", 1
|
||||
|
||||
new_index = 1
|
||||
|
||||
for old_name_index in count(start=1):
|
||||
old_index_str = f"{old_name_index:03d}"
|
||||
duplicate_name = f"{base_name}.{old_index_str}"
|
||||
|
||||
if duplicate_name in skiplist:
|
||||
continue
|
||||
else:
|
||||
new_index = new_index + 1
|
||||
|
||||
yield duplicate_name, old_index_str, new_index
|
||||
|
||||
|
||||
split_into_convex_pieces(bpy.context.view_layer.objects.active)
|
||||
print("Done!")
|
|
@ -212,7 +212,6 @@ ReverbPlugin=
|
|||
OcclusionPlugin=
|
||||
SoundCueCookQualityIndex=-1
|
||||
-TargetedRHIs=SF_VULKAN_SM5
|
||||
+TargetedRHIs=SF_VULKAN_SM5
|
||||
+TargetedRHIs=SF_VULKAN_SM6
|
||||
|
||||
[/Script/Engine.PhysicsSettings]
|
||||
|
|
Binary file not shown.
BIN
Content/Environment/Geology/STM_VolcanicRocks_01.uasset (Stored with Git LFS)
BIN
Content/Environment/Geology/STM_VolcanicRocks_01.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Environment/Geology/STM_VolcanicRocks_02.uasset (Stored with Git LFS)
BIN
Content/Environment/Geology/STM_VolcanicRocks_02.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Maps/Gym/Gym_Environment.umap (Stored with Git LFS)
BIN
Content/Maps/Gym/Gym_Environment.umap (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/Materials/MasterMaterials/M_LD_Lava.uasset (Stored with Git LFS)
BIN
Content/Materials/MasterMaterials/M_LD_Lava.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/Materials/MaterialInstances/MI_LD_Color_GreyDark.uasset (Stored with Git LFS)
Normal file
BIN
Content/Materials/MaterialInstances/MI_LD_Color_GreyDark.uasset (Stored with Git LFS)
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -25,6 +25,13 @@
|
|||
{
|
||||
"Name": "GeometryScripting",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "TextureGraph",
|
||||
"Enabled": true,
|
||||
"SupportedTargetPlatforms": [
|
||||
"Win64"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue