更新
This commit is contained in:
1 parent
daf8667065
commit
84ff112176
42 files changed
+3033
-7318
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11
-11
@@ -6,9 +6,11 @@ from typing import cast
|
||||
import requests
|
||||
from download_print import download_datas_by_pre_layout
|
||||
from download_print import BatchModelInfo
|
||||
from download_print import is_test
|
||||
from print_factory_type_setting_obj_run import print_type_setting_obj
|
||||
|
||||
from config import print_factory_type_dir
|
||||
from config import is_test
|
||||
from general import is_run_local_data
|
||||
|
||||
# 如果没有 DISPLAY,自动启动 Xvfb
|
||||
if "DISPLAY" not in os.environ:
|
||||
@@ -161,8 +163,8 @@ def main():
|
||||
|
||||
def test_main():
|
||||
# while True:
|
||||
# data= {'machine_print_counts': 150, 'pre_batch_id': 9910032, 'print_machine_id': 14, 'print_orders': [{'counts': 1, 'layout_z': 35.62070959151145, 'model_weight': 41.862, 'order_id': 917900, 'pid': 376666, 'print_id': 126318}, {'counts': 1, 'layout_z': 31.380497732946978, 'model_weight': 35.158, 'order_id': 917810, 'pid': 376562, 'print_id': 126413}, {'counts': 1, 'layout_z': 26.136854969886777, 'model_weight': 21.403, 'order_id': 917820, 'pid': 376572, 'print_id': 126414}, {'counts': 1, 'layout_z': 37.967177899738104, 'model_weight': 30.48, 'order_id': 917922, 'pid': 376684, 'print_id': 126415}, {'counts': 1, 'layout_z': 20.368887098446734, 'model_weight': 13.097, 'order_id': 918228, 'pid': 377198, 'print_id': 126417}, {'counts': 1, 'layout_z': 21.4227336927951, 'model_weight': 15.859, 'order_id': 917576, 'pid': 376345, 'print_id': 126769}, {'counts': 1, 'layout_z': 27.539947066784066, 'model_weight': 25.185, 'order_id': 917811, 'pid': 376564, 'print_id': 126875}, {'counts': 1, 'layout_z': 31.692723822970393, 'model_weight': 32.796, 'order_id': 917821, 'pid': 376592, 'print_id': 126876}, {'counts': 1, 'layout_z': 20.20031720146163, 'model_weight': 8.664, 'order_id': 917822, 'pid': 376602, 'print_id': 126877}, {'counts': 1, 'layout_z': 22.41680629925537, 'model_weight': 15.206, 'order_id': 917827, 'pid': 376616, 'print_id': 126878}, {'counts': 1, 'layout_z': 25.443981671314514, 'model_weight': 11.965, 'order_id': 917879, 'pid': 376635, 'print_id': 126879}, {'counts': 1, 'layout_z': 31.09222672704002, 'model_weight': 20.318, 'order_id': 917902, 'pid': 376667, 'print_id': 126882}, {'counts': 1, 'layout_z': 30.3317820592175, 'model_weight': 32.964, 'order_id': 917903, 'pid': 376668, 'print_id': 126884}, {'counts': 1, 'layout_z': 28.69639626624317, 'model_weight': 27.751, 'order_id': 917904, 'pid': 376669, 'print_id': 126885}, {'counts': 1, 'layout_z': 28.043180029163203, 'model_weight': 19.866, 'order_id': 917917, 'pid': 376677, 'print_id': 126886}, {'counts': 1, 'layout_z': 28.58783468947201, 'model_weight': 25.505, 'order_id': 918196, 'pid': 377126, 'print_id': 126888}, {'counts': 1, 'layout_z': 43.55871899466554, 'model_weight': 63.512, 'order_id': 918201, 'pid': 377131, 'print_id': 126890}, {'counts': 1, 'layout_z': 32.52479624889478, 'model_weight': 22.943, 'order_id': 918203, 'pid': 377133, 'print_id': 126891}, {'counts': 1, 'layout_z': 33.21834123013511, 'model_weight': 42.163, 'order_id': 918206, 'pid': 377139, 'print_id': 126892}, {'counts': 1, 'layout_z': 27.81674335447885, 'model_weight': 15.455, 'order_id': 918207, 'pid': 377141, 'print_id': 126893}, {'counts': 1, 'layout_z': 29.78203639784899, 'model_weight': 29.696, 'order_id': 918231, 'pid': 377201, 'print_id': 126894}, {'counts': 1, 'layout_z': 23.66875736859606, 'model_weight': 13.39, 'order_id': 915637, 'pid': 373135, 'print_id': 126895}, {'counts': 1, 'layout_z': 29.00408914521088, 'model_weight': 19.553, 'order_id': 918246, 'pid': 377209, 'print_id': 126897}, {'counts': 1, 'layout_z': 24.44397251338635, 'model_weight': 15.424, 'order_id': 918325, 'pid': 376974, 'print_id': 126899}, {'counts': 1, 'layout_z': 31.755406379699707, 'model_weight': 31.131, 'order_id': 918492, 'pid': 377584, 'print_id': 126900}, {'counts': 1, 'layout_z': 38.03699946264237, 'model_weight': 47.412, 'order_id': 918493, 'pid': 377587, 'print_id': 126901}, {'counts': 1, 'layout_z': 43.52919799223749, 'model_weight': 37.842, 'order_id': 918494, 'pid': 377589, 'print_id': 126902}, {'counts': 1, 'layout_z': 25.906302369108843, 'model_weight': 18.64, 'order_id': 918495, 'pid': 377590, 'print_id': 126903}, {'counts': 1, 'layout_z': 36.02611978829816, 'model_weight': 43.814, 'order_id': 918496, 'pid': 377591, 'print_id': 126904}, {'counts': 1, 'layout_z': 26.020689766262052, 'model_weight': 18.513, 'order_id': 918497, 'pid': 377594, 'print_id': 126905}, {'counts': 1, 'layout_z': 23.988078117370605, 'model_weight': 15.753, 'order_id': 918498, 'pid': 377596, 'print_id': 126906}, {'counts': 1, 'layout_z': 27.457814063914604, 'model_weight': 23.507, 'order_id': 918500, 'pid': 377601, 'print_id': 126908}, {'counts': 1, 'layout_z': 29.306090320672993, 'model_weight': 25.043, 'order_id': 918501, 'pid': 377605, 'print_id': 126909}, {'counts': 1, 'layout_z': 29.070231478924583, 'model_weight': 25.598, 'order_id': 918504, 'pid': 377606, 'print_id': 126910}, {'counts': 1, 'layout_z': 24.314504768436095, 'model_weight': 18.68, 'order_id': 918555, 'pid': 377684, 'print_id': 126911}, {'counts': 1, 'layout_z': 37.52979171030315, 'model_weight': 53.541, 'order_id': 918558, 'pid': 377689, 'print_id': 126913}, {'counts': 1, 'layout_z': 26.530791960490333, 'model_weight': 15.082, 'order_id': 918560, 'pid': 377702, 'print_id': 126915}, {'counts': 1, 'layout_z': 23.604568481445312, 'model_weight': 21.614, 'order_id': 918583, 'pid': 377748, 'print_id': 126917}, {'counts': 1, 'layout_z': 27.865868478825355, 'model_weight': 22.6, 'order_id': 918636, 'pid': 377819, 'print_id': 126919}, {'counLine truncated
|
||||
data= {"machine_print_counts":50,"pre_batch_id":991112,"print_machine_id":22,"print_orders":[{"counts":1,"layout_z":3.543965775898459,"model_weight":12.833,"order_id":857420,"pid":268473,"print_id":85240},{"counts":1,"layout_z":7.798861189952786,"model_weight":13.784,"order_id":875986,"pid":305425,"print_id":88136}]}
|
||||
# data= {'machine_print_counts': 150, 'machine_type': 'small_machine', 'pre_batch_id': 9910032, 'print_machine_id': 14, 'print_orders': [{'counts': 1, 'layout_z': 35.62070959151145, 'model_weight': 41.862, 'order_id': 917900, 'pid': 376666, 'print_id': 126318}, {'counts': 1, 'layout_z': 31.380497732946978, 'model_weight': 35.158, 'order_id': 917810, 'pid': 376562, 'print_id': 126413}, {'counts': 1, 'layout_z': 26.136854969886777, 'model_weight': 21.403, 'order_id': 917820, 'pid': 376572, 'print_id': 126414}, {'counts': 1, 'layout_z': 37.967177899738104, 'model_weight': 30.48, 'order_id': 917922, 'pid': 376684, 'print_id': 126415}, {'counts': 1, 'layout_z': 20.368887098446734, 'model_weight': 13.097, 'order_id': 918228, 'pid': 377198, 'print_id': 126417}, {'counts': 1, 'layout_z': 21.4227336927951, 'model_weight': 15.859, 'order_id': 917576, 'pid': 376345, 'print_id': 126769}, {'counts': 1, 'layout_z': 27.539947066784066, 'model_weight': 25.185, 'order_id': 917811, 'pid': 376564, 'print_id': 126875}, {'counts': 1, 'layout_z': 31.692723822970393, 'model_weight': 32.796, 'order_id': 917821, 'pid': 376592, 'print_id': 126876}, {'counts': 1, 'layout_z': 20.20031720146163, 'model_weight': 8.664, 'order_id': 917822, 'pid': 376602, 'print_id': 126877}, {'counts': 1, 'layout_z': 22.41680629925537, 'model_weight': 15.206, 'order_id': 917827, 'pid': 376616, 'print_id': 126878}, {'counts': 1, 'layout_z': 25.443981671314514, 'model_weight': 11.965, 'order_id': 917879, 'pid': 376635, 'print_id': 126879}, {'counts': 1, 'layout_z': 31.09222672704002, 'model_weight': 20.318, 'order_id': 917902, 'pid': 376667, 'print_id': 126882}, {'counts': 1, 'layout_z': 30.3317820592175, 'model_weight': 32.964, 'order_id': 917903, 'pid': 376668, 'print_id': 126884}, {'counts': 1, 'layout_z': 28.69639626624317, 'model_weight': 27.751, 'order_id': 917904, 'pid': 376669, 'print_id': 126885}, {'counts': 1, 'layout_z': 28.043180029163203, 'model_weight': 19.866, 'order_id': 917917, 'pid': 376677, 'print_id': 126886}, {'counts': 1, 'layout_z': 28.58783468947201, 'model_weight': 25.505, 'order_id': 918196, 'pid': 377126, 'print_id': 126888}, {'counts': 1, 'layout_z': 43.55871899466554, 'model_weight': 63.512, 'order_id': 918201, 'pid': 377131, 'print_id': 126890}, {'counts': 1, 'layout_z': 32.52479624889478, 'model_weight': 22.943, 'order_id': 918203, 'pid': 377133, 'print_id': 126891}, {'counts': 1, 'layout_z': 33.21834123013511, 'model_weight': 42.163, 'order_id': 918206, 'pid': 377139, 'print_id': 126892}, {'counts': 1, 'layout_z': 27.81674335447885, 'model_weight': 15.455, 'order_id': 918207, 'pid': 377141, 'print_id': 126893}, {'counts': 1, 'layout_z': 29.78203639784899, 'model_weight': 29.696, 'order_id': 918231, 'pid': 377201, 'print_id': 126894}, {'counts': 1, 'layout_z': 23.66875736859606, 'model_weight': 13.39, 'order_id': 915637, 'pid': 373135, 'print_id': 126895}, {'counts': 1, 'layout_z': 29.00408914521088, 'model_weight': 19.553, 'order_id': 918246, 'pid': 377209, 'print_id': 126897}, {'counts': 1, 'layout_z': 24.44397251338635, 'model_weight': 15.424, 'order_id': 918325, 'pid': 376974, 'print_id': 126899}, {'counts': 1, 'layout_z': 31.755406379699707, 'model_weight': 31.131, 'order_id': 918492, 'pid': 377584, 'print_id': 126900}, {'counts': 1, 'layout_z': 38.03699946264237, 'model_weight': 47.412, 'order_id': 918493, 'pid': 377587, 'print_id': 126901}, {'counts': 1, 'layout_z': 43.52919799223749, 'model_weight': 37.842, 'order_id': 918494, 'pid': 377589, 'print_id': 126902}, {'counts': 1, 'layout_z': 25.906302369108843, 'model_weight': 18.64, 'order_id': 918495, 'pid': 377590, 'print_id': 126903}, {'counts': 1, 'layout_z': 36.02611978829816, 'model_weight': 43.814, 'order_id': 918496, 'pid': 377591, 'print_id': 126904}, {'counts': 1, 'layout_z': 26.020689766262052, 'model_weight': 18.513, 'order_id': 918497, 'pid': 377594, 'print_id': 126905}, {'counts': 1, 'layout_z': 23.988078117370605, 'model_weight': 15.753, 'order_id': 918498, 'pid': 377596, 'print_id': 126906}, {'counts': 1, 'layout_z': 27.457814063914604, 'model_weight': 23.507, 'order_id': 918500, 'pid': 377601, 'print_id': 126908}, {'counts': 1, 'layout_z': 29.306090320672993, 'model_weight': 25.043, 'order_id': 918501, 'pid': 377605, 'print_id': 126909}, {'counts': 1, 'layout_z': 29.070231478924583, 'model_weight': 25.598, 'order_id': 918504, 'pid': 377606, 'print_id': 126910}, {'counts': 1, 'layout_z': 24.314504768436095, 'model_weight': 18.68, 'order_id': 918555, 'pid': 377684, 'print_id': 126911}, {'counts': 1, 'layout_z': 37.52979171030315, 'model_weight': 53.541, 'order_id': 918558, 'pid': 377689, 'print_id': 126913}, {'counts': 1, 'layout_z': 26.530791960490333, 'model_weight': 15.082, 'order_id': 918560, 'pid': 377702, 'print_id': 126915}, {'counts': 1, 'layout_z': 23.604568481445312, 'model_weight': 21.614, 'order_id': 918583, 'pid': 377748, 'print_id': 126917}, {'counts': 1, 'layout_z': 27.865868478825355, 'model_weight': 22.6, 'order_id': 918636, 'pid': 37Line truncated
|
||||
data= {"machine_print_counts":50, 'machine_type': 'small_machine',"pre_batch_id":991112,"print_machine_id":22,"print_orders":[{"counts":1,"layout_z":3.543965775898459,"model_weight":12.833,"order_id":857420,"pid":268473,"print_id":85240},{"counts":1,"layout_z":7.798861189952786,"model_weight":13.784,"order_id":875986,"pid":305425,"print_id":88136}]}
|
||||
|
||||
process_clound_print(data)
|
||||
|
||||
@@ -218,9 +220,7 @@ def process_clound_print(data):
|
||||
|
||||
machine_print_counts = parsed["machine_print_counts"]
|
||||
|
||||
machine_type = "big_machine"
|
||||
if not is_test:
|
||||
machine_type = parsed["machine_type"]
|
||||
machine_type = parsed["machine_type"]
|
||||
|
||||
if machine_type == "small_machine" :
|
||||
selected_machine = "小机型"
|
||||
@@ -313,7 +313,6 @@ def process_clound_print(data):
|
||||
|
||||
print("-" * 40)
|
||||
|
||||
print_factory_type_dir="/root/print_factory_type"
|
||||
workdir = f"{print_factory_type_dir}/data/{pre_batch_id}"
|
||||
oss_config = f"{print_factory_type_dir}/print_factory_type_setting_big/download_print/run.yaml"
|
||||
|
||||
@@ -331,14 +330,15 @@ def process_clound_print(data):
|
||||
src_dir = pre_batch_id
|
||||
selected_mode="紧凑" # 标准 紧凑
|
||||
output_format="JSON" # 模型 JSON
|
||||
cache_type_setting_dir=f"{print_factory_type_dir}/data/{src_dir}/arrange"
|
||||
base_original_obj_dir = f"{print_factory_type_dir}/data/{src_dir}"
|
||||
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,
|
||||
batch_id=pre_batch_id,show_chart=False,selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)
|
||||
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,batch_id=pre_batch_id,
|
||||
selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)
|
||||
#排版结束
|
||||
|
||||
if __name__ == '__main__':
|
||||
if is_test:
|
||||
print(f"is_run_local_data={is_run_local_data()}")
|
||||
if is_run_local_data():
|
||||
# if is_test:
|
||||
test_main()
|
||||
else:
|
||||
print(f"is_test={is_test}, run main")
|
||||
|
||||
@@ -0,0 +1,748 @@
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
import copy
|
||||
import time
|
||||
import argparse
|
||||
|
||||
from general import *
|
||||
|
||||
# -------------------------- 开始:获取z值最低 --------------------------
|
||||
|
||||
"""
|
||||
对外部提供的获取最低z的接口
|
||||
get_lowest_position_of_z_out
|
||||
|
||||
参数:
|
||||
obj_path, 模型数据路径
|
||||
|
||||
返回:
|
||||
total_matrix: 旋转矩阵
|
||||
z_max: Z最高点
|
||||
"""
|
||||
|
||||
def get_lowest_position_of_z_out(mesh_obj):
|
||||
|
||||
total_matrix = np.eye(4)
|
||||
|
||||
voxel_size = 3
|
||||
|
||||
# print(f"obj_path={obj_path}, get_lowest_position_of_center voxel_size={voxel_size}")
|
||||
start_time1 = time.time()
|
||||
|
||||
vertices = np.asarray(mesh_obj.vertices)
|
||||
|
||||
# 确保网格有顶点
|
||||
if len(vertices) == 0:
|
||||
# raise ValueError(f"Mesh has no vertices: {obj_path}")
|
||||
print(f"Warning: Mesh has no vertices: {mesh_obj}")
|
||||
return None
|
||||
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# print("voxel_size",voxel_size,obj_path, len(pcd.points), len(mesh_obj.vertices))
|
||||
|
||||
# 对点云进行下采样(体素网格法)
|
||||
#"""
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
if len(np.asarray(pcd_downsampled.points)) <= 0:
|
||||
bbox = pcd.get_axis_aligned_bounding_box()
|
||||
volume = bbox.volume()
|
||||
|
||||
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
|
||||
|
||||
# 处理体积为零的情况
|
||||
if volume <= 0:
|
||||
# 计算点云的实际范围
|
||||
points = np.asarray(pcd.points)
|
||||
if len(points) > 0:
|
||||
min_bound = np.min(points, axis=0)
|
||||
max_bound = np.max(points, axis=0)
|
||||
extent = max_bound - min_bound
|
||||
|
||||
# 确保最小维度至少为0.01
|
||||
min_dimension = max(0.01, np.min(extent))
|
||||
volume = min_dimension ** 3
|
||||
else:
|
||||
volume = 1.0 # 最后的安全回退
|
||||
|
||||
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
|
||||
|
||||
# 安全计算密度 - 防止除零错误
|
||||
if len(pcd.points) > 0 and volume > 0:
|
||||
original_density = len(pcd.points) / volume
|
||||
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
|
||||
else:
|
||||
# 当点数为0或体积为0时使用默认体素大小
|
||||
voxel_size = 1.0 # 默认值
|
||||
|
||||
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
|
||||
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# print("get_lowest_position_of_center1 time", time.time()-start_time1)
|
||||
start_time2 = time.time()
|
||||
# 确保下采样后有点云
|
||||
if len(np.asarray(pcd_downsampled.points)) == 0:
|
||||
# 使用原始点云作为后备
|
||||
pcd_downsampled = pcd
|
||||
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
|
||||
# 初始化最小重心Y的值
|
||||
max_z_of_mass_y = float('inf')
|
||||
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
best_angle_x, best_angle_y, best_angle_z, max_z_of_mass_y = parallel_rotation(points, angle_step=3)
|
||||
|
||||
# 使用最佳角度进行旋转并平移obj
|
||||
pcd_transformed = copy.deepcopy(mesh_obj)
|
||||
|
||||
# 最佳角度旋转
|
||||
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
|
||||
pcd_transformed.rotate(R_x)
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
|
||||
pcd_transformed.rotate(R_y)
|
||||
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
|
||||
pcd_transformed.rotate(R_z)
|
||||
|
||||
T_x = np.eye(4)
|
||||
T_x[:3, :3] = R_x
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_x @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_y = np.eye(4)
|
||||
T_y[:3, :3] = R_y
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_y @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_z = np.eye(4)
|
||||
T_z[:3, :3] = R_z
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_z @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#试着旋转180,让脸朝上
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans1 = np.eye(4)
|
||||
T_trans1[:3, 3] = translation_vector
|
||||
total_matrix = T_trans1 @ total_matrix
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean1 = np.mean(vertices[:, 2])
|
||||
z_max1 = np.max(vertices[:, 2])
|
||||
start_time_v1 = time.time()
|
||||
volume_centroid = get_volume_centroid(pcd_transformed)
|
||||
z_volume_center1 = volume_centroid[2]
|
||||
delta = time.time() - start_time_v1
|
||||
# print(f"get_volume_centroid time={delta}")
|
||||
|
||||
angle_rad = np.pi
|
||||
#print("旋转前质心:", pcd_transformed.get_center())
|
||||
#print("旋转前点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
# 构建绕中心点旋转的变换矩阵[3](@ref)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
R_y180 = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
T_rotate = np.eye(4)
|
||||
T_rotate[:3, :3] = R_y180
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_rotate @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#print("旋转后质心:", pcd_transformed.get_center())
|
||||
#print("旋转后点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
|
||||
#
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
max_z = np.max(vertices[:, 2])
|
||||
# print("min_z1", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
# translation_vector = np.array([0,0,-min_z + (min_z-max_z),])
|
||||
# print("translation_vector1",translation_vector)
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans2 = np.eye(4)
|
||||
T_trans2[:3, 3] = translation_vector
|
||||
translation = total_matrix[:3, 3]
|
||||
# print("translation_vector2",translation_vector)
|
||||
# print(1,translation)
|
||||
|
||||
total_matrix = T_trans2 @ total_matrix
|
||||
translation = total_matrix[:3, 3]
|
||||
# print(2,translation)
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean2 = np.mean(vertices[:, 2])
|
||||
z_max2 = np.max(vertices[:, 2])
|
||||
volume_centroid = get_volume_centroid(pcd_transformed)
|
||||
z_volume_center2 = volume_centroid[2]
|
||||
|
||||
# print(f"get_lowest_position_of_center z_max1={z_max1}, z_max2={z_max2}, len={len(pcd_transformed.vertices)}, obj_path={obj_path}")
|
||||
|
||||
# print(f"z_mean1={z_mean1}, z_mean2={z_mean2}")
|
||||
# if (z_mean2 > z_mean1):
|
||||
print(f"z_volume_center1={z_volume_center1}, z_volume_center2={z_volume_center2}")
|
||||
if (z_volume_center2 > z_volume_center1):
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
centroid = pcd_transformed.get_center()
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
# 构建反向旋转矩阵
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
T_rotate_inv = np.eye(4)
|
||||
T_rotate_inv[:3, :3] = R_y
|
||||
# 完整的反向绕中心旋转矩阵
|
||||
T_rot_center_inv = T_origin_to_center @ T_rotate_inv @ T_center_to_origin
|
||||
total_matrix = T_rot_center_inv @ total_matrix
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
# print("min_z2", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans3 = np.eye(4)
|
||||
T_trans3[:3, 3] = translation_vector
|
||||
total_matrix = T_trans3 @ total_matrix
|
||||
|
||||
# z_mean_min = min(z_mean1, z_mean2)
|
||||
z_max = min(z_max1, z_max2)
|
||||
|
||||
# print("get_lowest_position_of_center2 time", time.time()-start_time2)
|
||||
return total_matrix, z_max
|
||||
|
||||
def get_lowest_position_of_center_ext(mesh_obj, total_matrix):
|
||||
# print(f"get_lowest_position_of_center_ext {obj_path}")
|
||||
temp_matrix, z_max = get_lowest_position_of_z_out(mesh_obj)
|
||||
|
||||
total_matrix = temp_matrix @ total_matrix
|
||||
|
||||
return total_matrix, z_max
|
||||
|
||||
def calculate_rotation_and_top_of_mass(angle_x, angle_y, angle_z, points):
|
||||
"""计算某一组旋转角度后的重心"""
|
||||
# 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||
R_x = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||
])
|
||||
|
||||
R_y = np.array([
|
||||
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||
[0, 1, 0],
|
||||
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||
])
|
||||
|
||||
R_z = np.array([
|
||||
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
# 综合旋转矩阵
|
||||
R = R_z @ R_y @ R_x
|
||||
|
||||
# 执行旋转
|
||||
rotated_points = points @ R.T
|
||||
|
||||
# 计算最小z值
|
||||
min_z = np.min(rotated_points[:, 2])
|
||||
|
||||
# 计算平移向量,将最小Z值平移到0
|
||||
translation_vector = np.array([0, 0, -min_z])
|
||||
rotated_points += translation_vector
|
||||
|
||||
top_of_mass = np.max(rotated_points, axis=0)
|
||||
|
||||
return top_of_mass[2], angle_x, angle_y, angle_z
|
||||
|
||||
def parallel_rotation(points, angle_step=4):
|
||||
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
|
||||
max_top = float('inf')
|
||||
|
||||
for angle_x in range(-90, 90, angle_step):
|
||||
for angle_y in range(0, 360, angle_step):
|
||||
max_z, ax, ay, _ = calculate_rotation_and_top_of_mass(angle_x, angle_y, 0, points)
|
||||
if max_z < max_top:
|
||||
max_top = max_z
|
||||
best_angle_x = ax
|
||||
best_angle_y = ay
|
||||
|
||||
return (best_angle_x, best_angle_y, 0, max_top)
|
||||
|
||||
def compute_mesh_center(vertices):
|
||||
|
||||
if len(vertices) == 0:
|
||||
raise ValueError("顶点数组不能为空")
|
||||
|
||||
# 确保vertices是NumPy数组
|
||||
vertices_np = np.asarray(vertices)
|
||||
|
||||
# 使用NumPy的mean函数直接计算均值(向量化操作)
|
||||
centroid = np.mean(vertices_np, axis=0)
|
||||
|
||||
return centroid
|
||||
|
||||
# -------------------------- 结束:获取z值最低 --------------------------
|
||||
|
||||
# -------------------------- 开始:bbox --------------------------
|
||||
|
||||
def get_models_bbox(dict_pcd_fix):
|
||||
"""
|
||||
单独提取:从dict_fix中解析所有模型的包围盒(bbox)尺寸信息
|
||||
:param dict_fix: 包含PLY文件名和对应点云的字典
|
||||
:return: 模型列表(包含name和dimensions)
|
||||
"""
|
||||
all_models = []
|
||||
extend_dist = 2 # 尺寸扩展量(单位:厘米)
|
||||
for ply_file in dict_pcd_fix:
|
||||
# 解析PLY文件名中的尺寸信息(格式:"模型ID=维度1+维度2+维度3.ply")
|
||||
bbox_with_text = ply_file.split("=")
|
||||
bbox_with = bbox_with_text[-1]
|
||||
split_text = bbox_with.replace(".ply", "").split("+")
|
||||
|
||||
# 转换单位:米 → 厘米 → 加扩展量 → 转回米(int取整避免浮点数精度问题)
|
||||
x_length = int(float(split_text[2]) * 100) + extend_dist # 第三个维度→x方向
|
||||
y_length = int(float(split_text[0]) * 100) + extend_dist # 第一个维度→y方向
|
||||
z_length = int(float(split_text[1]) * 100) + extend_dist # 第二个维度→z方向
|
||||
|
||||
all_models.append({
|
||||
'name': ply_file,
|
||||
'dimensions': (int(x_length / 100), int(z_length / 100), int(y_length / 100)) # 单位:米
|
||||
})
|
||||
return all_models
|
||||
|
||||
def arrange_models_on_platform(models, machine_size):
|
||||
"""
|
||||
单独提取:将模型在打印平台上进行排版布局
|
||||
:param models: 由get_models_bbox返回的模型列表(包含name和dimensions)
|
||||
:param machine_size: 打印机尺寸 (width, depth, height)
|
||||
:return: (placed_models, unplaced_models) - 已放置和未放置的模型列表
|
||||
"""
|
||||
# 初始化打印平台
|
||||
platform = Platform(
|
||||
int(machine_size[0]),
|
||||
int(machine_size[1]),
|
||||
int(machine_size[2])
|
||||
)
|
||||
print("开始计算排序...")
|
||||
platform.arrange_models(models)
|
||||
platform.print_results()
|
||||
return platform.get_result()
|
||||
|
||||
import os
|
||||
|
||||
def compute_bbox_all_ext(base_original_obj_dir,compact_min_dis=True):
|
||||
|
||||
obj_id_list = [aa.split(".o")[0] for aa in os.listdir(base_original_obj_dir) if aa.endswith(".obj")]
|
||||
obj_id_list = obj_id_list
|
||||
|
||||
dict_mesh_obj = {}
|
||||
for pid_t_y in obj_id_list:
|
||||
|
||||
obj_name = pid_t_y+".obj"
|
||||
obj_path = os.path.join(base_original_obj_dir,obj_name)
|
||||
mesh_obj = read_mesh(obj_path)
|
||||
|
||||
if mesh_obj is None:
|
||||
continue
|
||||
|
||||
dict_mesh_obj[obj_name] = mesh_obj
|
||||
|
||||
return compute_bbox_all(dict_mesh_obj,compact_min_dis)
|
||||
|
||||
def compute_bbox_all(dict_mesh_obj,compact_min_dis):
|
||||
dict_total_matrix= {}
|
||||
dict_pcd_fix= {}
|
||||
for key, value in dict_mesh_obj.items():
|
||||
start_time = time.time()
|
||||
|
||||
obj_name = key
|
||||
mesh_obj = value
|
||||
|
||||
total_matrix, pcd_fix, ply_name = compute_bbox(mesh_obj,obj_name,compact_min_dis)
|
||||
|
||||
dict_total_matrix[obj_name] = total_matrix
|
||||
dict_pcd_fix[ply_name] = pcd_fix
|
||||
|
||||
print(f"compute_bbox {obj_name} time={time.time()-start_time}")
|
||||
|
||||
all_models = get_models_bbox(dict_pcd_fix)
|
||||
|
||||
return dict_total_matrix,all_models
|
||||
|
||||
def compute_bbox(mesh_obj,obj_name,compact_min_dis):
|
||||
|
||||
total_matrix = np.eye(4)
|
||||
total_matrix, z_min= get_lowest_position_of_center_ext(mesh_obj, total_matrix)
|
||||
|
||||
transformed_vertices = mesh_transform_by_matrix(np.asarray(mesh_obj.vertices), total_matrix)
|
||||
|
||||
obj_transformed = copy.deepcopy(mesh_obj)
|
||||
obj_transformed.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
voxel_size = 3 # 设置体素的大小,决定下采样的密度
|
||||
# 将点云摆正和X轴平衡
|
||||
obj_transformed_second,total_matrix = arrange_box_correctly(obj_transformed,voxel_size,total_matrix)
|
||||
|
||||
obj_transformed,total_matrix,pcd_fix,ply_name = get_new_bbox(obj_transformed_second,obj_name,voxel_size,compact_min_dis,total_matrix)
|
||||
|
||||
return total_matrix,pcd_fix,ply_name
|
||||
|
||||
def arrange_box_correctly(obj_transformed, voxel_size,total_matrix):
|
||||
|
||||
vertices = np.asarray(obj_transformed.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# 降采样与特征计算
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
cov = np.cov(points.T)
|
||||
|
||||
center = obj_transformed.get_center()
|
||||
|
||||
# 特征分解与方向约束(关键修改点)
|
||||
eigen_vals, eigen_vecs = np.linalg.eigh(cov)
|
||||
max_axis = eigen_vecs[:, np.argmax(eigen_vals)]
|
||||
|
||||
# print("max_axis", max_axis)
|
||||
# 强制主方向向量X分量为正(指向右侧)
|
||||
if max_axis[0] < 0 or (max_axis[0] == 0 and max_axis[1] < 0):
|
||||
max_axis = -max_axis
|
||||
|
||||
target_dir = np.array([1, 0]) # 目标方向为X正轴
|
||||
current_dir = max_axis[:2] / np.linalg.norm(max_axis[:2])
|
||||
dot_product = np.dot(current_dir, target_dir)
|
||||
|
||||
# print("dot_product", dot_product)
|
||||
if dot_product < 0.8: # 阈值控制方向敏感性(建议0.6~0.9)
|
||||
max_axis = -max_axis # 强制翻转方向
|
||||
|
||||
# 计算旋转角度
|
||||
angle_z = np.arctan2(max_axis[1], max_axis[0]) % (2 * np.pi)
|
||||
|
||||
if max_axis[0] <= 0 and max_axis[1] <= 0:
|
||||
angle_z += np.pi
|
||||
|
||||
R = o3d.geometry.get_rotation_matrix_from_axis_angle([0, 0, -angle_z])
|
||||
|
||||
T = np.eye(4)
|
||||
T[:3, :3] = R
|
||||
T[:3, 3] = center - R.dot(center) # 保持中心不变
|
||||
obj_transformed.transform(T)
|
||||
|
||||
total_matrix = T @ total_matrix
|
||||
|
||||
return obj_transformed, total_matrix
|
||||
|
||||
def get_new_bbox(obj_transformed_second,obj_name,voxel_size,compact_min_dis,total_matrix):
|
||||
|
||||
# 计算点云的边界
|
||||
points = np.asarray(obj_transformed_second.vertices)
|
||||
|
||||
min_bound = np.min(points, axis=0) # 获取点云的最小边界
|
||||
max_bound = np.max(points, axis=0) # 获取点云的最大边界
|
||||
|
||||
# 确保包围盒的Y坐标不低于0
|
||||
min_bound[2] = max(min_bound[2], 0) # 确保Y坐标的最小值不低于0
|
||||
|
||||
# 重新计算包围盒的中心和半长轴
|
||||
bbox_center = (min_bound + max_bound) / 2 # 计算包围盒的中心点
|
||||
bbox_extent = (max_bound - min_bound) # 计算包围盒的半长轴(尺寸)
|
||||
|
||||
# 创建包围盒,确保尺寸正确
|
||||
new_bbox = o3d.geometry.OrientedBoundingBox(center=bbox_center,
|
||||
R=np.eye(3), # 旋转矩阵,默认没有旋转
|
||||
extent=bbox_extent)
|
||||
# 获取包围盒的长、宽和高
|
||||
x_length = round(bbox_extent[0],3) # X 方向的长
|
||||
y_length = round(bbox_extent[1],3) # Y 方向的宽
|
||||
z_length = round(bbox_extent[2],3) # Z 方向的高
|
||||
bbox_points = np.array([
|
||||
[min_bound[0], min_bound[1], min_bound[2]],
|
||||
[max_bound[0], min_bound[1], min_bound[2]],
|
||||
[max_bound[0], max_bound[1], min_bound[2]],
|
||||
[min_bound[0], max_bound[1], min_bound[2]],
|
||||
[min_bound[0], min_bound[1], max_bound[2]],
|
||||
[max_bound[0], min_bound[1], max_bound[2]],
|
||||
[max_bound[0], max_bound[1], max_bound[2]],
|
||||
[min_bound[0], max_bound[1], max_bound[2]]
|
||||
])
|
||||
first_corner = bbox_points[2]
|
||||
translation_vector = -first_corner
|
||||
obj_transformed_second.translate(translation_vector)
|
||||
|
||||
T_trans = np.eye(4)
|
||||
T_trans[:3, 3] = translation_vector # 设置平移分量 [2,3](@ref)
|
||||
total_matrix = T_trans @ total_matrix # 矩阵乘法顺序:最新变换左乘[4,5](@ref)
|
||||
|
||||
new_bbox.translate(translation_vector)
|
||||
|
||||
vertices = np.asarray(obj_transformed_second.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
if compact_min_dis:
|
||||
pcd_downsampled = down_sample(pcd, voxel_size, False)
|
||||
pcd_fix = pcd_downsampled
|
||||
else:
|
||||
pcd_fix = pcd
|
||||
|
||||
ply_print_pid = obj_name.replace(".obj","")
|
||||
ply_name = f"{ply_print_pid}={z_length}+{y_length}+{x_length}.ply"
|
||||
|
||||
return obj_transformed_second, total_matrix, pcd_fix, ply_name
|
||||
|
||||
class Platform:
|
||||
def __init__(self, width, depth, height):
|
||||
self.width = width
|
||||
self.depth = depth
|
||||
self.height = height
|
||||
self.placed_models = [] # 已放置的模型
|
||||
self.unplaced_models = [] # 未能放置的模型
|
||||
self.first_line = True
|
||||
self.remove_multiobj_name = ""
|
||||
|
||||
def is_cross_border(self, x, y, z, model):
|
||||
mx, my, mz = model['dimensions']
|
||||
|
||||
return is_cross_border_c(x, y, z, mx, my, mz, self.width, self.depth, self.height)
|
||||
|
||||
def check_multiobj_cross_pre(self, name, pre_model):
|
||||
if (pre_model==None):
|
||||
return
|
||||
|
||||
if not is_same_obj(name, pre_model['name']):
|
||||
return
|
||||
|
||||
self.unplaced_models.append(pre_model)
|
||||
if pre_model in self.placed_models:
|
||||
self.placed_models.remove(pre_model)
|
||||
|
||||
if "pre_model" in pre_model:
|
||||
self.pre_model = pre_model["pre_model"]
|
||||
|
||||
self.check_multiobj_cross_pre(name, self.pre_model)
|
||||
|
||||
def check_multiobj_cross(self, model):
|
||||
if not is_multi_obj(model['name']):
|
||||
return False
|
||||
|
||||
self.unplaced_models.append(model)
|
||||
|
||||
if "pre_model" in model:
|
||||
self.pre_model = model["pre_model"]
|
||||
|
||||
self.check_multiobj_cross_pre(model['name'], self.pre_model)
|
||||
|
||||
return True
|
||||
|
||||
def can_place(self, x, y, z, model, is_print=False):
|
||||
mx, my, mz = model['dimensions']
|
||||
|
||||
if self.is_cross_border(x, y, z, model):
|
||||
|
||||
print(f"can_place False 1 cross_border {x}, {y}, {z}, {model}, {self.width}, {self.depth}, {self.height}")
|
||||
return False
|
||||
|
||||
# 碰撞检测(正确逻辑与间距处理)
|
||||
for placed in self.placed_models:
|
||||
px, py, pz = placed['position']
|
||||
pdx, pdy, pdz = placed['dimensions']
|
||||
|
||||
# 使用AABB碰撞检测算法[4](@ref)
|
||||
if (x > px - pdx - extend_dist_model_x and
|
||||
x - mx - extend_dist_model_x < px and
|
||||
y > py - pdy - extend_dist_model_y and
|
||||
y - my - extend_dist_model_y < py and
|
||||
z < pz + pdz and
|
||||
z + mz > pz):
|
||||
print("can_place False 2",False,model,x,y,z,px,pdx,extend_dist_model_x,py,pdy,extend_dist_model_y,my,pz,pdz,pz)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def place_model(self, model, pre_model):
|
||||
mx, my, mz = model['dimensions']
|
||||
if mz > self.height:
|
||||
self.unplaced_models.append(model)
|
||||
return False
|
||||
|
||||
if is_same_obj(model['name'], self.remove_multiobj_name):
|
||||
self.unplaced_models.append(model)
|
||||
return False
|
||||
|
||||
z = 0
|
||||
|
||||
if pre_model is None:
|
||||
if self.first_line:
|
||||
model['position'] = (mx + extend_dist_border_x_min, self.depth - extend_dist_border_y_max, 0)
|
||||
print(f"First Model {model['name']}")
|
||||
model['first_line'] = True
|
||||
else:
|
||||
model['position'] = (self.width - extend_dist_border_x_max, self.depth - extend_dist_border_y_max, 0)
|
||||
model['first_line'] = False
|
||||
|
||||
print("model position1", model['name'], model['position'])
|
||||
self.placed_models.append(model)
|
||||
return True
|
||||
|
||||
pre_px, pre_py, pre_pz = pre_model['position']
|
||||
pre_mx, pre_my, pre_mz = pre_model['dimensions']
|
||||
if self.first_line:
|
||||
px = pre_px + mx + extend_dist_model_x
|
||||
model['first_line'] = True
|
||||
else:
|
||||
px = pre_px - pre_mx - extend_dist_model_x
|
||||
model['first_line'] = False
|
||||
print(model['name'], "px", px, pre_px, pre_mx)
|
||||
|
||||
reach_limit_x = False
|
||||
if self.first_line:
|
||||
if px > self.width:
|
||||
reach_limit_x = True
|
||||
else:
|
||||
if px - mx < 0:
|
||||
reach_limit_x = True
|
||||
|
||||
if reach_limit_x:
|
||||
self.first_line = False
|
||||
px = self.width - extend_dist_border_x_max
|
||||
start_y = my + extend_dist_border_y_min
|
||||
final_y = self.depth
|
||||
print("reach_limit_x final_y1", model['name'], my, final_y, my, extend_dist_border_x_max, px)
|
||||
for y in range(start_y, final_y, +1):
|
||||
# print("y",y)
|
||||
if self.can_place(px, y, z, model, True)==False:
|
||||
y -= 1
|
||||
|
||||
if self.is_cross_border(px, y, z, model):
|
||||
print(f"cross border : {model['name']}")
|
||||
if self.check_multiobj_cross(model):
|
||||
self.remove_multiobj_name = model['name']
|
||||
return False
|
||||
|
||||
model['position'] = (px, y, z)
|
||||
print("model position2", model['name'], model['position'])
|
||||
self.placed_models.append(model)
|
||||
return True
|
||||
else:
|
||||
start_y = my + extend_dist_border_y_min
|
||||
final_y = self.depth
|
||||
print("final_y2", model['name'], start_y, final_y, my, extend_dist_border_y_max, px)
|
||||
for y in range(start_y, final_y, +1):
|
||||
if self.can_place(px, y, z, model)==False:
|
||||
y -= 1
|
||||
|
||||
if self.is_cross_border(px, y, z, model):
|
||||
print(f"cross border : {model['name']}")
|
||||
if self.check_multiobj_cross(model):
|
||||
self.remove_multiobj_name = model['name']
|
||||
return False
|
||||
|
||||
model['position'] = (px, y, z)
|
||||
print("model position2", model['name'], model['position'])
|
||||
self.placed_models.append(model)
|
||||
return True
|
||||
|
||||
if 'position' in model:
|
||||
print("model position3", model['name'], model['position'])
|
||||
else:
|
||||
print("model position3 no exist position", model['name'])
|
||||
|
||||
self.unplaced_models.append(model)
|
||||
return False
|
||||
|
||||
def arrange_models(self, models):
|
||||
|
||||
"""对所有模型进行排布(单层)"""
|
||||
print("⚠️ 单层放置模式:所有模型只能放在平台底面(Z=0)")
|
||||
# 按高度和面积排序,优先放大模型
|
||||
models = sorted(models, key=lambda m: (-m['dimensions'][2], -m['dimensions'][0] * m['dimensions'][1]))
|
||||
self.pre_model = None
|
||||
for model in models:
|
||||
print(f"arrange_models {model['name']}")
|
||||
pre_model_temp = self.pre_model
|
||||
if self.place_model(model, self.pre_model):
|
||||
self.pre_model = model
|
||||
|
||||
model["pre_model"] = pre_model_temp
|
||||
|
||||
def print_results(self):
|
||||
"""打印排布结果"""
|
||||
print("Placed Models:")
|
||||
for model in self.placed_models:
|
||||
print(f" - {model['name']} at {model['position']} with dimensions {model['dimensions']}")
|
||||
print("Unplaced Models:")
|
||||
for model in self.unplaced_models:
|
||||
print(f" - {model['name']} with dimensions {model['dimensions']}")
|
||||
|
||||
def get_result(self):
|
||||
return self.placed_models, self.unplaced_models
|
||||
|
||||
# -------------------------- 结束:bbox --------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--obj_path", type=str, required=True, help="batchobj_path_id")
|
||||
args = parser.parse_args()
|
||||
|
||||
obj_path = args.obj_path
|
||||
max, z = get_lowest_position_of_z_out(obj_path)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from enum import Enum
|
||||
class run_mode(Enum):
|
||||
formal = 1
|
||||
test = 2
|
||||
fix = 3
|
||||
|
||||
# !!!谨慎选择run_mode.fix,这将把修复的数据上传到正式的系统 !!!
|
||||
curr_run_mode = run_mode.test
|
||||
|
||||
is_test = True
|
||||
|
||||
# print_factory_type_dir="/root/print_factory_type"
|
||||
print_factory_type_dir="/home/algo/Documents/print_factory_type"
|
||||
|
||||
url_send_layout = 'https://mp.api.suwa3d.com/api/printTypeSettingOrder/printTypeSettingOrderSuccess'
|
||||
|
||||
big_machine_size = [600, 500, 300]
|
||||
small_machine_size = [380, 345, 250]
|
||||
+10
-60
@@ -13,7 +13,10 @@ import math
|
||||
import os
|
||||
import argparse
|
||||
|
||||
is_test = False
|
||||
from config import print_factory_type_dir
|
||||
from config import is_test
|
||||
|
||||
from general import is_use_debug_oss
|
||||
|
||||
CameraModel = collections.namedtuple(
|
||||
"CameraModel", ["model_id", "model_name", "num_params"]
|
||||
@@ -1399,12 +1402,8 @@ def download_transform_save_by_batch(batch_id, workdir, oss_config):
|
||||
layout_data = datas["layout_data"]
|
||||
|
||||
original_obj_pid_dir = workdir
|
||||
cache_type_setting_dir = os.path.join(workdir, "arrange")
|
||||
Path(cache_type_setting_dir).mkdir(exist_ok=True)
|
||||
|
||||
print(f"original_obj_pid_dir={original_obj_pid_dir}, cache_type_setting_dir={cache_type_setting_dir}")
|
||||
|
||||
transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir)
|
||||
transform_save_o3d(layout_data, original_obj_pid_dir)
|
||||
|
||||
|
||||
def download_datas_by_json(pid_file, workdir, oss_config):
|
||||
@@ -1427,12 +1426,8 @@ def download_transform_save_by_json(pid_file, workdir, oss_config):
|
||||
layout_data = download_datas_by_json(pid_file, workdir, oss_config)
|
||||
|
||||
original_obj_pid_dir = workdir
|
||||
cache_type_setting_dir = os.path.join(workdir, "arrange")
|
||||
Path(cache_type_setting_dir).mkdir(exist_ok=True)
|
||||
|
||||
print(f"original_obj_pid_dir={original_obj_pid_dir}, cache_type_setting_dir={cache_type_setting_dir}")
|
||||
|
||||
transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir)
|
||||
transform_save_o3d(layout_data, original_obj_pid_dir)
|
||||
|
||||
def upload_result(base_original_obj_dir, oss_config, batch_id):
|
||||
|
||||
@@ -1442,7 +1437,9 @@ def upload_result(base_original_obj_dir, oss_config, batch_id):
|
||||
target_dir = f"{base_original_obj_dir}"
|
||||
|
||||
oss_batch_dir = "batchPrint"
|
||||
if is_test:
|
||||
print(f"is_use_debug_oss={is_use_debug_oss()}")
|
||||
if is_use_debug_oss():
|
||||
# if is_test:
|
||||
oss_batch_dir = "batchPrint/debug_hsc"
|
||||
|
||||
print(f"target_dir={target_dir}, batch_id={batch_id}")
|
||||
@@ -1456,52 +1453,6 @@ def upload_result(base_original_obj_dir, oss_config, batch_id):
|
||||
pass
|
||||
|
||||
import open3d as o3d
|
||||
from test_load_json import custom_mesh_transform
|
||||
|
||||
def transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir):
|
||||
meshes = []
|
||||
# 小打印机380*345,需要偏移-380,-345
|
||||
need_offset = True
|
||||
for model in layout_data["models"]:
|
||||
transform = model.get('transform', {})
|
||||
|
||||
homo_matrix = transform["homo_matrix"] # 获取存储的列表
|
||||
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
|
||||
|
||||
obj_name = model.get('file_name', '')
|
||||
obj_path = os.path.join(original_obj_pid_dir, obj_name)
|
||||
# 加载网格
|
||||
try:
|
||||
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
|
||||
if not mesh.has_vertices():
|
||||
print(f"警告: 网格无有效顶点 - {obj_path}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"加载模型失败: {obj_path} - {e}")
|
||||
continue
|
||||
|
||||
original_vertices = np.asarray(mesh.vertices)
|
||||
|
||||
transformed_vertices = custom_mesh_transform(original_vertices, reconstructed_matrix)
|
||||
# 如果 need_offset 为 True,应用额外的偏移
|
||||
if need_offset:
|
||||
# 应用偏移 (-380, -345, 0)
|
||||
offset = np.array([-380, -345, 0])
|
||||
transformed_vertices += offset
|
||||
print(f"已对模型 {obj_name} 应用偏移: {offset}")
|
||||
|
||||
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
meshes.append(mesh)
|
||||
|
||||
# obj_path_arrange = os.path.join(original_obj_pid_dir, "arrange")
|
||||
obj_path_arrange = cache_type_setting_dir
|
||||
if not os.path.exists(obj_path_arrange):
|
||||
os.mkdir(obj_path_arrange)
|
||||
obj_path_arrange_obj = os.path.join(obj_path_arrange, obj_name)
|
||||
print("obj_path_arrange_obj", obj_path_arrange_obj)
|
||||
mesh.compute_vertex_normals()
|
||||
o3d.io.write_triangle_mesh(obj_path_arrange_obj, mesh,write_triangle_uvs=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -1517,9 +1468,8 @@ if __name__ == "__main__":
|
||||
args = parser.parse_args()
|
||||
"""
|
||||
# batch_id = args.batch_id
|
||||
batch_id = 2499
|
||||
batch_id = 10118
|
||||
|
||||
print_factory_type_dir="/root/print_factory_type"
|
||||
# workdir = args.workdir
|
||||
workdir = f"{print_factory_type_dir}/{batch_id}"
|
||||
|
||||
|
||||
+29
-89
@@ -1,33 +1,22 @@
|
||||
import yaml
|
||||
import oss2
|
||||
import os
|
||||
from tqdm import tqdm
|
||||
import os
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import os
|
||||
import argparse
|
||||
|
||||
import bpy
|
||||
import sys
|
||||
import open3d as o3d
|
||||
|
||||
def custom_mesh_transform(vertices, transform_matrix):
|
||||
"""
|
||||
手动实现网格变换:对每个顶点应用齐次变换矩阵
|
||||
参数:
|
||||
vertices: 网格顶点数组 (N, 3)
|
||||
transform_matrix: 4x4 齐次变换矩阵
|
||||
返回:
|
||||
变换后的顶点数组 (N, 3)
|
||||
"""
|
||||
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
|
||||
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
|
||||
import requests
|
||||
import json
|
||||
import shutil
|
||||
|
||||
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
|
||||
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
|
||||
|
||||
# 3. 转回非齐次坐标 (3xN) → (N, 3)
|
||||
transformed_vertices = transformed_homogeneous[:3, :].T
|
||||
return transformed_vertices
|
||||
def download_transform_save_by_json(pid_file, workdir, oss_config):
|
||||
layout_data = download_datas_by_json(pid_file, workdir, oss_config)
|
||||
original_obj_pid_dir = workdir
|
||||
transform_save_bpy(layout_data, original_obj_pid_dir)
|
||||
|
||||
class DataTransfer:
|
||||
'''
|
||||
@@ -43,10 +32,6 @@ class DataTransfer:
|
||||
self.oss_path = oss_path.lstrip('/')
|
||||
self.oss_client = oss_client
|
||||
|
||||
order_id: str
|
||||
pid: str
|
||||
model_height: str
|
||||
|
||||
def download_data_rename_json(self, json_model_info):
|
||||
"""
|
||||
从 OSS 下载数据到本地,保持原有目录结构
|
||||
@@ -163,10 +148,6 @@ class DataTransfer:
|
||||
|
||||
print(f"下载文件: {obj_key} -> {local_path}")
|
||||
|
||||
import requests
|
||||
import json
|
||||
import shutil
|
||||
|
||||
def get_api(url):
|
||||
try:
|
||||
response = requests.get(url)
|
||||
@@ -185,6 +166,7 @@ class JSONModelInfo:
|
||||
obj_name: str
|
||||
order_id: str
|
||||
pid: str
|
||||
print_order_id: str
|
||||
model_height: str
|
||||
|
||||
def read_pids_from_json(pid_file):
|
||||
@@ -217,15 +199,19 @@ def read_pids_from_json(pid_file):
|
||||
|
||||
order_id = parts[0]
|
||||
pid = parts[1]
|
||||
print_order_id = parts[2]
|
||||
print_order_id = print_order_id.replace("P", "")
|
||||
model_height = parts[3]
|
||||
|
||||
model_info = JSONModelInfo(
|
||||
obj_name=obj_name,
|
||||
order_id=order_id,
|
||||
pid=pid,
|
||||
print_order_id=print_order_id,
|
||||
model_height=model_height
|
||||
)
|
||||
list_model_info.append(model_info)
|
||||
print(f"model_info={model_info}")
|
||||
|
||||
return list_model_info, data
|
||||
|
||||
@@ -233,7 +219,6 @@ def download_data_by_json(model_info, workdir, oss_client ):
|
||||
try:
|
||||
pid = model_info.pid
|
||||
model_height = model_info.model_height
|
||||
# target_dir = f"{workdir}/{pid}_image"
|
||||
target_dir = f"{workdir}"
|
||||
|
||||
url = f"https://mp.api.suwa3d.com/api/order/getOssSuffixByOrderId?order_id={model_info.order_id}"
|
||||
@@ -252,7 +237,7 @@ def download_data_by_json(model_info, workdir, oss_client ):
|
||||
shutil.rmtree(target_dir)
|
||||
print(f"下载后检查发现目标文件夹为空,已删除: {target_dir}")
|
||||
except Exception as e:
|
||||
print(f"卡通图片下载失败: {pid}, 错误: {str(e)}")
|
||||
print(f"下载失败: {pid}, 错误: {str(e)}")
|
||||
pass
|
||||
|
||||
def get_oss_client(cfg_path):
|
||||
@@ -286,69 +271,24 @@ def download_datas_by_json(pid_file, workdir, oss_config):
|
||||
|
||||
return data
|
||||
|
||||
def download_transform_save_by_json(pid_file, workdir, oss_config):
|
||||
layout_data = download_datas_by_json(pid_file, workdir, oss_config)
|
||||
|
||||
original_obj_pid_dir = workdir
|
||||
cache_type_setting_dir = os.path.join(workdir, "arrange")
|
||||
Path(cache_type_setting_dir).mkdir(exist_ok=True)
|
||||
|
||||
print(f"original_obj_pid_dir={original_obj_pid_dir}, cache_type_setting_dir={cache_type_setting_dir}")
|
||||
|
||||
transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir)
|
||||
|
||||
def transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir):
|
||||
meshes = []
|
||||
# 小打印机380*345,需要偏移-380,-345
|
||||
need_offset = True
|
||||
for model in layout_data["models"]:
|
||||
transform = model.get('transform', {})
|
||||
|
||||
homo_matrix = transform["homo_matrix"] # 获取存储的列表
|
||||
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
|
||||
|
||||
obj_name = model.get('file_name', '')
|
||||
obj_path = os.path.join(original_obj_pid_dir, obj_name)
|
||||
# 加载网格
|
||||
try:
|
||||
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
|
||||
if not mesh.has_vertices():
|
||||
print(f"警告: 网格无有效顶点 - {obj_path}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"加载模型失败: {obj_path} - {e}")
|
||||
continue
|
||||
|
||||
original_vertices = np.asarray(mesh.vertices)
|
||||
|
||||
transformed_vertices = custom_mesh_transform(original_vertices, reconstructed_matrix)
|
||||
# 如果 need_offset 为 True,应用额外的偏移
|
||||
if need_offset:
|
||||
# 应用偏移 (-380, -345, 0)
|
||||
offset = np.array([-380, -345, 0])
|
||||
transformed_vertices += offset
|
||||
print(f"已对模型 {obj_name} 应用偏移: {offset}")
|
||||
|
||||
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
meshes.append(mesh)
|
||||
|
||||
# obj_path_arrange = os.path.join(original_obj_pid_dir, "arrange")
|
||||
obj_path_arrange = cache_type_setting_dir
|
||||
if not os.path.exists(obj_path_arrange):
|
||||
os.mkdir(obj_path_arrange)
|
||||
obj_path_arrange_obj = os.path.join(obj_path_arrange, obj_name)
|
||||
print("obj_path_arrange_obj", obj_path_arrange_obj)
|
||||
mesh.compute_vertex_normals()
|
||||
o3d.io.write_triangle_mesh(obj_path_arrange_obj, mesh,write_triangle_uvs=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
script_args = []
|
||||
|
||||
try:
|
||||
separator_index = sys.argv.index("--")
|
||||
script_args = sys.argv[separator_index + 1:]
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument("--pid_file", type=str, required=True, help="批次号, 也是json文件名")
|
||||
parser.add_argument("--workdir", type=str, required=True, help="本代码文件所在的目录")
|
||||
parser.add_argument("--oss_config", type=str, required=True)
|
||||
args = parser.parse_args()
|
||||
parser.add_argument("--workdir", type=str, required=True, help="json文件所在的目录")
|
||||
parser.add_argument("--oss_config", type=str, required=True, help="run.yaml所在的目录")
|
||||
args = parser.parse_args(script_args)
|
||||
|
||||
download_transform_save_by_json(args.pid_file, args.workdir, args.oss_config)
|
||||
|
||||
# blender --background --python download_print_out.py -- --pid_file {your_batch_id} --workdir {your_batch_dir} --oss_config {your_yaml_dir/run.yaml}
|
||||
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
import re
|
||||
from config import *
|
||||
|
||||
# -------------------------- 开始:运行 ----------------------------------
|
||||
|
||||
def is_run_local_data():
|
||||
if curr_run_mode == run_mode.formal:
|
||||
return False
|
||||
return True
|
||||
|
||||
def need_upload_result():
|
||||
if curr_run_mode == run_mode.test:
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_use_debug_oss():
|
||||
if curr_run_mode == run_mode.test:
|
||||
return True
|
||||
return False
|
||||
|
||||
# -------------------------- 结束:运行 ----------------------------------
|
||||
|
||||
# -------------------------- 开始:模型 ----------------------------------
|
||||
|
||||
def read_mesh(obj_path):
|
||||
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
|
||||
return mesh_obj
|
||||
|
||||
def down_sample(pcd, voxel_size, farthest_sample = False):
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1500 # 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# 第一步:使用体素下采样快速减少点数量
|
||||
# voxel_size = 3
|
||||
if farthest_sample:
|
||||
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_voxel = pcd.voxel_down_sample(voxel_size)
|
||||
down_num = len(pcd_voxel.points)
|
||||
# print(f"original_num={original_num}, down_num={down_num}")
|
||||
|
||||
# 第二步:仅在必要时进行最远点下采样
|
||||
if len(pcd_voxel.points) > target_samples and False:
|
||||
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_downsampled = pcd_voxel
|
||||
|
||||
return pcd_downsampled
|
||||
|
||||
def get_volume_centroid(mesh):
|
||||
"""向量化版本的体积重心计算"""
|
||||
vertices = np.asarray(mesh.vertices)
|
||||
triangles = np.asarray(mesh.triangles)
|
||||
|
||||
# 一次性获取所有三角形的顶点 [n_triangles, 3, 3]
|
||||
tri_vertices = vertices[triangles]
|
||||
|
||||
# 分别提取三个顶点
|
||||
v0 = tri_vertices[:, 0, :] # [n, 3]
|
||||
v1 = tri_vertices[:, 1, :] # [n, 3]
|
||||
v2 = tri_vertices[:, 2, :] # [n, 3]
|
||||
|
||||
# 向量化计算叉积和点积
|
||||
cross_vec = np.cross(v1, v2) # [n, 3]
|
||||
dot_results = np.einsum('ij,ij->i', v0, cross_vec) # 批量点积 [n]
|
||||
|
||||
# 计算每个四面体体积 [n]
|
||||
tetra_volumes = np.abs(dot_results) / 6.0
|
||||
|
||||
# 计算每个四面体重心 [n, 3]
|
||||
tetra_centers = (v0 + v1 + v2) / 4.0
|
||||
|
||||
# 总体积和加权重心
|
||||
total_volume = np.sum(tetra_volumes)
|
||||
|
||||
if total_volume > 0:
|
||||
weighted_center = np.sum(tetra_volumes[:, np.newaxis] * tetra_centers, axis=0) / total_volume
|
||||
return weighted_center
|
||||
else:
|
||||
return np.mean(vertices, axis=0)
|
||||
|
||||
def mesh_tranform_to_pcd(mesh, transform_matrix):
|
||||
vertices = np.asarray(mesh.vertices)
|
||||
|
||||
transformed_vertices = mesh_transform_by_matrix(vertices, transform_matrix)
|
||||
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
return mesh_to_pcd(mesh)
|
||||
|
||||
def mesh_to_pcd(mesh, is_down_sample=True):
|
||||
|
||||
vertices = np.asarray(mesh.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
if is_down_sample:
|
||||
pcd_downsampled = down_sample(pcd, voxel_size, False)
|
||||
return pcd_downsampled
|
||||
else:
|
||||
return pcd
|
||||
|
||||
def mesh_transform_by_matrix(vertices, transform_matrix):
|
||||
"""
|
||||
手动实现网格变换:对每个顶点应用齐次变换矩阵
|
||||
参数:
|
||||
vertices: 网格顶点数组 (N, 3)
|
||||
transform_matrix: 4x4 齐次变换矩阵
|
||||
返回:
|
||||
变换后的顶点数组 (N, 3)
|
||||
"""
|
||||
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
|
||||
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
|
||||
|
||||
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
|
||||
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
|
||||
|
||||
# 3. 转回非齐次坐标 (3xN) → (N, 3)
|
||||
transformed_vertices = transformed_homogeneous[:3, :].T
|
||||
return transformed_vertices
|
||||
|
||||
import os
|
||||
|
||||
def transform_save_bpy(layout_data, original_obj_pid_dir):
|
||||
# 清除场景
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
|
||||
## 尺寸调整,环境设置
|
||||
bpy.ops.object.delete(use_global=False, confirm=False)
|
||||
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
|
||||
bpy.context.scene.unit_settings.scale_length = 0.001
|
||||
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
|
||||
|
||||
meshes = []
|
||||
need_offset = True
|
||||
for model in layout_data["models"]:
|
||||
transform = model.get('transform', {})
|
||||
homo_matrix = transform["homo_matrix"]
|
||||
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
|
||||
|
||||
obj_name = model.get('file_name', '')
|
||||
obj_path = os.path.join(original_obj_pid_dir, obj_name)
|
||||
|
||||
mtl_name_temp = obj_name
|
||||
separator = "_P"
|
||||
index = mtl_name_temp.find(separator)
|
||||
if index != -1:
|
||||
old_mtl_name = mtl_name_temp[:index]
|
||||
else:
|
||||
old_mtl_name = mtl_name_temp # 或者你希望的其他处理逻辑,比如直接使用原字符串或报错
|
||||
old_mtl_name = f"{old_mtl_name}.mtl"
|
||||
old_mtl_path = os.path.join(original_obj_pid_dir, old_mtl_name)
|
||||
|
||||
bpy.ops.wm.obj_import(filepath=obj_path)
|
||||
imported_object = bpy.context.object
|
||||
|
||||
if imported_object is None or imported_object.type != 'MESH':
|
||||
print(f"警告: 未能成功导入网格对象 {obj_name}。跳过。")
|
||||
continue
|
||||
|
||||
mesh_data = imported_object.data
|
||||
mesh_data.calc_loop_triangles()
|
||||
original_vertices = np.empty(len(mesh_data.vertices) * 3, dtype=np.float64)
|
||||
mesh_data.vertices.foreach_get('co', original_vertices)
|
||||
original_vertices = original_vertices.reshape(-1, 3)
|
||||
|
||||
transformed_vertices = mesh_transform_by_matrix(original_vertices, reconstructed_matrix)
|
||||
if need_offset:
|
||||
offset = np.array([-small_machine_size[0], -small_machine_size[1], 0])
|
||||
transformed_vertices += offset
|
||||
print(f"已对模型 {obj_name} 应用偏移: {offset}")
|
||||
|
||||
flattened_verts = transformed_vertices.reshape(-1)
|
||||
mesh_data.vertices.foreach_set('co', flattened_verts)
|
||||
|
||||
mesh_data.update()
|
||||
|
||||
meshes.append(imported_object)
|
||||
|
||||
# 导出前确保选中当前对象
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
imported_object.select_set(True)
|
||||
bpy.context.view_layer.objects.active = imported_object
|
||||
bpy.ops.wm.obj_export(
|
||||
filepath=obj_path,
|
||||
export_selected_objects=True,
|
||||
export_materials=True,
|
||||
path_mode='AUTO'
|
||||
)
|
||||
|
||||
os.remove(old_mtl_path)
|
||||
|
||||
def transform_save_o3d(layout_data, original_obj_pid_dir):
|
||||
meshes = []
|
||||
# 小打印机需要偏移[-small_machine_size[0], -small_machine_size[1], 0]
|
||||
need_offset = True
|
||||
for model in layout_data["models"]:
|
||||
transform = model.get('transform', {})
|
||||
|
||||
homo_matrix = transform["homo_matrix"] # 获取存储的列表
|
||||
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
|
||||
|
||||
obj_name = model.get('file_name', '')
|
||||
obj_path = os.path.join(original_obj_pid_dir, obj_name)
|
||||
# 加载网格
|
||||
try:
|
||||
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
|
||||
if not mesh.has_vertices():
|
||||
print(f"警告: 网格无有效顶点 - {obj_path}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"加载模型失败: {obj_path} - {e}")
|
||||
continue
|
||||
|
||||
original_vertices = np.asarray(mesh.vertices)
|
||||
|
||||
transformed_vertices = mesh_transform_by_matrix(original_vertices, reconstructed_matrix)
|
||||
# 如果 need_offset 为 True,应用额外的偏移
|
||||
if need_offset:
|
||||
offset = np.array([-small_machine_size[0], -small_machine_size[1], 0])
|
||||
transformed_vertices += offset
|
||||
print(f"已对模型 {obj_name} 应用偏移: {offset}")
|
||||
|
||||
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
meshes.append(mesh)
|
||||
|
||||
obj_path_arrange = os.path.join(original_obj_pid_dir, "arrange")
|
||||
if not os.path.exists(obj_path_arrange):
|
||||
os.mkdir(obj_path_arrange)
|
||||
obj_path_arrange_obj = os.path.join(obj_path_arrange, obj_name)
|
||||
print("obj_path_arrange_obj", obj_path_arrange_obj)
|
||||
mesh.compute_vertex_normals()
|
||||
o3d.io.write_triangle_mesh(obj_path_arrange_obj, mesh,write_triangle_uvs=True)
|
||||
|
||||
voxel_size = 3
|
||||
|
||||
def is_multi_obj(obj_name):
|
||||
pattern = r'_x(\d+)'
|
||||
match = re.search(pattern, obj_name)
|
||||
if match:
|
||||
number = match.group(1)
|
||||
|
||||
if int(number) > 1 :
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_same_obj(obj_name1, obj_name2):
|
||||
|
||||
pre_name1 = obj_name1.split("_x")[0]
|
||||
pre_name2 = obj_name2.split("_x")[0]
|
||||
|
||||
print(f"pre_name1={pre_name1}, pre_name2={pre_name2}")
|
||||
|
||||
if (pre_name1==pre_name2):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# -------------------------- 结束:模型 ----------------------------------
|
||||
|
||||
# -------------------------- 开始:碰撞检测和越界 --------------------------
|
||||
|
||||
extend_dist_border_x_min = 6
|
||||
extend_dist_border_x_max = 3
|
||||
extend_dist_border_z_max = 3
|
||||
extend_dist_border_y_min = 6
|
||||
extend_dist_border_y_max = 6
|
||||
extend_dist_model_x = 4
|
||||
extend_dist_model_y = 2
|
||||
extend_dist_model_z = 2
|
||||
extend_dist_box = 1
|
||||
def is_cross_border_c(x, y, z, mx, my, mz, max_x, max_y, max_z):
|
||||
if (x - mx < extend_dist_border_x_min or
|
||||
y - my < extend_dist_border_y_min or
|
||||
z + mz > max_z - extend_dist_border_z_max or
|
||||
y > max_y - extend_dist_border_y_max):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
# -------------------------- 结束:碰撞检测和越界 --------------------------
|
||||
@@ -1,847 +0,0 @@
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
import copy
|
||||
import time
|
||||
from get_lowest_position_of_z_out import get_lowest_position_of_z_out
|
||||
|
||||
# 对外部提供的获取最低z的接口
|
||||
def get_lowest_position_of_center_out2(obj_path):
|
||||
|
||||
total_matrix = np.eye(4)
|
||||
|
||||
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
|
||||
|
||||
voxel_size = 3
|
||||
|
||||
return get_lowest_position_of_center(mesh_obj, obj_path, total_matrix, voxel_size)
|
||||
|
||||
def calculate_rotation_and_center_of_mass(angle_x, angle_y, angle_z, points):
|
||||
"""计算某一组旋转角度后的重心"""
|
||||
# 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||
R_x = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||
])
|
||||
|
||||
R_y = np.array([
|
||||
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||
[0, 1, 0],
|
||||
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||
])
|
||||
|
||||
R_z = np.array([
|
||||
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
# 综合旋转矩阵
|
||||
R = R_z @ R_y @ R_x
|
||||
|
||||
# 执行旋转
|
||||
rotated_points = points @ R.T
|
||||
|
||||
# 计算最小z值
|
||||
min_z = np.min(rotated_points[:, 2])
|
||||
|
||||
# 计算平移向量,将最小Z值平移到0
|
||||
translation_vector = np.array([0, 0, -min_z])
|
||||
rotated_points += translation_vector
|
||||
|
||||
# 计算重心
|
||||
center_of_mass = np.mean(rotated_points, axis=0)
|
||||
|
||||
return center_of_mass[2], angle_x, angle_y, angle_z
|
||||
|
||||
def parallel_rotation4(points, angle_step=4):
|
||||
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
|
||||
min_center = float('inf')
|
||||
|
||||
for angle_x in range(-45, 45, angle_step):
|
||||
for angle_y in range(0, 360, angle_step):
|
||||
center_z, ax, ay, _ = calculate_rotation_and_center_of_mass(angle_x, angle_y, 0, points)
|
||||
if center_z < min_center:
|
||||
min_center = center_z
|
||||
best_angle_x = ax
|
||||
best_angle_y = ay
|
||||
|
||||
return (best_angle_x, best_angle_y, 0, min_center)
|
||||
|
||||
import numpy as np
|
||||
from numba import cuda
|
||||
import math
|
||||
|
||||
# CUDA核函数:计算所有旋转角度下的重心高度
|
||||
@cuda.jit
|
||||
def compute_centers_kernel(points, centers, angle_x_start, angle_x_step, angle_y_start, angle_y_step, num_x, num_y):
|
||||
i = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
|
||||
j = cuda.blockIdx.y * cuda.blockDim.y + cuda.threadIdx.y
|
||||
|
||||
if i >= num_x or j >= num_y:
|
||||
return
|
||||
|
||||
# 获取整数角度值
|
||||
angle_x = angle_x_start + i * angle_x_step
|
||||
angle_y = angle_y_start + j * angle_y_step
|
||||
|
||||
rx = math.radians(float(angle_x)) # 使用 float() 进行转换
|
||||
ry = math.radians(float(angle_y))
|
||||
rz = 0.0
|
||||
|
||||
# 计算旋转矩阵
|
||||
cos_x = math.cos(rx)
|
||||
sin_x = math.sin(rx)
|
||||
cos_y = math.cos(ry)
|
||||
sin_y = math.sin(ry)
|
||||
cos_z = math.cos(rz)
|
||||
sin_z = math.sin(rz)
|
||||
|
||||
# 旋转矩阵: R = R_z * R_y * R_x
|
||||
R00 = cos_z * cos_y
|
||||
R01 = cos_z * sin_y * sin_x - sin_z * cos_x
|
||||
R02 = cos_z * sin_y * cos_x + sin_z * sin_x
|
||||
R10 = sin_z * cos_y
|
||||
R11 = sin_z * sin_y * sin_x + cos_z * cos_x
|
||||
R12 = sin_z * sin_y * cos_x - cos_z * sin_x
|
||||
R20 = -sin_y
|
||||
R21 = cos_y * sin_x
|
||||
R22 = cos_y * cos_x
|
||||
|
||||
n = points.shape[0]
|
||||
min_z = 1e10
|
||||
|
||||
# 第一遍:计算最小Z值
|
||||
for k in range(n):
|
||||
x = points[k, 0]
|
||||
y = points[k, 1]
|
||||
z = points[k, 2]
|
||||
|
||||
x_rot = R00 * x + R01 * y + R02 * z
|
||||
y_rot = R10 * x + R11 * y + R12 * z
|
||||
z_rot = R20 * x + R21 * y + R22 * z
|
||||
|
||||
if z_rot < min_z:
|
||||
min_z = z_rot
|
||||
|
||||
total_z = 0.0
|
||||
# 第二遍:平移并计算Z坐标和
|
||||
for k in range(n):
|
||||
x = points[k, 0]
|
||||
y = points[k, 1]
|
||||
z = points[k, 2]
|
||||
|
||||
x_rot = R00 * x + R01 * y + R02 * z
|
||||
y_rot = R10 * x + R11 * y + R12 * z
|
||||
z_rot = R20 * x + R21 * y + R22 * z
|
||||
|
||||
z_trans = z_rot - min_z
|
||||
total_z += z_trans
|
||||
|
||||
center_z = total_z / n
|
||||
centers[i, j] = center_z
|
||||
|
||||
# CUDA版本的并行旋转计算
|
||||
def parallel_rotation4_cuda(points, angle_step=4):
|
||||
angle_x_start = -45
|
||||
angle_x_end = 45
|
||||
angle_y_start = 0
|
||||
angle_y_end = 360
|
||||
|
||||
num_x = int((angle_x_end - angle_x_start) / angle_step)
|
||||
num_y = int((angle_y_end - angle_y_start) / angle_step)
|
||||
|
||||
# 将点云数据复制到GPU
|
||||
d_points = cuda.to_device(points.astype(np.float32))
|
||||
d_centers = cuda.device_array((num_x, num_y), dtype=np.float32)
|
||||
|
||||
# 配置线程块和网格
|
||||
threadsperblock = (16, 16)
|
||||
blockspergrid_x = (num_x + threadsperblock[0] - 1) // threadsperblock[0]
|
||||
blockspergrid_y = (num_y + threadsperblock[1] - 1) // threadsperblock[1]
|
||||
blockspergrid = (blockspergrid_x, blockspergrid_y)
|
||||
|
||||
# 启动核函数
|
||||
compute_centers_kernel[blockspergrid, threadsperblock](
|
||||
d_points, d_centers, angle_x_start, angle_step, angle_y_start, angle_step, num_x, num_y
|
||||
)
|
||||
|
||||
# 将结果复制回主机
|
||||
centers = d_centers.copy_to_host()
|
||||
|
||||
# 找到最小重心值的索引
|
||||
min_index = np.argmin(centers)
|
||||
i = min_index // num_y
|
||||
j = min_index % num_y
|
||||
|
||||
best_angle_x = angle_x_start + i * angle_step
|
||||
best_angle_y = angle_y_start + j * angle_step
|
||||
min_center = centers[i, j]
|
||||
|
||||
return best_angle_x, best_angle_y, 0, min_center
|
||||
|
||||
def read_mesh(obj_path, simple=True):
|
||||
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
|
||||
return mesh_obj
|
||||
|
||||
def compute_mesh_center2(vertices):
|
||||
"""
|
||||
计算网格质心
|
||||
|
||||
参数:
|
||||
vertices: 顶点坐标数组,形状为(N, 3)的NumPy数组或列表
|
||||
|
||||
返回:
|
||||
centroid: 质心坐标的NumPy数组 [x, y, z]
|
||||
"""
|
||||
if len(vertices) == 0:
|
||||
raise ValueError("顶点数组不能为空")
|
||||
|
||||
n = len(vertices) # 顶点数量
|
||||
# 初始化坐标累加器
|
||||
sum_x, sum_y, sum_z = 0.0, 0.0, 0.0
|
||||
|
||||
start_time1 = time.time()
|
||||
# 遍历所有顶点累加坐标值
|
||||
for vertex in vertices:
|
||||
sum_x += vertex[0]
|
||||
sum_y += vertex[1]
|
||||
sum_z += vertex[2]
|
||||
print("compute_mesh_center1 time", time.time()-start_time1)
|
||||
|
||||
# 计算各坐标轴的平均值
|
||||
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
|
||||
return centroid
|
||||
|
||||
def compute_mesh_center(vertices):
|
||||
"""
|
||||
计算网格质心(优化版)
|
||||
|
||||
参数:
|
||||
vertices: 顶点坐标数组,形状为(N, 3)的NumPy数组或列表
|
||||
|
||||
返回:
|
||||
centroid: 质心坐标的NumPy数组 [x, y, z]
|
||||
"""
|
||||
if len(vertices) == 0:
|
||||
raise ValueError("顶点数组不能为空")
|
||||
|
||||
# 确保vertices是NumPy数组
|
||||
vertices_np = np.asarray(vertices)
|
||||
|
||||
# 使用NumPy的mean函数直接计算均值(向量化操作)
|
||||
centroid = np.mean(vertices_np, axis=0)
|
||||
|
||||
return centroid
|
||||
|
||||
def get_lowest_position_of_center_ext3(mesh_obj, obj_path, voxel_size = 3):
|
||||
|
||||
best_angle_x, best_angle_y, best_angle_z, z_mean_min, pcd_transformed= get_lowest_position_of_center2(mesh_obj, obj_path, voxel_size)
|
||||
|
||||
return best_angle_x, best_angle_y, best_angle_z, z_mean_min
|
||||
|
||||
|
||||
def get_lowest_position_of_center_ext2(mesh_obj, obj_path, total_matrix, voxel_size):
|
||||
|
||||
# total_matrix, z_mean_min = get_lowest_position_of_center(obj_path, total_matrix, voxel_size)
|
||||
temp_matrix, z_mean_min = get_lowest_position_of_center(mesh_obj, obj_path, np.eye(4), voxel_size)
|
||||
# print("temp_matrix=",temp_matrix,voxel_size,mesh_obj)
|
||||
|
||||
total_matrix = temp_matrix @ total_matrix
|
||||
|
||||
return total_matrix, z_mean_min
|
||||
|
||||
def get_lowest_position_of_center_ext(obj_path, total_matrix):
|
||||
|
||||
temp_matrix, z_max = get_lowest_position_of_z_out(obj_path)
|
||||
|
||||
total_matrix = temp_matrix @ total_matrix
|
||||
|
||||
return total_matrix, z_max
|
||||
|
||||
def down_sample(pcd, voxel_size, farthest_sample = False):
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1500 # 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# 第一步:使用体素下采样快速减少点数量
|
||||
# voxel_size = 3
|
||||
if farthest_sample:
|
||||
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_voxel = pcd.voxel_down_sample(voxel_size)
|
||||
down_num = len(pcd_voxel.points)
|
||||
# print(f"original_num={original_num}, down_num={down_num}")
|
||||
|
||||
# 第二步:仅在必要时进行最远点下采样
|
||||
if len(pcd_voxel.points) > target_samples and False:
|
||||
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_downsampled = pcd_voxel
|
||||
|
||||
return pcd_downsampled
|
||||
|
||||
def get_lowest_position_of_center(mesh_obj, obj_path, total_matrix, voxel_size):
|
||||
|
||||
# print(f"obj_path={obj_path}, get_lowest_position_of_center voxel_size={voxel_size}")
|
||||
start_time1 = time.time()
|
||||
|
||||
vertices = np.asarray(mesh_obj.vertices)
|
||||
|
||||
# 确保网格有顶点
|
||||
if len(vertices) == 0:
|
||||
# raise ValueError(f"Mesh has no vertices: {obj_path}")
|
||||
print(f"Warning: Mesh has no vertices: {mesh_obj}")
|
||||
return None
|
||||
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# print("voxel_size",voxel_size,obj_path, len(pcd.points), len(mesh_obj.vertices))
|
||||
|
||||
# 对点云进行下采样(体素网格法)
|
||||
#"""
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
if len(np.asarray(pcd_downsampled.points)) <= 0:
|
||||
bbox = pcd.get_axis_aligned_bounding_box()
|
||||
volume = bbox.volume()
|
||||
|
||||
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
|
||||
|
||||
# 处理体积为零的情况
|
||||
if volume <= 0:
|
||||
# 计算点云的实际范围
|
||||
points = np.asarray(pcd.points)
|
||||
if len(points) > 0:
|
||||
min_bound = np.min(points, axis=0)
|
||||
max_bound = np.max(points, axis=0)
|
||||
extent = max_bound - min_bound
|
||||
|
||||
# 确保最小维度至少为0.01
|
||||
min_dimension = max(0.01, np.min(extent))
|
||||
volume = min_dimension ** 3
|
||||
else:
|
||||
volume = 1.0 # 最后的安全回退
|
||||
|
||||
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
|
||||
|
||||
# 安全计算密度 - 防止除零错误
|
||||
if len(pcd.points) > 0 and volume > 0:
|
||||
original_density = len(pcd.points) / volume
|
||||
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
|
||||
else:
|
||||
# 当点数为0或体积为0时使用默认体素大小
|
||||
voxel_size = 1.0 # 默认值
|
||||
|
||||
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
|
||||
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# print("get_lowest_position_of_center1 time", time.time()-start_time1)
|
||||
start_time2 = time.time()
|
||||
# 确保下采样后有点云
|
||||
if len(np.asarray(pcd_downsampled.points)) == 0:
|
||||
# 使用原始点云作为后备
|
||||
pcd_downsampled = pcd
|
||||
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
|
||||
# 初始化最小重心Y的值
|
||||
min_center_of_mass_y = float('inf')
|
||||
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation4(points, angle_step=3)
|
||||
|
||||
# 使用最佳角度进行旋转并平移obj
|
||||
pcd_transformed = copy.deepcopy(mesh_obj)
|
||||
|
||||
|
||||
# 最佳角度旋转
|
||||
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
|
||||
pcd_transformed.rotate(R_x)
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
|
||||
pcd_transformed.rotate(R_y)
|
||||
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
|
||||
pcd_transformed.rotate(R_z)
|
||||
|
||||
T_x = np.eye(4)
|
||||
T_x[:3, :3] = R_x
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_x @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_y = np.eye(4)
|
||||
T_y[:3, :3] = R_y
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_y @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_z = np.eye(4)
|
||||
T_z[:3, :3] = R_z
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_z @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#试着旋转180,让脸朝上
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans1 = np.eye(4)
|
||||
T_trans1[:3, 3] = translation_vector
|
||||
total_matrix = T_trans1 @ total_matrix
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean1 = np.mean(vertices[:, 2])
|
||||
z_max1 = np.max(vertices[:, 2])
|
||||
|
||||
angle_rad = np.pi
|
||||
#print("旋转前质心:", pcd_transformed.get_center())
|
||||
#print("旋转前点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
centroid = pcd_transformed.get_center()
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
# 构建绕中心点旋转的变换矩阵[3](@ref)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
R_y180 = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
T_rotate = np.eye(4)
|
||||
T_rotate[:3, :3] = R_y180
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_rotate @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#print("旋转后质心:", pcd_transformed.get_center())
|
||||
#print("旋转后点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
|
||||
#
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
max_z = np.max(vertices[:, 2])
|
||||
# print("min_z1", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
# translation_vector = np.array([0,0,-min_z + (min_z-max_z),])
|
||||
# print("translation_vector1",translation_vector)
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans2 = np.eye(4)
|
||||
T_trans2[:3, 3] = translation_vector
|
||||
translation = total_matrix[:3, 3]
|
||||
# print("translation_vector2",translation_vector)
|
||||
# print(1,translation)
|
||||
|
||||
total_matrix = T_trans2 @ total_matrix
|
||||
translation = total_matrix[:3, 3]
|
||||
# print(2,translation)
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean2 = np.mean(vertices[:, 2])
|
||||
z_max2 = np.max(vertices[:, 2])
|
||||
|
||||
# print(f"get_lowest_position_of_center z_max1={z_max1}, z_max2={z_max2}, len={len(pcd_transformed.vertices)}, obj_path={obj_path}")
|
||||
|
||||
if (z_mean2 > z_mean1):
|
||||
# if (z_max2 > z_max1):
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
centroid = pcd_transformed.get_center()
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
# 构建反向旋转矩阵
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
T_rotate_inv = np.eye(4)
|
||||
T_rotate_inv[:3, :3] = R_y
|
||||
# 完整的反向绕中心旋转矩阵
|
||||
T_rot_center_inv = T_origin_to_center @ T_rotate_inv @ T_center_to_origin
|
||||
total_matrix = T_rot_center_inv @ total_matrix
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
# print("min_z2", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans3 = np.eye(4)
|
||||
T_trans3[:3, 3] = translation_vector
|
||||
total_matrix = T_trans3 @ total_matrix
|
||||
|
||||
# z_mean_min = min(z_mean1, z_mean2)
|
||||
z_max_min = min(z_max1, z_max2)
|
||||
|
||||
# print("get_lowest_position_of_center2 time", time.time()-start_time2)
|
||||
return total_matrix, z_max_min
|
||||
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
|
||||
def is_valid_float_string(s):
|
||||
# 匹配科学计数法或普通小数,允许开头和末尾的空格
|
||||
pattern = r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$'
|
||||
return bool(re.match(pattern, s.strip()))
|
||||
|
||||
def safe_convert_to_float(s):
|
||||
"""
|
||||
尝试将字符串安全转换为float,处理一些不完整情况。
|
||||
"""
|
||||
s = s.strip().lower() # 去除空格,统一为小写
|
||||
|
||||
# 处理空字符串或完全非数字的情况
|
||||
if not s or s in ['na', 'nan', 'inf', 'null', 'none']:
|
||||
return None # 或者可以根据需要返回 float('nan')
|
||||
|
||||
# 检查是否为有效的浮点数字符串
|
||||
if is_valid_float_string(s):
|
||||
return float(s)
|
||||
|
||||
# 处理类似 '0.00000000e' 的情况(缺少指数)
|
||||
if s.endswith('e'):
|
||||
# 尝试添加指数 '0' -> 'e0'
|
||||
try:
|
||||
return float(s + '0')
|
||||
except ValueError:
|
||||
pass # 如果添加'e0'后仍然失败,则继续下面的异常处理
|
||||
|
||||
# 更激进的清理:移除非数字、小数点、负号和指数e以外的所有字符
|
||||
# 注意:这可能破坏有特定格式的字符串,慎用
|
||||
cleaned_s = re.sub(r'[^\d\.eE-]', '', s)
|
||||
try:
|
||||
return float(cleaned_s)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 如果所有尝试都失败,返回None或抛出异常
|
||||
return None
|
||||
|
||||
def string_to_matrix(data_string):
|
||||
"""
|
||||
将字符串转换为NumPy浮点数矩阵,并处理可能的转换错误。
|
||||
"""
|
||||
# 分割字符串
|
||||
lines = data_string.strip().split(';')
|
||||
matrix = []
|
||||
|
||||
for line in lines:
|
||||
num_list = line.split()
|
||||
float_row = []
|
||||
for num in num_list:
|
||||
# 使用安全转换函数
|
||||
value = safe_convert_to_float(num)
|
||||
if value is None:
|
||||
# 处理转换失败,例如记录日志、使用NaN代替
|
||||
print(f"警告: 无法转换字符串 '{num}',将其替换为NaN。")
|
||||
value = np.nan # 用NaN标记缺失或无效值
|
||||
float_row.append(value)
|
||||
matrix.append(float_row)
|
||||
|
||||
return np.array(matrix)
|
||||
|
||||
import ast
|
||||
|
||||
def get_lowest_position_of_center_net(printId, total_matrix):
|
||||
print("get_lowest_position_of_center_net", printId)
|
||||
|
||||
url = f"https://mp.api.suwa3d.com/api/printOrder/infoByPrintId?printId={printId}"
|
||||
res = requests.get(url)
|
||||
|
||||
datas = res.json()["data"]["layout"]
|
||||
print("datas=", datas)
|
||||
|
||||
homo_matrix_str = datas.get("homo_matrix")
|
||||
print("homo_matrix_str=", homo_matrix_str)
|
||||
|
||||
# 1. 去除字符串首尾的方括号
|
||||
str_cleaned = homo_matrix_str.strip('[]')
|
||||
# 2. 按行分割字符串
|
||||
rows = str_cleaned.split('\n')
|
||||
|
||||
# 3. 修复:处理每行中的逗号问题
|
||||
matrix_list = []
|
||||
for row in rows:
|
||||
if row.strip() == '':
|
||||
continue
|
||||
|
||||
# 去除行首尾的方括号和空格
|
||||
row_cleaned = row.strip(' []')
|
||||
# 按逗号分割,但过滤掉空字符串
|
||||
elements = [elem.strip() for elem in row_cleaned.split(',') if elem.strip() != '']
|
||||
|
||||
# 进一步清理每个元素:去除可能残留的逗号和方括号
|
||||
cleaned_elements = []
|
||||
for elem in elements:
|
||||
# 去除元素中可能存在的逗号、方括号和空格
|
||||
elem_cleaned = elem.strip(' ,[]')
|
||||
if elem_cleaned != '':
|
||||
cleaned_elements.append(elem_cleaned)
|
||||
|
||||
if cleaned_elements: # 只添加非空行
|
||||
matrix_list.append(cleaned_elements)
|
||||
|
||||
print("matrix_list=", matrix_list)
|
||||
|
||||
# 4. 安全地转换为浮点数数组(带错误处理)
|
||||
try:
|
||||
reconstructed_matrix = np.array(matrix_list, dtype=float)
|
||||
except ValueError as e:
|
||||
print(f"转换矩阵时出错: {e}")
|
||||
print("尝试逐个元素转换...")
|
||||
|
||||
# 逐个元素转换,便于定位问题元素
|
||||
float_matrix = []
|
||||
for i, row in enumerate(matrix_list):
|
||||
float_row = []
|
||||
for j, elem in enumerate(row):
|
||||
try:
|
||||
# 再次清理元素并转换
|
||||
cleaned_elem = elem.strip(' ,')
|
||||
float_val = float(cleaned_elem)
|
||||
float_row.append(float_val)
|
||||
except ValueError as ve:
|
||||
print(f"无法转换的元素: 行{i}, 列{j}, 值'{elem}', 错误: {ve}")
|
||||
# 可以选择设置为0或NaN,或者抛出异常
|
||||
float_row.append(0.0) # 或者 np.nan
|
||||
float_matrix.append(float_row)
|
||||
|
||||
reconstructed_matrix = np.array(float_matrix, dtype=float)
|
||||
|
||||
layout_z = datas.get("layout_z", 0)
|
||||
print("layout_z", layout_z)
|
||||
|
||||
reconstructed_matrix = reconstructed_matrix @ total_matrix
|
||||
print("reconstructed_matrix=", reconstructed_matrix)
|
||||
|
||||
return reconstructed_matrix, layout_z
|
||||
|
||||
def get_lowest_position_of_center2(mesh_obj, obj_path, voxel_size = 3):
|
||||
|
||||
# mesh_obj = read_mesh(obj_path)
|
||||
|
||||
vertices = np.asarray(mesh_obj.vertices)
|
||||
|
||||
# 确保网格有顶点
|
||||
if len(vertices) == 0:
|
||||
print(f"Warning: Mesh has no vertices: {obj_path}")
|
||||
return None
|
||||
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# 对点云进行下采样(体素网格法
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
if len(np.asarray(pcd_downsampled.points)) <= 0:
|
||||
bbox = pcd.get_axis_aligned_bounding_box()
|
||||
volume = bbox.volume()
|
||||
|
||||
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
|
||||
|
||||
# 处理体积为零的情况
|
||||
if volume <= 0:
|
||||
# 计算点云的实际范围
|
||||
points = np.asarray(pcd.points)
|
||||
if len(points) > 0:
|
||||
min_bound = np.min(points, axis=0)
|
||||
max_bound = np.max(points, axis=0)
|
||||
extent = max_bound - min_bound
|
||||
|
||||
# 确保最小维度至少为0.01
|
||||
min_dimension = max(0.01, np.min(extent))
|
||||
volume = min_dimension ** 3
|
||||
else:
|
||||
volume = 1.0 # 最后的安全回退
|
||||
|
||||
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
|
||||
|
||||
# 安全计算密度 - 防止除零错误
|
||||
if len(pcd.points) > 0 and volume > 0:
|
||||
original_density = len(pcd.points) / volume
|
||||
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
|
||||
else:
|
||||
# 当点数为0或体积为0时使用默认体素大小
|
||||
voxel_size = 1.0 # 默认值
|
||||
|
||||
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
|
||||
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
# 确保下采样后有点云
|
||||
if len(np.asarray(pcd_downsampled.points)) == 0:
|
||||
# 使用原始点云作为后备
|
||||
pcd_downsampled = pcd
|
||||
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
|
||||
# 初始化最小重心Y的值
|
||||
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
# 旋转并计算最优角度:绕X、Y、Z轴进行每度的旋转
|
||||
best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation4(points, angle_step=3)
|
||||
# print("best_angle1", best_angle_x, best_angle_y, best_angle_z)
|
||||
|
||||
# 使用最佳角度进行旋转并平移obj
|
||||
pcd_transformed = copy.deepcopy(mesh_obj)
|
||||
|
||||
# 最佳角度旋转
|
||||
"""
|
||||
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
|
||||
pcd_transformed.rotate(R_x)
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
|
||||
pcd_transformed.rotate(R_y)
|
||||
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
|
||||
pcd_transformed.rotate(R_z)
|
||||
#"""
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
center_point = aabb.get_center()
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean1 = np.mean(vertices[:, 2])
|
||||
|
||||
angle_rad = np.pi
|
||||
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
best_angle_y += angle_rad / np.pi * 180
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean2 = np.mean(vertices[:, 2])
|
||||
|
||||
# print("z_mean",z_mean1,z_mean2,len(pcd_transformed.vertices),obj_path)
|
||||
|
||||
if z_mean2 > z_mean1:
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
center_point = aabb.get_center()
|
||||
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
best_angle_y += -angle_rad / np.pi * 180
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
z_mean_min = min(z_mean1, z_mean2)
|
||||
|
||||
angle_z_delta = arrange_box_correctly(pcd_transformed, voxel_size)
|
||||
best_angle_z += angle_z_delta
|
||||
# print("angle_z_delta", angle_z_delta, best_angle_z)
|
||||
|
||||
return best_angle_x, best_angle_y, best_angle_z, z_mean_min, pcd_transformed
|
||||
|
||||
def arrange_box_correctly(obj_transformed, voxel_size):
|
||||
|
||||
vertices = np.asarray(obj_transformed.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# 降采样与特征计算
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
cov = np.cov(points.T)
|
||||
|
||||
center = obj_transformed.get_center()
|
||||
|
||||
# 特征分解与方向约束(关键修改点)
|
||||
eigen_vals, eigen_vecs = np.linalg.eigh(cov)
|
||||
max_axis = eigen_vecs[:, np.argmax(eigen_vals)]
|
||||
|
||||
# 强制主方向向量X分量为正(指向右侧)
|
||||
if max_axis[0] < 0 or (max_axis[0] == 0 and max_axis[1] < 0):
|
||||
max_axis = -max_axis
|
||||
|
||||
target_dir = np.array([1, 0]) # 目标方向为X正轴
|
||||
current_dir = max_axis[:2] / np.linalg.norm(max_axis[:2])
|
||||
dot_product = np.dot(current_dir, target_dir)
|
||||
|
||||
# print("dot_product", dot_product)
|
||||
if dot_product < 0.8: # 阈值控制方向敏感性(建议0.6~0.9)
|
||||
max_axis = -max_axis # 强制翻转方向
|
||||
|
||||
# 计算旋转角度
|
||||
angle_z = np.arctan2(max_axis[1], max_axis[0]) % (2 * np.pi)
|
||||
|
||||
if max_axis[0] <= 0 and max_axis[1] <= 0:
|
||||
angle_z += np.pi
|
||||
|
||||
# print("max_axis2", max_axis, -angle_z, np.rad2deg(-angle_z))
|
||||
|
||||
R = o3d.geometry.get_rotation_matrix_from_axis_angle([0, 0, -angle_z])
|
||||
|
||||
T = np.eye(4)
|
||||
T[:3, :3] = R
|
||||
T[:3, 3] = center - R.dot(center) # 保持中心不变
|
||||
obj_transformed.transform(T)
|
||||
|
||||
return np.rad2deg(-angle_z)
|
||||
@@ -1,352 +0,0 @@
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
import copy
|
||||
import time
|
||||
import argparse
|
||||
|
||||
"""
|
||||
对外部提供的获取最低z的接口
|
||||
get_lowest_position_of_z_out
|
||||
|
||||
参数:
|
||||
obj_path, 模型数据路径
|
||||
|
||||
返回:
|
||||
total_matrix: 旋转矩阵
|
||||
z_max: Z最高点
|
||||
"""
|
||||
|
||||
def get_lowest_position_of_z_out(obj_path):
|
||||
|
||||
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
|
||||
|
||||
total_matrix = np.eye(4)
|
||||
|
||||
voxel_size = 3
|
||||
|
||||
# print(f"obj_path={obj_path}, get_lowest_position_of_center voxel_size={voxel_size}")
|
||||
start_time1 = time.time()
|
||||
|
||||
vertices = np.asarray(mesh_obj.vertices)
|
||||
|
||||
# 确保网格有顶点
|
||||
if len(vertices) == 0:
|
||||
# raise ValueError(f"Mesh has no vertices: {obj_path}")
|
||||
print(f"Warning: Mesh has no vertices: {mesh_obj}")
|
||||
return None
|
||||
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# print("voxel_size",voxel_size,obj_path, len(pcd.points), len(mesh_obj.vertices))
|
||||
|
||||
# 对点云进行下采样(体素网格法)
|
||||
#"""
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
if len(np.asarray(pcd_downsampled.points)) <= 0:
|
||||
bbox = pcd.get_axis_aligned_bounding_box()
|
||||
volume = bbox.volume()
|
||||
|
||||
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
|
||||
|
||||
# 处理体积为零的情况
|
||||
if volume <= 0:
|
||||
# 计算点云的实际范围
|
||||
points = np.asarray(pcd.points)
|
||||
if len(points) > 0:
|
||||
min_bound = np.min(points, axis=0)
|
||||
max_bound = np.max(points, axis=0)
|
||||
extent = max_bound - min_bound
|
||||
|
||||
# 确保最小维度至少为0.01
|
||||
min_dimension = max(0.01, np.min(extent))
|
||||
volume = min_dimension ** 3
|
||||
else:
|
||||
volume = 1.0 # 最后的安全回退
|
||||
|
||||
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
|
||||
|
||||
# 安全计算密度 - 防止除零错误
|
||||
if len(pcd.points) > 0 and volume > 0:
|
||||
original_density = len(pcd.points) / volume
|
||||
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
|
||||
else:
|
||||
# 当点数为0或体积为0时使用默认体素大小
|
||||
voxel_size = 1.0 # 默认值
|
||||
|
||||
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
|
||||
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
pcd_downsampled.paint_uniform_color([0, 0, 1])
|
||||
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# print("get_lowest_position_of_center1 time", time.time()-start_time1)
|
||||
start_time2 = time.time()
|
||||
# 确保下采样后有点云
|
||||
if len(np.asarray(pcd_downsampled.points)) == 0:
|
||||
# 使用原始点云作为后备
|
||||
pcd_downsampled = pcd
|
||||
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
|
||||
# 初始化最小重心Y的值
|
||||
max_z_of_mass_y = float('inf')
|
||||
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
best_angle_x, best_angle_y, best_angle_z, max_z_of_mass_y = parallel_rotation(points, angle_step=3)
|
||||
|
||||
# 使用最佳角度进行旋转并平移obj
|
||||
pcd_transformed = copy.deepcopy(mesh_obj)
|
||||
|
||||
# 最佳角度旋转
|
||||
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
|
||||
pcd_transformed.rotate(R_x)
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
|
||||
pcd_transformed.rotate(R_y)
|
||||
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
|
||||
pcd_transformed.rotate(R_z)
|
||||
|
||||
T_x = np.eye(4)
|
||||
T_x[:3, :3] = R_x
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_x @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_y = np.eye(4)
|
||||
T_y[:3, :3] = R_y
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_y @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
T_z = np.eye(4)
|
||||
T_z[:3, :3] = R_z
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_z @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#试着旋转180,让脸朝上
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans1 = np.eye(4)
|
||||
T_trans1[:3, 3] = translation_vector
|
||||
total_matrix = T_trans1 @ total_matrix
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean1 = np.mean(vertices[:, 2])
|
||||
z_max1 = np.max(vertices[:, 2])
|
||||
|
||||
angle_rad = np.pi
|
||||
#print("旋转前质心:", pcd_transformed.get_center())
|
||||
#print("旋转前点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
centroid = pcd_transformed.get_center()
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
# 构建绕中心点旋转的变换矩阵[3](@ref)
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
R_y180 = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
|
||||
T_rotate = np.eye(4)
|
||||
T_rotate[:3, :3] = R_y180
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
T_rot_center = T_origin_to_center @ T_rotate @ T_center_to_origin
|
||||
total_matrix = T_rot_center @ total_matrix
|
||||
|
||||
#print("旋转后质心:", pcd_transformed.get_center())
|
||||
#print("旋转后点示例:", np.asarray(pcd_transformed.vertices)[:3])
|
||||
|
||||
#
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
max_z = np.max(vertices[:, 2])
|
||||
# print("min_z1", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
# translation_vector = np.array([0,0,-min_z + (min_z-max_z),])
|
||||
# print("translation_vector1",translation_vector)
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans2 = np.eye(4)
|
||||
T_trans2[:3, 3] = translation_vector
|
||||
translation = total_matrix[:3, 3]
|
||||
# print("translation_vector2",translation_vector)
|
||||
# print(1,translation)
|
||||
|
||||
total_matrix = T_trans2 @ total_matrix
|
||||
translation = total_matrix[:3, 3]
|
||||
# print(2,translation)
|
||||
|
||||
# 计算 z 坐标均值
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
z_mean2 = np.mean(vertices[:, 2])
|
||||
z_max2 = np.max(vertices[:, 2])
|
||||
|
||||
# print(f"get_lowest_position_of_center z_max1={z_max1}, z_max2={z_max2}, len={len(pcd_transformed.vertices)}, obj_path={obj_path}")
|
||||
|
||||
if (z_mean2 > z_mean1):
|
||||
# if (z_max2 > z_max1):
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
centroid = pcd_transformed.get_center()
|
||||
|
||||
aabb = pcd_transformed.get_axis_aligned_bounding_box()
|
||||
# center_point = aabb.get_center()
|
||||
center_point = compute_mesh_center(mesh_obj.vertices)
|
||||
|
||||
pcd_transformed.translate(-center_point)
|
||||
pcd_transformed.rotate(R_y, center=(0, 0, 0))
|
||||
pcd_transformed.translate(center_point)
|
||||
|
||||
T_center_to_origin = np.eye(4)
|
||||
T_center_to_origin[:3, 3] = -center_point
|
||||
T_origin_to_center = np.eye(4)
|
||||
T_origin_to_center[:3, 3] = center_point
|
||||
# 构建反向旋转矩阵
|
||||
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
|
||||
T_rotate_inv = np.eye(4)
|
||||
T_rotate_inv[:3, :3] = R_y
|
||||
# 完整的反向绕中心旋转矩阵
|
||||
T_rot_center_inv = T_origin_to_center @ T_rotate_inv @ T_center_to_origin
|
||||
total_matrix = T_rot_center_inv @ total_matrix
|
||||
|
||||
vertices = np.asarray(pcd_transformed.vertices)
|
||||
# 计算平移向量,将最小Y值平移到0
|
||||
min_z = np.min(vertices[:, 2])
|
||||
# print("min_z2", min_z, obj_path)
|
||||
translation_vector = np.array([0,0,-min_z,])
|
||||
pcd_transformed.translate(translation_vector)
|
||||
|
||||
T_trans3 = np.eye(4)
|
||||
T_trans3[:3, 3] = translation_vector
|
||||
total_matrix = T_trans3 @ total_matrix
|
||||
|
||||
# z_mean_min = min(z_mean1, z_mean2)
|
||||
z_max = min(z_max1, z_max2)
|
||||
|
||||
# print("get_lowest_position_of_center2 time", time.time()-start_time2)
|
||||
return total_matrix, z_max
|
||||
|
||||
def calculate_rotation_and_top_of_mass(angle_x, angle_y, angle_z, points):
|
||||
"""计算某一组旋转角度后的重心"""
|
||||
# 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||
R_x = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||
])
|
||||
|
||||
R_y = np.array([
|
||||
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||
[0, 1, 0],
|
||||
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||
])
|
||||
|
||||
R_z = np.array([
|
||||
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
# 综合旋转矩阵
|
||||
R = R_z @ R_y @ R_x
|
||||
|
||||
# 执行旋转
|
||||
rotated_points = points @ R.T
|
||||
|
||||
# 计算最小z值
|
||||
min_z = np.min(rotated_points[:, 2])
|
||||
|
||||
# 计算平移向量,将最小Z值平移到0
|
||||
translation_vector = np.array([0, 0, -min_z])
|
||||
rotated_points += translation_vector
|
||||
|
||||
top_of_mass = np.max(rotated_points, axis=0)
|
||||
|
||||
return top_of_mass[2], angle_x, angle_y, angle_z
|
||||
|
||||
def parallel_rotation(points, angle_step=4):
|
||||
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
|
||||
max_top = float('inf')
|
||||
|
||||
for angle_x in range(-90, 90, angle_step):
|
||||
for angle_y in range(0, 360, angle_step):
|
||||
max_z, ax, ay, _ = calculate_rotation_and_top_of_mass(angle_x, angle_y, 0, points)
|
||||
if max_z < max_top:
|
||||
max_top = max_z
|
||||
best_angle_x = ax
|
||||
best_angle_y = ay
|
||||
|
||||
return (best_angle_x, best_angle_y, 0, max_top)
|
||||
|
||||
def compute_mesh_center(vertices):
|
||||
|
||||
if len(vertices) == 0:
|
||||
raise ValueError("顶点数组不能为空")
|
||||
|
||||
# 确保vertices是NumPy数组
|
||||
vertices_np = np.asarray(vertices)
|
||||
|
||||
# 使用NumPy的mean函数直接计算均值(向量化操作)
|
||||
centroid = np.mean(vertices_np, axis=0)
|
||||
|
||||
return centroid
|
||||
|
||||
def down_sample(pcd, voxel_size, farthest_sample = False):
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1500 # 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# 第一步:使用体素下采样快速减少点数量
|
||||
# voxel_size = 3
|
||||
if farthest_sample:
|
||||
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_voxel = pcd.voxel_down_sample(voxel_size)
|
||||
down_num = len(pcd_voxel.points)
|
||||
# print(f"original_num={original_num}, down_num={down_num}")
|
||||
|
||||
# 第二步:仅在必要时进行最远点下采样
|
||||
if len(pcd_voxel.points) > target_samples and False:
|
||||
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_downsampled = pcd_voxel
|
||||
|
||||
return pcd_downsampled
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--obj_path", type=str, required=True, help="batchobj_path_id")
|
||||
args = parser.parse_args()
|
||||
|
||||
obj_path = args.obj_path
|
||||
max, z = get_lowest_position_of_z_out(obj_path)
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
def make_near_dict(base_dir,compact_dir):
|
||||
""""""
|
||||
|
||||
# 用于存储结果的字典
|
||||
results = {}
|
||||
# 遍历目录中的所有 .ply 文件
|
||||
for ply_file in os.listdir(base_dir):
|
||||
# 检查文件是否为 .ply 格式
|
||||
if ply_file.endswith('.ply'):
|
||||
ply_path = os.path.join(base_dir, ply_file)
|
||||
compact_ply_path = os.path.join(compact_dir, ply_file)
|
||||
if os.path.exists(compact_ply_path):
|
||||
ply_read_path = compact_ply_path
|
||||
else:
|
||||
ply_read_path = ply_path
|
||||
# 读取点云
|
||||
pcd = o3d.io.read_point_cloud(ply_read_path)
|
||||
|
||||
# 获取点云的点数据
|
||||
points = np.asarray(pcd.points)
|
||||
|
||||
# 计算质心
|
||||
centroid = np.mean(points, axis=0)
|
||||
|
||||
# 计算 Y 轴最小值
|
||||
min_y_value = np.min(points[:, 1]) # Y 轴最小值
|
||||
max_y_value = np.max(points[:, 1])
|
||||
|
||||
# 计算 X 轴最小值
|
||||
min_x_value = np.min(points[:, 0]) # X 轴最小值
|
||||
max_x_value = np.max(points[:, 0]) # X 轴最小值
|
||||
#ply_pid = ply_file.split("_")[0]
|
||||
# 将结果存入字典
|
||||
results[ply_file] = {
|
||||
"centroid": centroid,
|
||||
"min_x_value": min_x_value,
|
||||
"min_y_value": min_y_value,
|
||||
"max_x_value": max_x_value,
|
||||
"max_y_value": max_y_value,
|
||||
|
||||
}
|
||||
|
||||
# 打印结果
|
||||
# for ply_file, values in results.items():
|
||||
# print(f"文件: {ply_file}")
|
||||
# print(f" 质心: {values['centroid']}")
|
||||
# print(f" X 轴最小值: {values['min_x_value']}")
|
||||
# print(f" Y 轴最小值: {values['min_y_value']}")
|
||||
|
||||
# 计算每个ply需要触碰检测的
|
||||
check_touch_dict = {}
|
||||
for ply_file in results.keys():
|
||||
print(ply_file)
|
||||
#ply_pid = ply_file.split("_")[0]
|
||||
#print(ply_pid)
|
||||
bounds_min_x = results[ply_file]["min_x_value"]
|
||||
bounds_min_y = results[ply_file]["min_y_value"]
|
||||
#bounds_center = results[ply_file]["centroid"]
|
||||
need_check_list = []
|
||||
need_values_dict = {}
|
||||
for ply_file_near in results.keys():
|
||||
#print(ply_file_near)
|
||||
if ply_file!= ply_file_near:
|
||||
bounds_max_x = results[ply_file_near]["max_x_value"]
|
||||
bounds_max_y = results[ply_file_near]["max_y_value"]
|
||||
# if ply_file == "151140_9cm_x1=30.578+41.705+90.753.ply":
|
||||
# print("-"*50)
|
||||
# print("主::",ply_file)
|
||||
# print("从::",ply_file_near)
|
||||
# print(f"center_x{bounds_center[0]}")
|
||||
# print(f"center_y{bounds_center[1]}")
|
||||
# print(f"bounds_max_x{bounds_max_x}")
|
||||
# print(f"bounds_max_y{bounds_max_y}")
|
||||
# time.sleep(3)
|
||||
# 235605_12cm_x1=33.774+30.837+120.344.ply
|
||||
# if bounds_center[0]<bounds_max_x and bounds_center[1]<bounds_max_y:
|
||||
# print("添加",ply_file_near)
|
||||
# need_check_list.append(ply_file_near)
|
||||
|
||||
#if bounds_near_center[0]>bounds_min_x and bounds_near_center[1]>bounds_min_y:
|
||||
#print("-"*50)
|
||||
# print(f"bounds_min_x{bounds_min_x}")
|
||||
# print(f"bounds_max_x{bounds_max_x}")
|
||||
|
||||
x_dis = bounds_min_x - bounds_max_x
|
||||
y_dis = bounds_min_y - bounds_max_y
|
||||
#print(f"x_dis=={x_dis}")
|
||||
#print(f"y_dis=={y_dis}")
|
||||
#if ply_file=="158040_15cm_x1=80.682+89.345+152.468.ply":
|
||||
#if ply_file == "235547_4.8cm_x1=29.339+39.528+57.63.ply":
|
||||
# print("主::",ply_file)
|
||||
# print("从::",ply_file_near)
|
||||
#if ply_file == "158040_15cm_x1=80.682+89.345+152.468.ply":
|
||||
#if ply_file == "151140_9cm_x1=30.578+41.705+90.753.ply":
|
||||
# print("主::", ply_file)
|
||||
# print("临近::", ply_file_near)
|
||||
# time.sleep(3)
|
||||
if x_dis<-10 and y_dis<-10:
|
||||
need_check_list.append(ply_file_near)
|
||||
need_values_dict["need_check_list"] = need_check_list
|
||||
# need_values_dict["max_x_value"] = bounds_max_x
|
||||
# need_values_dict["max_y_value"] = bounds_max_y
|
||||
|
||||
check_touch_dict[ply_file] = need_values_dict
|
||||
|
||||
|
||||
# print(check_touch_dict)
|
||||
# print("开始要计算触碰检测的数据")
|
||||
# for ply_file, values in check_touch_dict.items():
|
||||
# print("*"*50)
|
||||
# print(ply_file)
|
||||
# print(values)
|
||||
|
||||
# 去掉离比较远的数据
|
||||
for check_touch_key,check_touch_values in check_touch_dict.items():
|
||||
print("-"*50)
|
||||
#print(check_touch_key)
|
||||
#print(check_touch_values["need_check_list"])
|
||||
need_check_list= check_touch_values["need_check_list"]
|
||||
#print(len(need_check_list))
|
||||
if len(need_check_list)>2:
|
||||
ply_A_path = os.path.join(base_dir, check_touch_key)
|
||||
compact_ply_path = os.path.join(compact_dir, check_touch_key)
|
||||
if os.path.exists(compact_ply_path):
|
||||
ply_read_path = compact_ply_path
|
||||
else:
|
||||
ply_read_path = ply_A_path
|
||||
pcd_A = o3d.io.read_point_cloud(ply_read_path)
|
||||
points_A = np.asarray(pcd_A.points)
|
||||
distances = []
|
||||
for i, check_touch in enumerate(need_check_list):
|
||||
point = results[check_touch]['centroid']
|
||||
ply_path = os.path.join(base_dir, check_touch)
|
||||
|
||||
# 读取当前点云
|
||||
pcd = o3d.io.read_point_cloud(ply_path)
|
||||
points = np.asarray(pcd.points)
|
||||
|
||||
# 计算点云之间最小点对距离(brute-force)
|
||||
diff = points_A[:, np.newaxis, :] - points[np.newaxis, :, :] # (N, M, 3)
|
||||
dists = np.linalg.norm(diff, axis=2) # (N, M)
|
||||
min_distance = np.min(dists)
|
||||
|
||||
#print(f"check_touch: {check_touch}, centroid: {point}, min_distance: {min_distance:.4f}")
|
||||
distances.append((i, point, min_distance, check_touch))
|
||||
distances.sort(key=lambda x: x[2])
|
||||
|
||||
# 提取最近的 3 个点
|
||||
nearest_points = distances[:5]
|
||||
last_elements = [item[-1] for item in nearest_points]
|
||||
# print(f"nearest_points---------{nearest_points}")
|
||||
# print(f"check_touch_key--------{check_touch_key}")
|
||||
# print(f"last_elements--------{last_elements}")
|
||||
check_touch_dict[check_touch_key]["need_check_list"] = last_elements
|
||||
|
||||
return check_touch_dict
|
||||
|
||||
# for check_touch_key,check_touch_values in check_touch_dict.items():
|
||||
# print("*"*50)
|
||||
# print(check_touch_key)
|
||||
# print(check_touch_values)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
bounds_fix_out_dir = "/data/datasets_20t/type_setting_test_data/print_bounds_fix_data/"
|
||||
check_touch_dict=make_near_dict(bounds_fix_out_dir)
|
||||
print(f"check_touch_dict--------------{check_touch_dict}")
|
||||
"""
|
||||
{'need_check_list': ['131508_18cm_x1=51.412+87.921+181.446.ply', '239617_12cm_x1=43.987+54.233+120.691.ply']},
|
||||
"""
|
||||
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
is_run_local_data=True
|
||||
pre_batch_id=991112
|
||||
print_ids=[85240, 88136]
|
||||
----------------------------------------
|
||||
----------------------------------------
|
||||
路径 /home/algo/Documents/print_factory_type/data/ 不是一个有效目录。
|
||||
目录 /home/algo/Documents/print_factory_type/full/ 下的内容已清空。
|
||||
从文件读取了 2 个path
|
||||
开始下载print_model_info: BatchModelInfo(order_id=875986, pid=305425, print_order_id=88136, model_size='9cm_x1', path='objs/download/print/305425/base/model/9cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/305425/base/model/9cm/' 存在,共找到 6 个对象。
|
||||
已更新材质库引用: 305425Tex1.jpg -> 875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.mtl -> /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.obj -> /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/blend/305425_org.blend -> /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
下载 in obj_key
|
||||
下载 in obj_key
|
||||
开始下载print_model_info: BatchModelInfo(order_id=857420, pid=268473, print_order_id=85240, model_size='5cm_x1', path='objs/download/print/268473/base_cartoon/badge/1/5cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/268473/base_cartoon/badge/1/5cm/' 存在,共找到 4 个对象。
|
||||
已更新材质库引用: 268473Tex1.jpg -> 857420_268473Tex1.jpg
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.mtl -> /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.obj -> /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
下载 in obj_key
|
||||
下载耗时:5.1790547370910645
|
||||
selected_machine 小机型 selected_mode 紧凑 output_format JSON
|
||||
make_bbox_for_print total_time=5.503540992736816
|
||||
z_volume_center1=3.494785873362541, z_volume_center2=4.630428442012234
|
||||
compute_bbox 857420_268473_P85240_5cm_x1.obj time=2.4211812019348145
|
||||
z_volume_center1=7.4748487042688625, z_volume_center2=8.653334486695568
|
||||
compute_bbox 875986_305425_P88136_9cm_x1.obj time=2.196851968765259
|
||||
all_models [{'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9)}, {'name': '875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply', 'dimensions': (90, 25, 19)}]
|
||||
开始计算排序...
|
||||
开始计算排序...
|
||||
⚠️ 单层放置模式:所有模型只能放在平台底面(Z=0)
|
||||
arrange_models 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
pre_name1=875986_305425_P88136_9cm, pre_name2=
|
||||
First Model 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
model position1 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply (96, 339, 0)
|
||||
arrange_models 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply
|
||||
pre_name1=857420_268473_P85240_5cm, pre_name2=
|
||||
857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply px 150 96 90
|
||||
final_y2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply 56 345 50 6 150
|
||||
can_place False 1 cross_border 150, 340, 0, {'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9), 'first_line': True}, 380, 345, 250
|
||||
model position2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply (150, 339, 0)
|
||||
Placed Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
Unplaced Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
pcd_second : len(pcd_first)=2, len(pcd_second)=0, len(last_pcd_processed)=2, len(pcd_all)=2
|
||||
is_test= True
|
||||
need_upload_result=False
|
||||
3D打印布局已保存至: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
排版错误模型数量::0
|
||||
排版剩余模型数量::0
|
||||
排版完成
|
||||
排版总耗时::0 分 / 16.58465886116028 秒
|
||||
计算重心::0 分 / 16.583354234695435 秒
|
||||
排包围盒::0 分 / 0.0005736351013183594 秒
|
||||
挪紧凑::0 分 / 8.440017700195312e-05 秒
|
||||
移动到位置::0 分 / 0.0006456375122070312 秒
|
||||
选择机型=小机型
|
||||
已加载并变换: 857420_268473_P85240_5cm_x1.obj
|
||||
已加载并变换: 875986_305425_P88136_9cm_x1.obj
|
||||
add_plank /home/algo/Documents/print_factory_type/blank/blank_bias/blank_small.obj
|
||||
成功加载并变换了 3 个模型
|
||||
高级渲染图片已保存到: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
保存截图耗时::0 分 / 6.980211019515991 秒
|
||||
执行上 传-parent_dir=/home/algo/Documents/print_factory_type,base_original_obj_dir=/home/algo/Documents/print_factory_type/data/991112,batch_id=991112
|
||||
is_use_debug_oss=True
|
||||
target_dir=/home/algo/Documents/print_factory_type/data/991112, batch_id=991112
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.json
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.jpg
|
||||
is_test=True
|
||||
need_upload_result=False
|
||||
is_run_local_data=True
|
||||
pre_batch_id=991112
|
||||
print_ids=[85240, 88136]
|
||||
----------------------------------------
|
||||
----------------------------------------
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112/blend 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112/blend
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112
|
||||
目录 /home/algo/Documents/print_factory_type/data/ 下的内容已清空。
|
||||
目录 /home/algo/Documents/print_factory_type/full/ 下的内容已清空。
|
||||
从文件读取了 2 个path
|
||||
开始下载print_model_info: BatchModelInfo(order_id=875986, pid=305425, print_order_id=88136, model_size='9cm_x1', path='objs/download/print/305425/base/model/9cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/305425/base/model/9cm/' 存在,共找到 6 个对象。
|
||||
已更新材质库引用: 305425Tex1.jpg -> 875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.mtl -> /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.obj -> /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/blend/305425_org.blend -> /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
下载 in obj_key
|
||||
下载 in obj_key
|
||||
开始下载print_model_info: BatchModelInfo(order_id=857420, pid=268473, print_order_id=85240, model_size='5cm_x1', path='objs/download/print/268473/base_cartoon/badge/1/5cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/268473/base_cartoon/badge/1/5cm/' 存在,共找到 4 个对象。
|
||||
已更新材质库引用: 268473Tex1.jpg -> 857420_268473Tex1.jpg
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.mtl -> /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.obj -> /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
下载 in obj_key
|
||||
下载耗时:4.482503890991211
|
||||
selected_machine 小机型 selected_mode 紧凑 output_format JSON
|
||||
make_bbox_for_print total_time=5.583776950836182
|
||||
z_volume_center1=3.494785873362541, z_volume_center2=4.630428442012234
|
||||
compute_bbox 857420_268473_P85240_5cm_x1.obj time=2.4529471397399902
|
||||
z_volume_center1=7.4748487042688625, z_volume_center2=8.653334486695568
|
||||
compute_bbox 875986_305425_P88136_9cm_x1.obj time=2.36457896232605
|
||||
all_models [{'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9)}, {'name': '875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply', 'dimensions': (90, 25, 19)}]
|
||||
开始计算排序...
|
||||
开始计算排序...
|
||||
⚠️ 单层放置模式:所有模型只能放在平台底面(Z=0)
|
||||
arrange_models 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
pre_name1=875986_305425_P88136_9cm, pre_name2=
|
||||
First Model 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
model position1 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply (96, 339, 0)
|
||||
arrange_models 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply
|
||||
pre_name1=857420_268473_P85240_5cm, pre_name2=
|
||||
857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply px 150 96 90
|
||||
final_y2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply 56 345 50 6 150
|
||||
can_place False 1 cross_border 150, 340, 0, {'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9), 'first_line': True}, 380, 345, 250
|
||||
model position2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply (150, 339, 0)
|
||||
Placed Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
Unplaced Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
pcd_second : len(pcd_first)=2, len(pcd_second)=0, len(last_pcd_processed)=2, len(pcd_all)=2
|
||||
is_test= True
|
||||
need_upload_result=False
|
||||
3D打印布局已保存至: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
排版错误模型数量::0
|
||||
排版剩余模型数量::0
|
||||
排版完成
|
||||
排版总耗时::0 分 / 17.15925693511963 秒
|
||||
计算重心::0 分 / 17.15758728981018 秒
|
||||
排包围盒::0 分 / 0.0007126331329345703 秒
|
||||
挪紧凑::0 分 / 0.00011324882507324219 秒
|
||||
移动到位置::0 分 / 0.0008420944213867188 秒
|
||||
选择机型=小机型
|
||||
已加载并变换: 857420_268473_P85240_5cm_x1.obj
|
||||
已加载并变换: 875986_305425_P88136_9cm_x1.obj
|
||||
add_plank /home/algo/Documents/print_factory_type/blank/blank_bias/blank_small.obj
|
||||
成功加载并变换了 3 个模型
|
||||
高级渲染图片已保存到: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
保存截图耗时::0 分 / 7.824073791503906 秒
|
||||
执行上 传-parent_dir=/home/algo/Documents/print_factory_type,base_original_obj_dir=/home/algo/Documents/print_factory_type/data/991112,batch_id=991112
|
||||
is_use_debug_oss=True
|
||||
target_dir=/home/algo/Documents/print_factory_type/data/991112, batch_id=991112
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.json
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.jpg
|
||||
is_test=True
|
||||
need_upload_result=False
|
||||
is_run_local_data=True
|
||||
pre_batch_id=991112
|
||||
print_ids=[85240, 88136]
|
||||
----------------------------------------
|
||||
----------------------------------------
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112/blend 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112/blend
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112
|
||||
目录 /home/algo/Documents/print_factory_type/data/ 下的内容已清空。
|
||||
目录 /home/algo/Documents/print_factory_type/full/ 下的内容已清空。
|
||||
从文件读取了 2 个path
|
||||
开始下载print_model_info: BatchModelInfo(order_id=875986, pid=305425, print_order_id=88136, model_size='9cm_x1', path='objs/download/print/305425/base/model/9cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/305425/base/model/9cm/' 存在,共找到 6 个对象。
|
||||
已更新材质库引用: 305425Tex1.jpg -> 875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.mtl -> /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425.obj -> /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
下载文件: objs/download/print/305425/base/model/9cm/305425Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
下载文件: objs/download/print/305425/base/model/9cm/blend/305425_org.blend -> /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
下载 in obj_key
|
||||
下载 in obj_key
|
||||
开始下载print_model_info: BatchModelInfo(order_id=857420, pid=268473, print_order_id=85240, model_size='5cm_x1', path='objs/download/print/268473/base_cartoon/badge/1/5cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/268473/base_cartoon/badge/1/5cm/' 存在,共找到 4 个对象。
|
||||
已更新材质库引用: 268473Tex1.jpg -> 857420_268473Tex1.jpg
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.mtl -> /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.obj -> /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
下载 in obj_key
|
||||
下载耗时:4.554089784622192
|
||||
selected_machine 小机型 selected_mode 紧凑 output_format JSON
|
||||
make_bbox_for_print total_time=5.638967514038086
|
||||
z_volume_center1=3.494785873362541, z_volume_center2=4.630428442012234
|
||||
compute_bbox 857420_268473_P85240_5cm_x1.obj time=2.3993477821350098
|
||||
z_volume_center1=7.4748487042688625, z_volume_center2=8.653334486695568
|
||||
compute_bbox 875986_305425_P88136_9cm_x1.obj time=2.222135305404663
|
||||
all_models [{'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9)}, {'name': '875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply', 'dimensions': (90, 25, 19)}]
|
||||
开始计算排序...
|
||||
开始计算排序...
|
||||
⚠️ 单层放置模式:所有模型只能放在平台底面(Z=0)
|
||||
arrange_models 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
pre_name1=875986_305425_P88136_9cm, pre_name2=
|
||||
First Model 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply
|
||||
model position1 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply (96, 339, 0)
|
||||
arrange_models 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply
|
||||
pre_name1=857420_268473_P85240_5cm, pre_name2=
|
||||
857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply px 150 96 90
|
||||
final_y2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply 56 345 50 6 150
|
||||
can_place False 1 cross_border 150, 340, 0, {'name': '857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply', 'dimensions': (50, 50, 9), 'first_line': True}, 380, 345, 250
|
||||
model position2 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply (150, 339, 0)
|
||||
Placed Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
Unplaced Models:
|
||||
- 875986_305425_P88136_9cm_x1=19.759+25.308+90.184.ply at (96, 339, 0) with dimensions (90, 25, 19)
|
||||
- 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply at (150, 339, 0) with dimensions (50, 50, 9)
|
||||
pcd_second : len(pcd_first)=2, len(pcd_second)=0, len(last_pcd_processed)=2, len(pcd_all)=2
|
||||
is_test= True
|
||||
need_upload_result=False
|
||||
3D打印布局已保存至: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
排版错误模型数量::0
|
||||
排版剩余模型数量::0
|
||||
排版完成
|
||||
排版总耗时::0 分 / 16.85209321975708 秒
|
||||
计算重心::0 分 / 16.850478410720825 秒
|
||||
排包围盒::0 分 / 0.0006732940673828125 秒
|
||||
挪紧凑::0 分 / 0.00010204315185546875 秒
|
||||
移动到位置::0 分 / 0.0008370876312255859 秒
|
||||
选择机型=小机型
|
||||
已加载并变换: 857420_268473_P85240_5cm_x1.obj
|
||||
已加载并变换: 875986_305425_P88136_9cm_x1.obj
|
||||
add_plank /home/algo/Documents/print_factory_type/blank/blank_bias/blank_small.obj
|
||||
成功加载并变换了 3 个模型
|
||||
高级渲染图片已保存到: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
保存截图耗时::0 分 / 7.028083324432373 秒
|
||||
执行上 传-parent_dir=/home/algo/Documents/print_factory_type,base_original_obj_dir=/home/algo/Documents/print_factory_type/data/991112,batch_id=991112
|
||||
is_use_debug_oss=True
|
||||
target_dir=/home/algo/Documents/print_factory_type/data/991112, batch_id=991112
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.json
|
||||
文件已上传到: batchPrint/debug_hsc/991112/991112.jpg
|
||||
is_test=True
|
||||
need_upload_result=False
|
||||
is_run_local_data=True
|
||||
pre_batch_id=991112
|
||||
print_ids=[85240, 88136]
|
||||
----------------------------------------
|
||||
----------------------------------------
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425Tex1.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425_P88136_9cm_x1.obj
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/blend/305425_org.blend
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112/blend 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112/blend
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.json
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/991112.jpg
|
||||
已删除: /home/algo/Documents/print_factory_type/data/991112/875986_305425.mtl
|
||||
目录 /home/algo/Documents/print_factory_type/data/991112 下的内容已清空。
|
||||
已删除子目录: /home/algo/Documents/print_factory_type/data/991112
|
||||
目录 /home/algo/Documents/print_factory_type/data/ 下的内容已清空。
|
||||
目录 /home/algo/Documents/print_factory_type/full/ 下的内容已清空。
|
||||
从文件读取了 2 个path
|
||||
开始下载print_model_info: BatchModelInfo(order_id=857420, pid=268473, print_order_id=85240, model_size='5cm_x1', path='objs/download/print/268473/base_cartoon/badge/1/5cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/268473/base_cartoon/badge/1/5cm/' 存在,共找到 4 个对象。
|
||||
已更新材质库引用: 268473Tex1.jpg -> 857420_268473Tex1.jpg
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.mtl -> /home/algo/Documents/print_factory_type/data/991112/857420_268473.mtl
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473.obj -> /home/algo/Documents/print_factory_type/data/991112/857420_268473_P85240_5cm_x1.obj
|
||||
下载文件: objs/download/print/268473/base_cartoon/badge/1/5cm/268473Tex1.jpg -> /home/algo/Documents/print_factory_type/data/991112/857420_268473Tex1.jpg
|
||||
下载 in obj_key
|
||||
开始下载print_model_info: BatchModelInfo(order_id=875986, pid=305425, print_order_id=88136, model_size='9cm_x1', path='objs/download/print/305425/base/model/9cm', count=1)
|
||||
target_dir= /home/algo/Documents/print_factory_type/data/991112
|
||||
前缀 'objs/download/print/305425/base/model/9cm/' 存在,共找到 6 个对象。
|
||||
File diff suppressed because it is too large.
Load diff
@@ -7,12 +7,21 @@ import argparse
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, script_dir)
|
||||
|
||||
from print_show_weight_max_obj import make_bbox_for_print,copy_obj_2x
|
||||
from print_mplot3d_point_cloud_layout import *
|
||||
from print_merged_many_obj import move_compact_obj_to_file
|
||||
from print_show_weight_max_obj import copy_obj_2x
|
||||
from point_cloud_layout import *
|
||||
from test_load_json import load_show_save
|
||||
from download_print import upload_result
|
||||
|
||||
from config import print_factory_type_dir
|
||||
from config import is_test
|
||||
from config import url_send_layout
|
||||
from config import big_machine_size
|
||||
from config import small_machine_size
|
||||
|
||||
from general import need_upload_result
|
||||
|
||||
from point_cloud_layout import make_bbox_for_print
|
||||
|
||||
def get_base_directory():
|
||||
"""获取脚本或可执行文件的基础目录"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
@@ -26,116 +35,63 @@ def get_base_directory():
|
||||
from datetime import datetime
|
||||
import gc
|
||||
|
||||
def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=None,batch_id=0,show_chart=True,selected_mode="标准",output_format="JSON",selected_machine="大机型"):
|
||||
def print_type_setting_obj(base_original_obj_dir=None,batch_id=0,selected_mode="标准",output_format="JSON",selected_machine="大机型"):
|
||||
|
||||
print_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
weight_fix_out_obj_dir = f"{cache_type_setting_dir}/temp/print_weight_fix_data_obj"
|
||||
weight_fix_out_ply_dir = f"{cache_type_setting_dir}/temp/print_weight_fix_data_ply"
|
||||
bounds_fix_out_dir = f"{cache_type_setting_dir}/temp/print_bounds_fix_data"
|
||||
bounds_compact_out_dir = f"{cache_type_setting_dir}/temp/print_bounds_compact_data"
|
||||
compact_obj_out_dir = f"{cache_type_setting_dir}/temp/print_compact_obj" # 最后的结果
|
||||
placed_remove_dir = f"{base_original_obj_dir}/place_remove_dir" # 已经放置的放到这个目录
|
||||
|
||||
# 获取基础目录
|
||||
base_path = get_base_directory()
|
||||
# 获取父目录
|
||||
parent_dir = os.path.dirname(base_path)
|
||||
bad_dir = os.path.join(parent_dir, "bad")
|
||||
full_dir = os.path.join(parent_dir, "full")
|
||||
# print(bad_dir)
|
||||
# print(full_dir)
|
||||
|
||||
if output_format == "模型":
|
||||
if os.path.exists(weight_fix_out_obj_dir):
|
||||
shutil.rmtree(weight_fix_out_obj_dir)
|
||||
if os.path.exists(weight_fix_out_ply_dir):
|
||||
shutil.rmtree(weight_fix_out_ply_dir)
|
||||
if os.path.exists(bounds_fix_out_dir):
|
||||
shutil.rmtree(bounds_fix_out_dir)
|
||||
if os.path.exists(bounds_compact_out_dir):
|
||||
shutil.rmtree(bounds_compact_out_dir)
|
||||
if os.path.exists(compact_obj_out_dir):
|
||||
shutil.rmtree(compact_obj_out_dir)
|
||||
# if os.path.exists(output_folder): # 不需要
|
||||
# shutil.rmtree(output_folder)
|
||||
|
||||
time.sleep(1)
|
||||
if not os.path.exists(weight_fix_out_obj_dir):
|
||||
os.makedirs(weight_fix_out_obj_dir)
|
||||
if not os.path.exists(weight_fix_out_ply_dir):
|
||||
os.makedirs(weight_fix_out_ply_dir)
|
||||
if not os.path.exists(bounds_fix_out_dir):
|
||||
os.mkdir(bounds_fix_out_dir)
|
||||
if not os.path.exists(bounds_compact_out_dir):
|
||||
os.makedirs(bounds_compact_out_dir)
|
||||
if not os.path.exists(compact_obj_out_dir):
|
||||
os.makedirs(compact_obj_out_dir)
|
||||
# if not os.path.exists(output_folder): # 不需要
|
||||
# os.makedirs(output_folder)
|
||||
|
||||
print("selected_machine",selected_machine,"selected_mode",selected_mode,"output_format",output_format)
|
||||
compact_min_dis = True
|
||||
compact_min_dis2 = False
|
||||
if selected_mode=="标准" :
|
||||
compact_min_dis = False
|
||||
# if output_format=="JSON":
|
||||
compact_min_dis2 = True
|
||||
else :
|
||||
compact_min_dis = True
|
||||
|
||||
move_back = True
|
||||
|
||||
machine_size = [600, 500, 300]
|
||||
machine_size = big_machine_size
|
||||
if selected_machine=="小机型":
|
||||
machine_size[0] = 380
|
||||
machine_size[1] = 345
|
||||
machine_size[2] = 250
|
||||
machine_size = small_machine_size
|
||||
|
||||
start_time = time.time()
|
||||
copy_obj_2x(base_original_obj_dir)
|
||||
dict_bad = {}
|
||||
dict_best_angel = {}
|
||||
dict_fix = {}
|
||||
dict_origin = {}
|
||||
dict_origin_real = {}
|
||||
dict_total_matrix= {}
|
||||
dict_mesh_obj = make_bbox_for_print(base_original_obj_dir, weight_fix_out_obj_dir, weight_fix_out_ply_dir,show_chart,dict_bad, dict_best_angel,dict_fix,dict_origin,dict_origin_real,compact_min_dis or compact_min_dis2,dict_total_matrix)
|
||||
mesh_count = len(dict_mesh_obj)
|
||||
|
||||
dict_total_matrix, all_models, dict_pcd_fix = make_bbox_for_print(base_original_obj_dir,dict_bad,dict_origin,compact_min_dis)
|
||||
mesh_count = len(dict_origin)
|
||||
if mesh_count<=0:
|
||||
print("选择的文件夹没有模型")
|
||||
return -1
|
||||
|
||||
end_time1 = time.time()
|
||||
dict_bounds_fix = {}
|
||||
placed_models= ply_print_layout_platform(weight_fix_out_obj_dir,weight_fix_out_ply_dir,bounds_fix_out_dir,show_chart,dict_mesh_obj,dict_fix,dict_bounds_fix,machine_size,dict_total_matrix)
|
||||
|
||||
###以上迁移到部分前置计算
|
||||
|
||||
dict_pcd_fix2 = {}
|
||||
list_placed_model = ply_print_layout_platform(dict_pcd_fix,dict_pcd_fix2,machine_size,dict_total_matrix,all_models)
|
||||
|
||||
end_time2 = time.time()
|
||||
|
||||
dict_unplaced = {}
|
||||
dict_compact = {}
|
||||
|
||||
dict_mesh_obj = dict_origin
|
||||
|
||||
if compact_min_dis:
|
||||
# if output_format=="JSON" :
|
||||
if True :
|
||||
can_compact_json = True
|
||||
if can_compact_json :
|
||||
compact_mode_for_min_dis1_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
|
||||
else :
|
||||
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
|
||||
else :
|
||||
compact_mode_for_min_dis(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size)
|
||||
compact_mode_for_min_dis_json(list_placed_model,dict_unplaced,dict_pcd_fix,machine_size,dict_total_matrix)
|
||||
else:
|
||||
compact_min_dis2 = False
|
||||
if compact_min_dis2:
|
||||
compact_mode_for_min_dis2_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
|
||||
else :
|
||||
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
|
||||
pass_for_min_dis(list_placed_model, dict_unplaced,dict_pcd_fix)
|
||||
|
||||
end_time3 = time.time()
|
||||
|
||||
#if os.path.exists(placed_remove_dir):
|
||||
# shutil.rmtree(placed_remove_dir)
|
||||
if not os.path.exists(placed_remove_dir):
|
||||
os.makedirs(placed_remove_dir)
|
||||
|
||||
if not os.path.exists(bad_dir):
|
||||
os.makedirs(bad_dir)
|
||||
|
||||
@@ -143,23 +99,14 @@ def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=Non
|
||||
os.makedirs(full_dir)
|
||||
|
||||
is_small_machine = True if selected_machine=="小机型" else False
|
||||
use_json = True if output_format=="JSON" else False
|
||||
save_mesh = True if output_format=="模型" else False
|
||||
# if use_json:
|
||||
|
||||
version = "print_type_setting25.local"
|
||||
|
||||
layout_data, send_layout_data = move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,
|
||||
base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,
|
||||
placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,
|
||||
dict_compact,dict_origin,dict_total_matrix,save_mesh,cache_type_setting_dir,
|
||||
batch_id, print_start_time,selected_machine,selected_mode,version)
|
||||
|
||||
# else:
|
||||
# move_obj_to_compact_bounds(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,placed_remove_dir,dict_bad,bad_dir,full_dir,dict_bounds_fix,dict_compact,dict_origin)
|
||||
send_layout_data = move_obj_to_compact_bounds_json(base_original_obj_dir,dict_mesh_obj,dict_unplaced,dict_bad,bad_dir,full_dir,dict_pcd_fix,
|
||||
dict_total_matrix,save_mesh,batch_id,print_start_time,selected_machine,selected_mode,version)
|
||||
|
||||
end_time4 = time.time()
|
||||
#move_compact_obj_to_file(compact_obj_out_dir, output_folder) # 不需要
|
||||
print("排版完成")
|
||||
end_time = time.time()
|
||||
elapsed_seconds = end_time - start_time
|
||||
@@ -179,26 +126,12 @@ def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=Non
|
||||
print(f"挪紧凑::{elapsed_minutes3} 分 / {elapsed_seconds3} 秒")
|
||||
print(f"移动到位置::{elapsed_minutes4} 分 / {elapsed_seconds4} 秒")
|
||||
|
||||
dict_mesh_obj.clear()
|
||||
del dict_mesh_obj
|
||||
dict_bad.clear()
|
||||
del dict_bad
|
||||
dict_fix.clear()
|
||||
del dict_fix
|
||||
dict_bounds_fix.clear()
|
||||
del dict_bounds_fix
|
||||
dict_unplaced.clear()
|
||||
del dict_unplaced
|
||||
dict_compact.clear()
|
||||
del dict_compact
|
||||
gc.collect()
|
||||
|
||||
# print(base_original_obj_dir,blank_dir,batch_id)
|
||||
|
||||
is_screenshot = True
|
||||
if is_screenshot:
|
||||
start_time = time.time()
|
||||
blank_path = get_blank_path(parent_dir, is_small_machine)
|
||||
blank_path = get_blank_path(is_small_machine)
|
||||
load_show_save(base_original_obj_dir, dict_origin, blank_path, batch_id)
|
||||
elapsed_seconds5 = time.time() - start_time
|
||||
elapsed_minutes5 = int(elapsed_seconds5 // 60)
|
||||
@@ -210,23 +143,24 @@ def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=Non
|
||||
|
||||
is_upload_result = True
|
||||
if is_upload_result:
|
||||
print(f"执行上传-parent_dir={parent_dir},base_original_obj_dir={base_original_obj_dir},batch_id={batch_id}")
|
||||
print(f"执行上 传-parent_dir={print_factory_type_dir},base_original_obj_dir={base_original_obj_dir},batch_id={batch_id}")
|
||||
# oss_config = f"{base_original_obj_dir}/../print_factory_type_setting_big/download_print/run.yaml"
|
||||
oss_config = f"{parent_dir}/print_factory_type_setting_big/download_print/run.yaml"
|
||||
oss_config = f"{print_factory_type_dir}/print_factory_type_setting_big/download_print/run.yaml"
|
||||
|
||||
upload_result(base_original_obj_dir, oss_config, batch_id)
|
||||
|
||||
print(f"is_test={is_test}")
|
||||
print(f"need_upload_result={need_upload_result()}")
|
||||
# if need_upload_result():
|
||||
if is_test :
|
||||
is_send_layout_data = False
|
||||
else :
|
||||
is_send_layout_data = True
|
||||
# is_send_layout_data = False
|
||||
|
||||
if is_send_layout_data:
|
||||
print(f"send_layout_data={send_layout_data}")
|
||||
url = 'https://mp.api.suwa3d.com/api/printTypeSettingOrder/printTypeSettingOrderSuccess'
|
||||
# url = 'http://127.0.0.1:8199/api/typeSettingPrintOrder/printTypeSettingOrderSuccess'
|
||||
# url = 'https://mp.api.suwa3d.com/api/printTypeSettingOrder/printTypeSettingOrderSuccess'
|
||||
url = url_send_layout
|
||||
try:
|
||||
response = requests.post(url, json.dumps(send_layout_data), timeout=30)
|
||||
#写入文件中 log/request.txt
|
||||
@@ -254,38 +188,46 @@ def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=Non
|
||||
|
||||
return 0
|
||||
|
||||
def get_blank_path(parent_dir=None, is_small_machine=False):
|
||||
def get_blank_path(is_small_machine=False):
|
||||
if is_small_machine:
|
||||
return os.path.join(parent_dir, "blank/blank_bias/blank_small.obj")
|
||||
return os.path.join(print_factory_type_dir, "blank/blank_bias/blank_small.obj")
|
||||
else:
|
||||
return os.path.join(parent_dir, "blank/blank_bias/blank2.obj")
|
||||
return os.path.join(print_factory_type_dir, "blank/blank_bias/blank2.obj")
|
||||
|
||||
def preview(base_original_obj_dir=None, batch_id=0, is_small_machine=False):
|
||||
|
||||
base_path = get_base_directory()
|
||||
parent_dir = os.path.dirname(base_path)
|
||||
# blank_dir = os.path.join(parent_dir, "blank", "blank_bias")
|
||||
blank_path = get_blank_path(parent_dir, is_small_machine)
|
||||
blank_path = get_blank_path(is_small_machine)
|
||||
|
||||
load_show_save(base_original_obj_dir, {}, blank_path, batch_id, True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
def get_pcd(obj, is_down_sample=True):
|
||||
vertices = np.asarray(obj.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
if is_down_sample:
|
||||
voxel_size = 3
|
||||
pcd_downsampled = down_sample(pcd, voxel_size, False)
|
||||
return pcd_downsampled
|
||||
else:
|
||||
return pcd
|
||||
|
||||
if __name__ == '__main__':
|
||||
# parser = argparse.ArgumentParser()
|
||||
# parser.add_argument("--batch_id", type=str, required=True, help="batch_id")
|
||||
# args = parser.parse_args()
|
||||
|
||||
# batch_id = args.batch_id
|
||||
batch_id = "9"
|
||||
batch_id = "1"
|
||||
src_dir = batch_id
|
||||
selected_mode="紧凑" # 标准 紧凑
|
||||
output_format="JSON" # 模型 JSON
|
||||
selected_machine = "大机型" # 小机型 大机型
|
||||
|
||||
print_factory_type_dir="/root/print_factory_type"
|
||||
# cache_type_setting_dir=f"/data/datasets_20t/type_setting_test_data/{src_dir}"
|
||||
cache_type_setting_dir=f"{print_factory_type_dir}/{src_dir}/arrange"
|
||||
base_original_obj_dir = f"{print_factory_type_dir}/{src_dir}"
|
||||
|
||||
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,
|
||||
batch_id=batch_id,show_chart=False,selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)
|
||||
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,
|
||||
batch_id=batch_id,selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)
|
||||
@@ -1,193 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import sys
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, script_dir)
|
||||
|
||||
from print_show_weight_max_obj import make_bbox_for_print,copy_obj_2x
|
||||
from print_mplot3d_point_cloud_layout import *
|
||||
from print_merged_many_obj import move_compact_obj_to_file
|
||||
from test_load_json import load_and_show
|
||||
|
||||
|
||||
def get_base_directory():
|
||||
"""获取脚本或可执行文件的基础目录"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 打包后的可执行文件环境
|
||||
base_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# 正常脚本运行环境
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
return base_path
|
||||
|
||||
from datetime import datetime
|
||||
def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=None,show_chart=True,selected_mode="标准",output_format="JSON",selected_machine="大机型"):
|
||||
|
||||
print_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
weight_fix_out_obj_dir = f"{cache_type_setting_dir}/print_weight_fix_data_obj"
|
||||
weight_fix_out_ply_dir = f"{cache_type_setting_dir}/print_weight_fix_data_ply"
|
||||
bounds_fix_out_dir = f"{cache_type_setting_dir}/print_bounds_fix_data"
|
||||
bounds_compact_out_dir = f"{cache_type_setting_dir}/print_bounds_compact_data"
|
||||
compact_obj_out_dir = f"{cache_type_setting_dir}/print_compact_obj" # 最后的结果
|
||||
placed_remove_dir = f"{base_original_obj_dir}/place_remove_dir" # 已经放置的放到这个目录
|
||||
|
||||
# 获取基础目录
|
||||
base_path = get_base_directory()
|
||||
# 获取父目录
|
||||
parent_dir = os.path.dirname(base_path)
|
||||
bad_dir = os.path.join(parent_dir, "bad")
|
||||
full_dir = os.path.join(parent_dir, "full")
|
||||
blank_dir = os.path.join(parent_dir, "blank")
|
||||
# print(bad_dir)
|
||||
# print(full_dir)
|
||||
|
||||
# 测试代码
|
||||
"""
|
||||
selected_machine = "小机型" # 小机型 大机型
|
||||
selected_mode="紧凑" # 标准 紧凑
|
||||
output_format="模型" # 模型 JSON
|
||||
"""
|
||||
|
||||
if output_format == "模型":
|
||||
if os.path.exists(weight_fix_out_obj_dir):
|
||||
shutil.rmtree(weight_fix_out_obj_dir)
|
||||
if os.path.exists(weight_fix_out_ply_dir):
|
||||
shutil.rmtree(weight_fix_out_ply_dir)
|
||||
if os.path.exists(bounds_fix_out_dir):
|
||||
shutil.rmtree(bounds_fix_out_dir)
|
||||
if os.path.exists(bounds_compact_out_dir):
|
||||
shutil.rmtree(bounds_compact_out_dir)
|
||||
if os.path.exists(compact_obj_out_dir):
|
||||
shutil.rmtree(compact_obj_out_dir)
|
||||
# if os.path.exists(output_folder): # 不需要
|
||||
# shutil.rmtree(output_folder)
|
||||
|
||||
time.sleep(1)
|
||||
if not os.path.exists(weight_fix_out_obj_dir):
|
||||
os.makedirs(weight_fix_out_obj_dir)
|
||||
if not os.path.exists(weight_fix_out_ply_dir):
|
||||
os.makedirs(weight_fix_out_ply_dir)
|
||||
if not os.path.exists(bounds_fix_out_dir):
|
||||
os.mkdir(bounds_fix_out_dir)
|
||||
if not os.path.exists(bounds_compact_out_dir):
|
||||
os.makedirs(bounds_compact_out_dir)
|
||||
if not os.path.exists(compact_obj_out_dir):
|
||||
os.makedirs(compact_obj_out_dir)
|
||||
# if not os.path.exists(output_folder): # 不需要
|
||||
# os.makedirs(output_folder)
|
||||
|
||||
print("selected_machine",selected_machine,"selected_mode",selected_mode,"output_format",output_format)
|
||||
compact_min_dis = True
|
||||
compact_min_dis2 = False
|
||||
if selected_mode=="标准" :
|
||||
compact_min_dis = False
|
||||
if output_format=="JSON":
|
||||
compact_min_dis2 = True
|
||||
else :
|
||||
compact_min_dis = True
|
||||
|
||||
move_back = True
|
||||
|
||||
machine_size = [600, 500, 300]
|
||||
if selected_machine=="小机型":
|
||||
machine_size[0] = 380
|
||||
machine_size[1] = 345
|
||||
machine_size[2] = 250
|
||||
|
||||
start_time = time.time()
|
||||
copy_obj_2x(base_original_obj_dir)
|
||||
dict_bad = {}
|
||||
dict_best_angel = {}
|
||||
dict_fix = {}
|
||||
dict_origin = {}
|
||||
dict_total_matrix= {}
|
||||
dict_mesh_obj = make_bbox_for_print(base_original_obj_dir, weight_fix_out_obj_dir, weight_fix_out_ply_dir,show_chart,dict_bad, dict_best_angel,dict_fix,dict_origin,compact_min_dis or compact_min_dis2,dict_total_matrix)
|
||||
mesh_count = len(dict_mesh_obj)
|
||||
if mesh_count<=0:
|
||||
print("选择的文件夹没有模型")
|
||||
return -1
|
||||
end_time1 = time.time()
|
||||
dict_bounds_fix = {}
|
||||
placed_models= ply_print_layout_platform(weight_fix_out_obj_dir,weight_fix_out_ply_dir,bounds_fix_out_dir,show_chart,dict_mesh_obj,dict_fix,dict_bounds_fix,machine_size,dict_total_matrix)
|
||||
end_time2 = time.time()
|
||||
dict_unplaced = {}
|
||||
dict_compact = {}
|
||||
|
||||
if compact_min_dis:
|
||||
if output_format=="JSON" :
|
||||
can_compact_json = True
|
||||
if can_compact_json :
|
||||
compact_mode_for_min_dis1_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
|
||||
else :
|
||||
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
|
||||
else :
|
||||
compact_mode_for_min_dis(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size)
|
||||
else:
|
||||
if compact_min_dis2:
|
||||
compact_mode_for_min_dis2_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
|
||||
else :
|
||||
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
|
||||
|
||||
end_time3 = time.time()
|
||||
|
||||
#if os.path.exists(placed_remove_dir):
|
||||
# shutil.rmtree(placed_remove_dir)
|
||||
if not os.path.exists(placed_remove_dir):
|
||||
os.makedirs(placed_remove_dir)
|
||||
|
||||
if not os.path.exists(bad_dir):
|
||||
os.makedirs(bad_dir)
|
||||
|
||||
if not os.path.exists(full_dir):
|
||||
os.makedirs(full_dir)
|
||||
|
||||
save_mesh = True if output_format=="模型" else False
|
||||
# move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,dict_compact,save_mesh,dict_origin,dict_total_matrix,print_start_time)
|
||||
|
||||
version = "print_type_setting25.11.21.1"
|
||||
batch_id = os.path.basename(base_path.rstrip('/'))
|
||||
move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,
|
||||
base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,
|
||||
placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,
|
||||
dict_compact,dict_origin,dict_total_matrix,save_mesh,cache_type_setting_dir,
|
||||
batch_id, print_start_time,selected_machine,selected_mode,version)
|
||||
end_time4 = time.time()
|
||||
#move_compact_obj_to_file(compact_obj_out_dir, output_folder) # 不需要
|
||||
print("排版完成")
|
||||
end_time = time.time()
|
||||
elapsed_seconds = end_time - start_time
|
||||
elapsed_seconds1 = end_time1 - start_time # 计算重心
|
||||
elapsed_seconds2 = end_time2 - end_time1 #
|
||||
elapsed_seconds3 = end_time3 - end_time2
|
||||
elapsed_seconds4 = end_time4 - end_time3
|
||||
elapsed_minutes = int(elapsed_seconds // 60)
|
||||
elapsed_minutes1 = int(elapsed_seconds1 // 60)
|
||||
elapsed_minutes2 = int(elapsed_seconds2 // 60)
|
||||
elapsed_minutes3 = int(elapsed_seconds3 // 60)
|
||||
elapsed_minutes4 = int(elapsed_seconds4 // 60)
|
||||
print(f"排版总耗时::{elapsed_minutes} 分 / {elapsed_seconds} 秒")
|
||||
print(f"计算重心::{elapsed_minutes1} 分 / {elapsed_seconds1} 秒")
|
||||
print(f"排包围盒::{elapsed_minutes2} 分 / {elapsed_seconds2} 秒")
|
||||
|
||||
print(f"挪紧凑::{elapsed_minutes3} 分 / {elapsed_seconds3} 秒")
|
||||
print(f"移动到位置::{elapsed_minutes4} 分 / {elapsed_seconds4} 秒")
|
||||
|
||||
# load_and_show(base_original_obj_dir,blank_dir)
|
||||
|
||||
return 0
|
||||
|
||||
def preview(base_original_obj_dir=None):
|
||||
|
||||
base_path = get_base_directory()
|
||||
parent_dir = os.path.dirname(base_path)
|
||||
blank_dir = os.path.join(parent_dir, "blank", "blank_bias")
|
||||
|
||||
load_and_show(base_original_obj_dir,blank_dir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
src_dir = "12-9" # 1 5.6.5 5.6.4 5.6.1 5.9 temp
|
||||
cache_type_setting_dir=f"/data/datasets_20t/type_setting_test_data/{src_dir}"
|
||||
base_original_obj_dir = f"{print_factory_type_dir}/{src_dir}"
|
||||
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,show_chart=False)
|
||||
@@ -1,162 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
|
||||
def merged_obj_for_group(input_folder,output_folder):
|
||||
""""""
|
||||
group_size = 5
|
||||
obj_pid_list = os.listdir(input_folder)
|
||||
group_obj_list = [obj_pid_list[i:i + group_size] for i in range(0, len(obj_pid_list), group_size)]
|
||||
print(group_obj_list)
|
||||
for group_obj in group_obj_list:
|
||||
print(group_obj)
|
||||
group_pid = "_".join(group_obj)
|
||||
print(group_pid)
|
||||
|
||||
output_group_folder = os.path.join(output_folder,group_pid)
|
||||
os.makedirs(output_group_folder, exist_ok=True)
|
||||
|
||||
#input_root_folder = "/data/datasets_20t/obj_merger_test_data/"
|
||||
#output_folder = "/data/datasets_20t/obj_merger_result/"
|
||||
output_obj = os.path.join(output_group_folder, f"{group_pid}.obj")
|
||||
output_mtl = os.path.join(output_group_folder, f"{group_pid}.mtl")
|
||||
|
||||
# 初始化
|
||||
merged_obj = []
|
||||
merged_mtl = []
|
||||
texture_files = set()
|
||||
material_offset = 0
|
||||
vertex_offset = 0
|
||||
texture_offset = 0
|
||||
normal_offset = 0
|
||||
current_materials = {}
|
||||
|
||||
# 遍历每个子文件夹
|
||||
for folder_name in group_obj:
|
||||
folder_path = os.path.join(input_folder, folder_name)
|
||||
if not os.path.isdir(folder_path):
|
||||
continue
|
||||
|
||||
obj_file = None
|
||||
mtl_file = None
|
||||
texture_file = None
|
||||
|
||||
# 寻找 .obj、.mtl 和 .jpg 文件
|
||||
for file_name in os.listdir(folder_path):
|
||||
if file_name.endswith(".obj"):
|
||||
obj_file = os.path.join(folder_path, file_name)
|
||||
elif file_name.endswith(".mtl"):
|
||||
mtl_file = os.path.join(folder_path, file_name)
|
||||
elif file_name.endswith(".jpg"):
|
||||
texture_file = os.path.join(folder_path, file_name)
|
||||
|
||||
# 跳过不完整的文件夹
|
||||
if not obj_file or not mtl_file or not texture_file:
|
||||
print(f"跳过不完整的文件夹:{folder_path}")
|
||||
continue
|
||||
|
||||
# 读取 .obj 文件
|
||||
with open(obj_file, "r") as obj_f:
|
||||
obj_lines = obj_f.readlines()
|
||||
|
||||
|
||||
for line in obj_lines:
|
||||
if line.startswith("mtllib"):
|
||||
# 替换材质文件名
|
||||
|
||||
merged_obj.append(f"mtllib {os.path.basename(output_mtl)}\n")
|
||||
|
||||
elif line.startswith("usemtl"):
|
||||
# 重命名材质名称,避免冲突
|
||||
original_material = line.split()[1]
|
||||
new_material = f"{original_material}_{material_offset}"
|
||||
print(f"original_material---{original_material}")
|
||||
print(f"new_material---{new_material}")
|
||||
merged_obj.append(f"usemtl {new_material}\n")
|
||||
current_materials[original_material] = new_material
|
||||
elif line.startswith("v "): # 顶点
|
||||
vertex = line.split()[1:]
|
||||
merged_obj.append(f"v {' '.join(vertex)}\n")
|
||||
elif line.startswith("vt "): # 纹理坐标
|
||||
texture = line.split()[1:]
|
||||
merged_obj.append(f"vt {' '.join(texture)}\n")
|
||||
elif line.startswith("vn "): # 法线
|
||||
normal = line.split()[1:]
|
||||
merged_obj.append(f"vn {' '.join(normal)}\n")
|
||||
elif line.startswith("f "): # 面数据
|
||||
face = line.split()[1:]
|
||||
updated_face = []
|
||||
for vertex in face:
|
||||
indices = vertex.split("/")
|
||||
indices = [
|
||||
str(int(indices[0]) + vertex_offset) if indices[0] else "",
|
||||
str(int(indices[1]) + texture_offset) if len(indices) > 1 and indices[1] else "",
|
||||
str(int(indices[2]) + normal_offset) if len(indices) > 2 and indices[2] else "",
|
||||
]
|
||||
updated_face.append("/".join(indices))
|
||||
merged_obj.append(f"f {' '.join(updated_face)}\n")
|
||||
|
||||
# 更新偏移量
|
||||
vertex_offset += sum(1 for line in obj_lines if line.startswith("v "))
|
||||
texture_offset += sum(1 for line in obj_lines if line.startswith("vt "))
|
||||
normal_offset += sum(1 for line in obj_lines if line.startswith("vn "))
|
||||
|
||||
# 读取 .mtl 文件
|
||||
with open(mtl_file, "r") as mtl_f:
|
||||
mtl_lines = mtl_f.readlines()
|
||||
|
||||
for line in mtl_lines:
|
||||
if line.startswith("newmtl"):
|
||||
# 重命名材质
|
||||
original_material = line.split()[1]
|
||||
new_material = current_materials.get(original_material, original_material)
|
||||
merged_mtl.append(f"newmtl {new_material}\n")
|
||||
elif line.startswith(("map_Kd", "map_Ka", "map_bump")):
|
||||
# 替换贴图路径为相对路径
|
||||
texture_name = os.path.basename(texture_file)
|
||||
merged_mtl.append(f"{line.split()[0]} {texture_name}\n")
|
||||
texture_files.add(texture_file)
|
||||
else:
|
||||
merged_mtl.append(line)
|
||||
|
||||
material_offset += 1
|
||||
|
||||
# 写入合并后的 .obj 和 .mtl 文件
|
||||
with open(output_obj, "w") as obj_out:
|
||||
obj_out.writelines(merged_obj)
|
||||
|
||||
with open(output_mtl, "w") as mtl_out:
|
||||
mtl_out.writelines(merged_mtl)
|
||||
print(f"texture_files====={texture_files}")
|
||||
# 将纹理文件复制到输出目录
|
||||
for texture_file in texture_files:
|
||||
shutil.copy(texture_file, output_group_folder)
|
||||
|
||||
print(f"合并完成:{output_obj} 和 {output_mtl}")
|
||||
print(f"纹理文件已复制到:{output_group_folder}")
|
||||
|
||||
def move_compact_obj_to_file(input_folder,output_folder):
|
||||
""""""
|
||||
group_size = 50
|
||||
obj_pid_list = os.listdir(input_folder)
|
||||
group_obj_list = [obj_pid_list[i:i + group_size] for i in range(0, len(obj_pid_list), group_size)]
|
||||
print(group_obj_list)
|
||||
for group_obj in group_obj_list:
|
||||
print(f"group_obj{group_obj}")
|
||||
out_obj_file_name = group_obj[0]+"_"+group_obj[-1]
|
||||
print(f"out_obj_file_name:::{out_obj_file_name}")
|
||||
group_out_put_dir = os.path.join(output_folder,out_obj_file_name)
|
||||
os.makedirs(group_out_put_dir,exist_ok=True)
|
||||
for obj_name in group_obj:
|
||||
original_obj_dir = os.path.join(input_folder,obj_name)
|
||||
for file_name in os.listdir(original_obj_dir):
|
||||
original_path = os.path.join(original_obj_dir,file_name)
|
||||
dis_path = os.path.join(group_out_put_dir,file_name)
|
||||
shutil.copy(original_path,dis_path)
|
||||
print("分组完成。")
|
||||
|
||||
if __name__ == '__main__':
|
||||
input_folder = "/data/datasets_20t/type_setting_test_data/print_compact_obj/"
|
||||
output_folder = "/data/datasets_20t/type_setting_test_data/obj_merger_result/"
|
||||
#merged_obj_for_group(input_folder,output_folder)
|
||||
move_compact_obj_to_file(input_folder, output_folder)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,127 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pandas as pd
|
||||
from threading import Thread
|
||||
from PyQt5.QtWidgets import QApplication, QMainWindow,QMessageBox
|
||||
from print_setting_ui import Ui_MainWindow
|
||||
from PyQt5.QtGui import QFont
|
||||
from PySide2.QtCore import Signal,QObject
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
|
||||
import sys
|
||||
import os
|
||||
import warnings
|
||||
import time
|
||||
import time
|
||||
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||
QVBoxLayout, QFileDialog, QMessageBox)
|
||||
from threading import Thread
|
||||
from print_factory_type_setting_obj_run import print_type_setting_obj
|
||||
|
||||
warnings.filterwarnings('ignore')
|
||||
pd.set_option('display.width', None)
|
||||
|
||||
|
||||
class MySignals(QObject):
|
||||
text_print = Signal(str)
|
||||
update_table = Signal(str)
|
||||
|
||||
|
||||
class MyMainForm(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self,parent=None):
|
||||
super(MyMainForm, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.folder_path = ""
|
||||
self.cache_path = ""
|
||||
self.pushButton.clicked.connect(self.on_select_folder)
|
||||
self.pushButton_2.clicked.connect(self.on_run_clicked)
|
||||
self.pushButton_3.clicked.connect(self.on_open_output_clicked)
|
||||
self.global_ms = MySignals()
|
||||
self.global_ms.text_print.connect(self.printToGui)
|
||||
|
||||
def printToGui(self,text):
|
||||
n_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||
self.textBrowser.append(n_time+" "+str(text))
|
||||
|
||||
def on_select_folder(self):
|
||||
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||
if folder:
|
||||
self.folder_path = folder
|
||||
#self.folder_path_label.setText(f"📂 当前选择文件夹: {folder}")
|
||||
#self.run_status_label.setText("")
|
||||
self.cache_path = folder + "_arrange"
|
||||
os.makedirs(self.cache_path, exist_ok=True)
|
||||
|
||||
def on_run_clicked(self):
|
||||
|
||||
def threadFunc1():
|
||||
if not self.folder_path:
|
||||
#self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||
return
|
||||
|
||||
# if hasattr(self, 'worker') and self.worker.isRunning():
|
||||
# self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||
# return
|
||||
#self.run_btn.setEnabled(False)
|
||||
#self.run_status_label.setText("🚀 程序正在运行,请稍候...")
|
||||
|
||||
print_type_setting_obj(
|
||||
base_original_obj_dir=self.folder_path,
|
||||
cache_type_setting_dir=self.cache_path,
|
||||
show_chart=False
|
||||
)
|
||||
|
||||
#self.run_status_label.setText("✅ 排版完成!")
|
||||
|
||||
thread = Thread(target=threadFunc1)
|
||||
thread.start()
|
||||
|
||||
def open_file_cross_platform(self, path):
|
||||
if not os.path.exists(path):
|
||||
print("路径不存在!")
|
||||
return
|
||||
if sys.platform.startswith('win'):
|
||||
os.startfile(path)
|
||||
elif sys.platform.startswith('darwin'):
|
||||
QProcess.startDetached("open", [path])
|
||||
else:
|
||||
QProcess.startDetached("xdg-open", [path])
|
||||
|
||||
def on_open_output_clicked(self):
|
||||
output_path = os.path.join(self.cache_path, "print_compact_obj")
|
||||
if os.path.exists(output_path):
|
||||
self.open_file_cross_platform(output_path)
|
||||
else:
|
||||
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||
|
||||
|
||||
def douyin_spider_go(self):
|
||||
"""下载抖音视频"""
|
||||
def threadFunc1():
|
||||
print("开始下载")
|
||||
self.load_chrome_video()
|
||||
thread = Thread(target=threadFunc1)
|
||||
thread.start()
|
||||
#thread.run()
|
||||
|
||||
def open_config_dir(self):
|
||||
"""打开文件夹"""
|
||||
def threadFunc1():
|
||||
try:
|
||||
start_directory = os.path.join(self.dir_base)
|
||||
os.startfile(start_directory)
|
||||
except RecursionError:
|
||||
print("打开配置文件夹失败。")
|
||||
thread = Thread(target=threadFunc1)
|
||||
thread.start()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
#multiprocessing.freeze_support()
|
||||
app = QApplication(sys.argv)
|
||||
myWin = MyMainForm()
|
||||
myWin.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'dou_spider_ui.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.4
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(300, 150)
|
||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
#self.folder_path_label = QLabel("📁 请选择要排版的文件夹")
|
||||
#self.folder_path_label.setWordWrap(True)
|
||||
# 按钮布局
|
||||
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.pushButton.setObjectName("pushButton")
|
||||
self.verticalLayout.addWidget(self.pushButton)
|
||||
|
||||
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.pushButton_2.setObjectName("pushButton_2")
|
||||
self.verticalLayout.addWidget(self.pushButton_2)
|
||||
|
||||
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.pushButton_3.setObjectName("pushButton_3")
|
||||
self.verticalLayout.addWidget(self.pushButton_3)
|
||||
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.statusbar = QtWidgets.QStatusBar(MainWindow)
|
||||
self.statusbar.setObjectName("statusbar")
|
||||
MainWindow.setStatusBar(self.statusbar)
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "自动排版工具"))
|
||||
self.pushButton.setText(_translate("MainWindow", "选择文件夹"))
|
||||
self.pushButton_2.setText(_translate("MainWindow", "开始自动排版"))
|
||||
self.pushButton_3.setText(_translate("MainWindow", "打开排版好的文件夹"))
|
||||
|
||||
@@ -17,10 +17,7 @@ from multiprocessing import Pool, RawArray
|
||||
import ctypes
|
||||
import itertools
|
||||
|
||||
from get_lowest_position_of_center_ext import get_lowest_position_of_center_ext
|
||||
from get_lowest_position_of_center_ext import get_lowest_position_of_center_ext2
|
||||
from get_lowest_position_of_center_ext import get_lowest_position_of_center_ext3
|
||||
from get_lowest_position_of_center_ext import get_lowest_position_of_center_net
|
||||
from compute_print_net import down_sample
|
||||
|
||||
def make_pcd_plane():
|
||||
# 创建Y=0的平面点云
|
||||
@@ -360,22 +357,6 @@ def parallel_rotation4(points, angle_step=4):
|
||||
|
||||
#"""
|
||||
|
||||
def read_mesh(obj_path, simple=True):
|
||||
mesh_obj = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True) #
|
||||
return mesh_obj
|
||||
if not simple:
|
||||
return mesh_obj
|
||||
original_triangles = len(mesh_obj.triangles)
|
||||
target_triangles = original_triangles if original_triangles <= 10000 else 10000
|
||||
if original_triangles > 10000:
|
||||
mesh_obj = mesh_obj.simplify_quadric_decimation(
|
||||
target_number_of_triangles=target_triangles,
|
||||
maximum_error=0.0001,
|
||||
boundary_weight=1.0
|
||||
)
|
||||
|
||||
return mesh_obj
|
||||
|
||||
def compute_mesh_center(vertices):
|
||||
"""
|
||||
计算网格质心
|
||||
@@ -403,28 +384,6 @@ def compute_mesh_center(vertices):
|
||||
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
|
||||
return centroid
|
||||
|
||||
def down_sample(pcd, voxel_size, farthest_sample = False):
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1500 # 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
|
||||
# 第一步:使用体素下采样快速减少点数量
|
||||
# voxel_size = 3
|
||||
if farthest_sample:
|
||||
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_voxel = pcd.voxel_down_sample(voxel_size)
|
||||
down_num = len(pcd_voxel.points)
|
||||
# print(f"original_num={original_num}, down_num={down_num}")
|
||||
|
||||
# 第二步:仅在必要时进行最远点下采样
|
||||
if len(pcd_voxel.points) > target_samples and False:
|
||||
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
|
||||
else:
|
||||
pcd_downsampled = pcd_voxel
|
||||
|
||||
return pcd_downsampled
|
||||
|
||||
def get_lowest_position_of_center(obj_path,voxel_size,dict_origin,total_matrix):
|
||||
|
||||
mesh_obj = read_mesh(obj_path)
|
||||
@@ -728,153 +687,6 @@ def axis_angle_to_rotation_matrix(axis, angle):
|
||||
cos_a + axis[2]**2*(1-cos_a)]
|
||||
])
|
||||
|
||||
def arrange_box_correctly(obj_transformed, voxel_size,total_matrix):
|
||||
|
||||
vertices = np.asarray(obj_transformed.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
# 降采样与特征计算
|
||||
pcd_downsampled = down_sample(pcd, voxel_size)
|
||||
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1000
|
||||
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
cov = np.cov(points.T)
|
||||
|
||||
center = obj_transformed.get_center()
|
||||
|
||||
# 特征分解与方向约束(关键修改点)
|
||||
eigen_vals, eigen_vecs = np.linalg.eigh(cov)
|
||||
max_axis = eigen_vecs[:, np.argmax(eigen_vals)]
|
||||
|
||||
# arrow = o3d.geometry.TriangleMesh.create_arrow(0.05, 0.1)
|
||||
# arrow.translate(center)
|
||||
# o3d.visualization.draw_geometries([obj_transformed, arrow])
|
||||
|
||||
# print("max_axis", max_axis)
|
||||
# 强制主方向向量X分量为正(指向右侧)
|
||||
if max_axis[0] < 0 or (max_axis[0] == 0 and max_axis[1] < 0):
|
||||
max_axis = -max_axis
|
||||
|
||||
target_dir = np.array([1, 0]) # 目标方向为X正轴
|
||||
current_dir = max_axis[:2] / np.linalg.norm(max_axis[:2])
|
||||
dot_product = np.dot(current_dir, target_dir)
|
||||
|
||||
# print("dot_product", dot_product)
|
||||
if dot_product < 0.8: # 阈值控制方向敏感性(建议0.6~0.9)
|
||||
max_axis = -max_axis # 强制翻转方向
|
||||
|
||||
# 计算旋转角度
|
||||
angle_z = np.arctan2(max_axis[1], max_axis[0]) % (2 * np.pi)
|
||||
|
||||
|
||||
if max_axis[0] <= 0 and max_axis[1] <= 0:
|
||||
angle_z += np.pi
|
||||
|
||||
# print("max_axis2", max_axis, angle_z / np.pi * 180 % 360)
|
||||
|
||||
# angle_z = 0
|
||||
R = o3d.geometry.get_rotation_matrix_from_axis_angle([0, 0, -angle_z])
|
||||
|
||||
T = np.eye(4)
|
||||
T[:3, :3] = R
|
||||
T[:3, 3] = center - R.dot(center) # 保持中心不变
|
||||
obj_transformed.transform(T)
|
||||
|
||||
total_matrix = T @ total_matrix
|
||||
|
||||
#arrow = o3d.geometry.TriangleMesh.create_arrow(0.05, 0.1)
|
||||
#arrow.translate(center)
|
||||
#o3d.visualization.draw_geometries([obj_transformed, arrow])
|
||||
|
||||
return obj_transformed, total_matrix
|
||||
|
||||
def get_new_bbox(obj_transformed_second,obj_name,weight_fix_out_dir,weight_fix_out_ply_dir,voxel_size,show_chart,dict_fix,compact_min_dis,total_matrix):
|
||||
|
||||
# 计算点云的边界
|
||||
points = np.asarray(obj_transformed_second.vertices)
|
||||
|
||||
min_bound = np.min(points, axis=0) # 获取点云的最小边界
|
||||
max_bound = np.max(points, axis=0) # 获取点云的最大边界
|
||||
|
||||
# 确保包围盒的Y坐标不低于0
|
||||
min_bound[2] = max(min_bound[2], 0) # 确保Y坐标的最小值不低于0
|
||||
|
||||
# 重新计算包围盒的中心和半长轴
|
||||
bbox_center = (min_bound + max_bound) / 2 # 计算包围盒的中心点
|
||||
bbox_extent = (max_bound - min_bound) # 计算包围盒的半长轴(尺寸)
|
||||
|
||||
# 创建包围盒,确保尺寸正确
|
||||
new_bbox = o3d.geometry.OrientedBoundingBox(center=bbox_center,
|
||||
R=np.eye(3), # 旋转矩阵,默认没有旋转
|
||||
extent=bbox_extent)
|
||||
# 获取包围盒的长、宽和高
|
||||
x_length = round(bbox_extent[0],3) # X 方向的长
|
||||
y_length = round(bbox_extent[1],3) # Y 方向的宽
|
||||
z_length = round(bbox_extent[2],3) # Z 方向的高
|
||||
bbox_points = np.array([
|
||||
[min_bound[0], min_bound[1], min_bound[2]],
|
||||
[max_bound[0], min_bound[1], min_bound[2]],
|
||||
[max_bound[0], max_bound[1], min_bound[2]],
|
||||
[min_bound[0], max_bound[1], min_bound[2]],
|
||||
[min_bound[0], min_bound[1], max_bound[2]],
|
||||
[max_bound[0], min_bound[1], max_bound[2]],
|
||||
[max_bound[0], max_bound[1], max_bound[2]],
|
||||
[min_bound[0], max_bound[1], max_bound[2]]
|
||||
])
|
||||
first_corner = bbox_points[2]
|
||||
translation_vector = -first_corner
|
||||
# start_time = time.time()
|
||||
obj_transformed_second.translate(translation_vector)
|
||||
|
||||
T_trans = np.eye(4)
|
||||
T_trans[:3, 3] = translation_vector # 设置平移分量 [2,3](@ref)
|
||||
total_matrix = T_trans @ total_matrix # 矩阵乘法顺序:最新变换左乘[4,5](@ref)
|
||||
|
||||
new_bbox.translate(translation_vector)
|
||||
# print("get_new_bbox1",time.time()-start_time)
|
||||
|
||||
vertices = np.asarray(obj_transformed_second.vertices)
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(vertices)
|
||||
|
||||
ply_print_pid = obj_name.replace(".obj","")
|
||||
ply_name = f"{ply_print_pid}={z_length}+{y_length}+{x_length}.ply"
|
||||
ply_out_path = os.path.join(weight_fix_out_ply_dir,ply_name)
|
||||
# o3d.io.write_point_cloud(ply_out_path, pcd_downsampled)
|
||||
# o3d.io.write_point_cloud(ply_out_path, pcd)
|
||||
|
||||
if compact_min_dis:
|
||||
original_num = len(pcd.points)
|
||||
target_samples = 1500 # 1000
|
||||
num_samples = min(target_samples, original_num)
|
||||
start_time = time.time()
|
||||
pcd_downsampled = down_sample(pcd, voxel_size, False)
|
||||
# print("down_sample time =",time.time()-start_time)
|
||||
dict_fix[ply_name] = pcd_downsampled
|
||||
else:
|
||||
dict_fix[ply_name] = pcd
|
||||
|
||||
# print("dict_fix write",ply_name)
|
||||
# print("voxel_down_sample&&write_point_cloud",time.time()-start_time)
|
||||
|
||||
if show_chart:
|
||||
# 创建包围盒的轮廓(线框)
|
||||
new_bbox_lines = o3d.geometry.LineSet.create_from_oriented_bounding_box(new_bbox)
|
||||
new_bbox_lines.paint_uniform_color([1, 0, 0]) # 红色
|
||||
|
||||
#创建坐标系
|
||||
coordinate_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=2, origin=[0, 0, 0])
|
||||
|
||||
# 创建Y=0的平面点云
|
||||
pcd_plane = make_pcd_plane()
|
||||
# 可视化点云和边界框
|
||||
o3d.visualization.draw_geometries([obj_transformed_second, coordinate_frame, pcd_plane, new_bbox_lines])
|
||||
|
||||
return obj_transformed_second, total_matrix
|
||||
|
||||
def load_obj_data(get_pid,base_out_dir):
|
||||
"""下载obj文件"""
|
||||
access_key_id = os.getenv('OSS_TEST_ACCESS_KEY_ID', 'LTAI5tBWbfkZntfJij4Fg9gz')
|
||||
@@ -910,130 +722,6 @@ def load_obj_data(get_pid,base_out_dir):
|
||||
# 如果迭代器没有返回任何对象,那么表示该"文件夹"不存在
|
||||
print("ossFolder does not exist.",get_pid)
|
||||
|
||||
def custom_mesh_transform(vertices, transform_matrix):
|
||||
"""
|
||||
手动实现网格变换:对每个顶点应用齐次变换矩阵
|
||||
参数:
|
||||
vertices: 网格顶点数组 (N, 3)
|
||||
transform_matrix: 4x4 齐次变换矩阵
|
||||
返回:
|
||||
变换后的顶点数组 (N, 3)
|
||||
"""
|
||||
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
|
||||
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
|
||||
|
||||
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
|
||||
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
|
||||
|
||||
# 3. 转回非齐次坐标 (3xN) → (N, 3)
|
||||
transformed_vertices = transformed_homogeneous[:3, :].T
|
||||
return transformed_vertices
|
||||
|
||||
def make_bbox_for_print(base_out_dir,weight_fix_out_dir,weight_fix_out_ply_dir,show_chart,dict_bad, dict_best_angel,dict_fix,dict_origin,dict_origin_real, compact_min_dis,dict_total_matrix):
|
||||
"""获取需要的盒子大小"""
|
||||
# 加载点云数据
|
||||
start_time1 = time.time()
|
||||
obj_id_list = [aa.split(".o")[0] for aa in os.listdir(base_out_dir) if aa.endswith(".obj")]
|
||||
#print(obj_id_list)
|
||||
#print(len(obj_id_list))
|
||||
#random.shuffle(obj_id_list)
|
||||
obj_id_list = obj_id_list
|
||||
#print(obj_id_list)
|
||||
voxel_size = 3 # 设置体素的大小,决定下采样的密度
|
||||
|
||||
#for pid in tqdm(obj_id_list,desc="get new bbox"):
|
||||
dict_mesh_obj = {}
|
||||
for pid_t_y in obj_id_list:
|
||||
start_time2 = time.time()
|
||||
obj_name = pid_t_y+".obj"
|
||||
obj_path = os.path.join(base_out_dir,obj_name)
|
||||
|
||||
total_matrix = np.eye(4)
|
||||
#放置最大接触面
|
||||
# obj_transformed, total_matrix = get_lowest_position_of_center(obj_path,voxel_size,dict_origin,total_matrix)
|
||||
|
||||
mesh_obj = read_mesh(obj_path)
|
||||
|
||||
# dict_origin_real[obj_path] = copy.deepcopy(mesh_obj)
|
||||
dict_origin[obj_path] = copy.deepcopy(mesh_obj)
|
||||
|
||||
start_time3 = time.time()
|
||||
total_matrix, z_min= get_lowest_position_of_center_ext(obj_path, total_matrix)
|
||||
# print("get_lowest_position_of_center_ext time", time.time()-start_time3)
|
||||
# print(f"total_matrix={total_matrix}")
|
||||
print(f"z_min={z_min}")
|
||||
printId = ""
|
||||
match = re.search(r"P(\d+)", obj_name) # 匹配 "P" 后的连续数字
|
||||
if match:
|
||||
printId = match.group(1)
|
||||
# print("printId", printId)
|
||||
|
||||
# total_matrix, z_mean_min = get_lowest_position_of_center_net(printId, total_matrix)
|
||||
|
||||
# print("total_matrix=", total_matrix)
|
||||
|
||||
original_vertices = np.asarray(mesh_obj.vertices)
|
||||
transformed_vertices = custom_mesh_transform(original_vertices, total_matrix)
|
||||
mesh_obj.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
# print("dict_origin[] obj_path=", obj_path)
|
||||
# dict_origin[obj_path] = mesh_obj
|
||||
|
||||
obj_transformed = copy.deepcopy(mesh_obj)
|
||||
translation = total_matrix[:3, 3]
|
||||
# print("make_bbox_for_print0", obj_name, translation)
|
||||
|
||||
if obj_transformed is None:
|
||||
dict_bad[obj_name]=obj_name
|
||||
# print(len(dict_bad))
|
||||
# print(obj_name)
|
||||
# 记录错误文件
|
||||
error_log = os.path.join(base_out_dir, "error_files.txt")
|
||||
with open(error_log, 'a') as f:
|
||||
f.write(f"{obj_path}\n")
|
||||
print(f"Skipping invalid file: {obj_path}")
|
||||
continue
|
||||
|
||||
start_time3 = time.time()
|
||||
best_angle_x, best_angle_y, best_angle_z, z_mean_min = get_lowest_position_of_center_ext3(mesh_obj, obj_path,voxel_size)
|
||||
# print("get_lowest_position_of_center_ext2 time", time.time()-start_time3)
|
||||
# print("best_angle=", best_angle_x, best_angle_y, best_angle_z, z_mean_min)
|
||||
dict_best_angel[obj_name] = [int(round(best_angle_x)), int(round(best_angle_y)), int(round(best_angle_z))]
|
||||
|
||||
start_time3 = time.time()
|
||||
#将点云摆正和X轴平衡
|
||||
obj_transformed_second,total_matrix = arrange_box_correctly(obj_transformed,voxel_size,total_matrix)
|
||||
# print("arrange_box_correctly time", time.time()-start_time3)
|
||||
"""
|
||||
# 创建可视化窗口
|
||||
vis = o3d.visualization.Visualizer()
|
||||
vis.create_window(window_name='模型展示')
|
||||
|
||||
# 添加所有模型到场景
|
||||
vis.add_geometry(obj_transformed_second)
|
||||
|
||||
# 设置相机视角
|
||||
vis.get_render_option().mesh_show_back_face = True
|
||||
vis.get_render_option().light_on = True
|
||||
|
||||
# 运行可视化
|
||||
vis.run()
|
||||
vis.destroy_window()
|
||||
#"""
|
||||
|
||||
#print("摆正后的obj")
|
||||
#o3d.visualization.draw_geometries([obj_transformed_second, ])
|
||||
start_time3 = time.time()
|
||||
mesh_obj,total_matrix = get_new_bbox(obj_transformed_second,obj_name,weight_fix_out_dir,weight_fix_out_ply_dir,voxel_size,show_chart,dict_fix,compact_min_dis,total_matrix)
|
||||
dict_mesh_obj[obj_name] = mesh_obj
|
||||
# print("get_new_bbox time", time.time()-start_time3)
|
||||
|
||||
dict_total_matrix[obj_name] = total_matrix
|
||||
print(f"make_bbox_for_print {obj_name} time={time.time()-start_time2}")
|
||||
|
||||
print(f"make_bbox_for_print total_time={time.time()-start_time1}")
|
||||
|
||||
return dict_mesh_obj
|
||||
import re
|
||||
def copy_obj_2x(base_obj_dir):
|
||||
obj_list = [aa for aa in os.listdir(base_obj_dir) if aa.endswith(".obj")]
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||
QVBoxLayout, QFileDialog, QMessageBox,
|
||||
QRadioButton, QHBoxLayout, QButtonGroup, QGroupBox)
|
||||
from PyQt5.QtCore import QProcess, QTimer
|
||||
from threading import Thread
|
||||
import print_factory_type_setting_obj_run
|
||||
|
||||
|
||||
class AutoLayoutApp(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.small_model_temp = False
|
||||
self.setWindowTitle("自动排版工具")
|
||||
self.setGeometry(200, 200, 600, 380) # 增加窗口高度以容纳新控件
|
||||
self.dw = print_factory_type_setting_obj_run
|
||||
self.folder_path = ""
|
||||
self.cache_path = ""
|
||||
self.process = None
|
||||
if self.small_model_temp:
|
||||
self.selected_mode = "紧凑" # 默认排版模式
|
||||
self.output_format = "模型" # 默认输出格式
|
||||
self.selected_machine = "小机型" # 默认机型
|
||||
else:
|
||||
self.selected_mode = "标准" # 默认排版模式
|
||||
self.output_format = "JSON" # 默认输出格式
|
||||
self.selected_machine = "大机型" # 默认机型
|
||||
self.is_running = False # 跟踪排版状态
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# ===== 机型选择区域(移到最上面) =====
|
||||
machine_group = QGroupBox("机型选择")
|
||||
machine_layout = QHBoxLayout()
|
||||
|
||||
# 创建机型选择按钮组
|
||||
self.machine_group = QButtonGroup(self)
|
||||
|
||||
# 创建两种机型选项
|
||||
self.large_machine = QRadioButton("大机型(600 * 500 * 300)")
|
||||
self.small_machine = QRadioButton("小机型(380 * 345 * 250)")
|
||||
|
||||
if self.small_model_temp:
|
||||
self.large_machine.setEnabled(False)
|
||||
self.small_machine.setChecked(True) # 默认选中
|
||||
else:
|
||||
self.large_machine.setChecked(True) # 默认选中
|
||||
|
||||
# 添加到按钮组
|
||||
self.machine_group.addButton(self.large_machine, 1)
|
||||
self.machine_group.addButton(self.small_machine, 2)
|
||||
|
||||
# 连接信号
|
||||
self.machine_group.buttonToggled.connect(self.on_machine_changed)
|
||||
|
||||
# 添加到布局
|
||||
machine_layout.addWidget(self.large_machine)
|
||||
machine_layout.addWidget(self.small_machine)
|
||||
machine_group.setLayout(machine_layout)
|
||||
layout.addWidget(machine_group)
|
||||
|
||||
# ===== 排版模式选择区域(改为QGroupBox) =====
|
||||
mode_group = QGroupBox("排版模式")
|
||||
mode_layout = QHBoxLayout()
|
||||
|
||||
# 创建单选按钮组
|
||||
self.mode_group = QButtonGroup(self)
|
||||
|
||||
# 创建三种排版模式选项
|
||||
self.standard_mode = QRadioButton("标准模式(适合规整模型组)")
|
||||
self.compact_mode = QRadioButton("紧凑模式(复杂度较高))")
|
||||
self.advanced_mode = QRadioButton("高级模式(复杂度最高)")
|
||||
self.advanced_mode.setVisible(False)
|
||||
|
||||
if self.small_model_temp:
|
||||
self.standard_mode.setEnabled(False)
|
||||
self.compact_mode.setChecked(True) # 默认选中
|
||||
else:
|
||||
self.standard_mode.setChecked(True) # 默认选中
|
||||
|
||||
# 添加到按钮组(确保互斥选择)
|
||||
self.mode_group.addButton(self.standard_mode, 1)
|
||||
self.mode_group.addButton(self.compact_mode, 2)
|
||||
self.mode_group.addButton(self.advanced_mode, 3)
|
||||
|
||||
# 连接信号
|
||||
self.mode_group.buttonToggled.connect(self.on_mode_changed)
|
||||
|
||||
# 添加到布局
|
||||
mode_layout.addWidget(self.standard_mode)
|
||||
mode_layout.addWidget(self.compact_mode)
|
||||
mode_layout.addWidget(self.advanced_mode)
|
||||
mode_group.setLayout(mode_layout)
|
||||
layout.addWidget(mode_group)
|
||||
|
||||
# ==== 输出格式选择区域 ====
|
||||
format_group = QGroupBox("输出格式")
|
||||
format_layout = QHBoxLayout()
|
||||
|
||||
# 创建输出格式按钮组
|
||||
self.format_group = QButtonGroup(self)
|
||||
|
||||
# 创建两种输出格式选项
|
||||
self.json_format = QRadioButton("JSON格式")
|
||||
self.model_format = QRadioButton("模型格式")
|
||||
|
||||
if self.small_model_temp:
|
||||
self.json_format.setEnabled(False)
|
||||
self.model_format.setChecked(True) # 默认选中
|
||||
else:
|
||||
self.json_format.setChecked(True) # 默认选中
|
||||
|
||||
# 添加到按钮组
|
||||
self.format_group.addButton(self.json_format, 1)
|
||||
self.format_group.addButton(self.model_format, 2)
|
||||
|
||||
# 连接信号
|
||||
self.format_group.buttonToggled.connect(self.on_format_changed)
|
||||
|
||||
# 添加到布局
|
||||
format_layout.addWidget(self.json_format)
|
||||
format_layout.addWidget(self.model_format)
|
||||
format_group.setLayout(format_layout)
|
||||
layout.addWidget(format_group)
|
||||
|
||||
# ===== 原有UI元素 =====
|
||||
self.folder_path_label = QLabel(" 请选择要排版的文件夹")
|
||||
self.folder_path_label.setWordWrap(True)
|
||||
|
||||
self.run_status_label = QLabel("")
|
||||
|
||||
layout.addWidget(self.folder_path_label)
|
||||
layout.addWidget(self.run_status_label)
|
||||
|
||||
self.select_folder_btn = QPushButton("选择文件夹")
|
||||
self.select_folder_btn.clicked.connect(self.on_select_folder)
|
||||
layout.addWidget(self.select_folder_btn)
|
||||
|
||||
# 按钮布局(运行按钮和预览按钮在同一行)
|
||||
buttons_layout = QHBoxLayout()
|
||||
|
||||
self.run_btn = QPushButton("开始自动排版")
|
||||
self.run_btn.clicked.connect(self.on_run_clicked)
|
||||
buttons_layout.addWidget(self.run_btn)
|
||||
|
||||
# ===== 新增预览按钮 =====
|
||||
self.preview_btn = QPushButton("预览排版结果")
|
||||
self.preview_btn.clicked.connect(self.on_preview_clicked)
|
||||
self.preview_btn.setEnabled(False) # 初始禁用,排版完成后才可用
|
||||
buttons_layout.addWidget(self.preview_btn)
|
||||
|
||||
layout.addLayout(buttons_layout)
|
||||
|
||||
self.open_output_btn = QPushButton("打开排版好的文件夹")
|
||||
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
|
||||
layout.addWidget(self.open_output_btn)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def on_mode_changed(self, button, checked):
|
||||
"""处理排版模式选择变化"""
|
||||
if checked:
|
||||
# self.selected_mode = button.text().replace("模式", "")
|
||||
self.selected_mode = button.text()[:2]
|
||||
self.run_status_label.setText(f"已选择: {self.selected_mode} 模式")
|
||||
|
||||
def on_format_changed(self, button, checked):
|
||||
"""处理输出格式选择变化"""
|
||||
if checked:
|
||||
self.output_format = button.text().replace("格式", "")
|
||||
self.run_status_label.setText(f"输出格式: {self.output_format}")
|
||||
|
||||
def on_machine_changed(self, button, checked):
|
||||
"""处理机型选择变化"""
|
||||
if checked:
|
||||
self.selected_machine = button.text().split("(")[0]
|
||||
self.run_status_label.setText(f"已选择: {self.selected_machine}")
|
||||
|
||||
def on_select_folder(self):
|
||||
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||
if folder:
|
||||
self.folder_path = folder
|
||||
self.folder_path_label.setText(f" 当前选择文件夹: {folder}")
|
||||
self.run_status_label.setText("")
|
||||
self.cache_path = folder + "_arrange"
|
||||
# self.cache_path = os.path.join(folder, "arrange")
|
||||
|
||||
os.makedirs(self.cache_path, exist_ok=True)
|
||||
self.preview_btn.setEnabled(False) # 选择新文件夹后禁用预览按钮
|
||||
|
||||
def get_base_directory(self):
|
||||
"""获取脚本或可执行文件的基础目录"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 打包后的可执行文件环境
|
||||
base_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# 正常脚本运行环境
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
return base_path
|
||||
|
||||
def on_run_clicked(self):
|
||||
# 获取脚本所在目录的父目录
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(script_dir)
|
||||
|
||||
# 获取基础目录
|
||||
base_path = self.get_base_directory()
|
||||
# 获取父目录
|
||||
parent_dir = os.path.dirname(base_path)
|
||||
bad_dir = os.path.join(parent_dir, "bad")
|
||||
full_dir = os.path.join(parent_dir, "full")
|
||||
|
||||
# 检查bad目录
|
||||
bad_dir_exists = os.path.exists(bad_dir) and os.path.isdir(bad_dir)
|
||||
bad_dir_not_empty = bad_dir_exists and any(os.scandir(bad_dir))
|
||||
|
||||
# 检查full目录
|
||||
full_dir_exists = os.path.exists(full_dir) and os.path.isdir(full_dir)
|
||||
full_dir_not_empty = full_dir_exists and any(os.scandir(full_dir))
|
||||
|
||||
# 如果有异常数据需要处理
|
||||
if bad_dir_not_empty or full_dir_not_empty:
|
||||
message = "请处理以下目录中的异常数据:\n"
|
||||
if bad_dir_not_empty:
|
||||
message += f"- bad目录: {bad_dir}\n"
|
||||
if full_dir_not_empty:
|
||||
message += f"- full目录: {full_dir}\n"
|
||||
|
||||
QMessageBox.warning(self, "存在未处理的异常数据",
|
||||
message + "\n请先处理这些目录中的数据后再进行排版!")
|
||||
self.run_status_label.setText("⚠️ 存在未处理的异常数据,请先处理!")
|
||||
return
|
||||
|
||||
def threadFunc1():
|
||||
self.is_running = True # 标记排版开始
|
||||
if not self.folder_path:
|
||||
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||
self.is_running = False
|
||||
return
|
||||
|
||||
if self.process and self.process.state() == QProcess.Running:
|
||||
self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||
self.is_running = False
|
||||
return
|
||||
|
||||
self.run_btn.setEnabled(False)
|
||||
self.preview_btn.setEnabled(False) # 排版中禁用预览按钮
|
||||
self.run_status_label.setText(
|
||||
f" 正在使用 [{self.selected_mode}] 排版, 机型: {self.selected_machine}, 输出格式: {self.output_format}, 请稍候..."
|
||||
)
|
||||
|
||||
normalized_path = os.path.normpath(self.folder_path)
|
||||
self.batch_id = os.path.basename(normalized_path)
|
||||
|
||||
# 调用排版函数,传递所有参数
|
||||
self.dw.print_type_setting_obj(
|
||||
base_original_obj_dir=self.folder_path,
|
||||
cache_type_setting_dir=self.cache_path,
|
||||
show_chart=False,
|
||||
batch_id=self.batch_id,
|
||||
selected_mode=self.selected_mode,
|
||||
output_format=self.output_format,
|
||||
selected_machine=self.selected_machine
|
||||
)
|
||||
|
||||
self.run_status_label.setText(
|
||||
f"✅ [{self.selected_mode}] 排版完成! 机型: {self.selected_machine}, 输出格式: {self.output_format}"
|
||||
)
|
||||
self.run_btn.setEnabled(True)
|
||||
self.preview_btn.setEnabled(True) # 排版完成后启用预览按钮
|
||||
self.is_running = False # 标记排版结束
|
||||
|
||||
thread = Thread(target=threadFunc1)
|
||||
thread.start()
|
||||
|
||||
def on_process_finished(self, exitCode, exitStatus):
|
||||
self.run_btn.setEnabled(True)
|
||||
self.preview_btn.setEnabled(True) # 完成后启用预览按钮
|
||||
if exitCode == 0:
|
||||
self.run_status_label.setText("✅ 排版完成!")
|
||||
else:
|
||||
self.run_status_label.setText(f"❌❌ 进程异常退出 (代码: {exitCode})")
|
||||
|
||||
def on_process_error(self, error):
|
||||
self.run_btn.setEnabled(True)
|
||||
self.preview_btn.setEnabled(False) # 出错时禁用预览按钮
|
||||
self.run_status_label.setText(f"❌❌ 发生错误: {error.name}")
|
||||
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
|
||||
|
||||
def open_file_cross_platform(self, path):
|
||||
if not os.path.exists(path):
|
||||
self.run_status_label.setText("⚠️ 路径不存在!")
|
||||
return
|
||||
if sys.platform.startswith('win'):
|
||||
os.startfile(path)
|
||||
elif sys.platform.startswith('darwin'):
|
||||
QProcess.startDetached("open", [path])
|
||||
else:
|
||||
QProcess.startDetached("xdg-open", [path])
|
||||
|
||||
def on_preview_clicked(self):
|
||||
"""预览按钮点击事件处理"""
|
||||
if not self.cache_path:
|
||||
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||
return
|
||||
|
||||
is_small_machine = self.selected_machine=="小机型"
|
||||
|
||||
if os.path.exists(self.folder_path):
|
||||
self.run_status_label.setText("正在打开预览目录...")
|
||||
self.dw.preview(
|
||||
base_original_obj_dir=self.folder_path,batch_id=self.batch_id, is_small_machine=is_small_machine
|
||||
)
|
||||
else:
|
||||
self.run_status_label.setText("⚠️ 预览目录不存在!")
|
||||
|
||||
def on_open_output_clicked(self):
|
||||
"""打开排版结果文件夹"""
|
||||
if not self.cache_path:
|
||||
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||
return
|
||||
|
||||
open_dir = self.cache_path
|
||||
if self.output_format=="JSON":
|
||||
open_dir = self.folder_path
|
||||
|
||||
if os.path.exists(open_dir):
|
||||
self.open_file_cross_platform(open_dir)
|
||||
else:
|
||||
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt5.QtCore import QSharedMemory
|
||||
app = QApplication(sys.argv)
|
||||
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
|
||||
if not shared_mem.create(1):
|
||||
QMessageBox.critical(None, "错误", "程序已经在运行中!")
|
||||
sys.exit(1)
|
||||
window = AutoLayoutApp()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -1,38 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['print_type_setting_gui.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='print_type_setting_gui',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
@@ -1,126 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||
QVBoxLayout, QFileDialog, QMessageBox)
|
||||
from PyQt5.QtCore import QProcess, QTimer
|
||||
from threading import Thread
|
||||
import print_factory_type_setting_obj_run
|
||||
|
||||
|
||||
class AutoLayoutApp(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("自动排版工具")
|
||||
self.setGeometry(200, 200, 600, 200)
|
||||
self.dw= print_factory_type_setting_obj_run
|
||||
self.folder_path = ""
|
||||
self.cache_path = ""
|
||||
self.process = None
|
||||
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
layout = QVBoxLayout()
|
||||
|
||||
self.folder_path_label = QLabel("�� 请选择要排版的文件夹")
|
||||
self.folder_path_label.setWordWrap(True)
|
||||
|
||||
self.run_status_label = QLabel("")
|
||||
|
||||
layout.addWidget(self.folder_path_label)
|
||||
layout.addWidget(self.run_status_label)
|
||||
|
||||
self.select_folder_btn = QPushButton("选择文件夹")
|
||||
self.select_folder_btn.clicked.connect(self.on_select_folder)
|
||||
layout.addWidget(self.select_folder_btn)
|
||||
|
||||
self.run_btn = QPushButton("开始自动排版")
|
||||
self.run_btn.clicked.connect(self.on_run_clicked)
|
||||
layout.addWidget(self.run_btn)
|
||||
|
||||
self.open_output_btn = QPushButton("打开排版好的文件夹")
|
||||
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
|
||||
layout.addWidget(self.open_output_btn)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
def on_select_folder(self):
|
||||
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||
if folder:
|
||||
self.folder_path = folder
|
||||
self.folder_path_label.setText(f"�� 当前选择文件夹: {folder}")
|
||||
self.run_status_label.setText("")
|
||||
self.cache_path = folder + "_arrange"
|
||||
os.makedirs(self.cache_path, exist_ok=True)
|
||||
|
||||
def on_run_clicked(self):
|
||||
def threadFunc1():
|
||||
if not self.folder_path:
|
||||
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||
return
|
||||
|
||||
if self.process and self.process.state() == QProcess.Running:
|
||||
self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||
return
|
||||
|
||||
self.run_btn.setEnabled(False)
|
||||
self.run_status_label.setText("�� 程序正在运行,请稍候...")
|
||||
|
||||
return_code = self.dw.print_type_setting_obj(base_original_obj_dir=self.folder_path,cache_type_setting_dir=self.cache_path,
|
||||
show_chart=False)
|
||||
if return_code==0:
|
||||
self.run_status_label.setText("✅ 排版完成!")
|
||||
elif return_code==-1:
|
||||
self.run_status_label.setText("❌选择目录为空!")
|
||||
else:
|
||||
self.run_status_label.setText("❌排版失败!")
|
||||
self.run_btn.setEnabled(True)
|
||||
|
||||
thread = Thread(target=threadFunc1)
|
||||
thread.start()
|
||||
|
||||
def on_process_finished(self, exitCode, exitStatus):
|
||||
self.run_btn.setEnabled(True)
|
||||
if exitCode == 0:
|
||||
self.run_status_label.setText("✅ 排版完成!")
|
||||
else:
|
||||
self.run_status_label.setText(f"❌ 进程异常退出 (代码: {exitCode})")
|
||||
|
||||
def on_process_error(self, error):
|
||||
self.run_btn.setEnabled(True)
|
||||
self.run_status_label.setText(f"❌ 发生错误: {error.name}")
|
||||
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
|
||||
|
||||
def open_file_cross_platform(self, path):
|
||||
if not os.path.exists(path):
|
||||
self.run_status_label.setText("⚠️ 路径不存在!")
|
||||
return
|
||||
if sys.platform.startswith('win'):
|
||||
os.startfile(path)
|
||||
elif sys.platform.startswith('darwin'):
|
||||
QProcess.startDetached("open", [path])
|
||||
else:
|
||||
QProcess.startDetached("xdg-open", [path])
|
||||
|
||||
def on_open_output_clicked(self):
|
||||
if not self.cache_path:
|
||||
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||
return
|
||||
|
||||
output_path = os.path.join(self.cache_path, "print_compact_obj")
|
||||
if os.path.exists(output_path):
|
||||
self.open_file_cross_platform(output_path)
|
||||
else:
|
||||
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from PyQt5.QtCore import QSharedMemory
|
||||
app = QApplication(sys.argv)
|
||||
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
|
||||
if not shared_mem.create(1):
|
||||
QMessageBox.critical(None, "错误", "程序已经在运行中!")
|
||||
sys.exit(1)
|
||||
window = AutoLayoutApp()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox
|
||||
|
||||
class MyWindow(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.setWindowTitle("PyQt5 简单示例")
|
||||
self.setGeometry(100, 100, 300, 200)
|
||||
|
||||
self.button = QPushButton("点我一下", self)
|
||||
self.button.setGeometry(100, 80, 100, 30)
|
||||
self.button.clicked.connect(self.show_message)
|
||||
|
||||
def show_message(self):
|
||||
QMessageBox.information(self, "提示", "按钮被点击了!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = MyWindow()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
"""
|
||||
pyinstaller qt5_demo.py --hidden-import PySide2.QtXml
|
||||
"""
|
||||
@@ -1,44 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['qt5_demo.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=['PySide2.QtXml'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='qt5_demo',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='qt5_demo',
|
||||
)
|
||||
@@ -1,275 +0,0 @@
|
||||
import open3d as o3d
|
||||
import os
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation
|
||||
import sys
|
||||
|
||||
import argparse
|
||||
# import cv2
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from numba import njit, prange
|
||||
import time
|
||||
|
||||
|
||||
# 核心计算函数(支持Numba加速)
|
||||
@njit(fastmath=True, cache=True)
|
||||
def calculate_rotation_z(angle_x, angle_y, angle_z, points, cos_cache, sin_cache, angle_step):
|
||||
"""计算单个旋转组合后的重心Z坐标(无显式平移)"""
|
||||
# 获取预计算的三角函数值
|
||||
idx_x = angle_x // angle_step
|
||||
idx_y = angle_y // angle_step
|
||||
idx_z = angle_z // angle_step
|
||||
|
||||
cos_x = cos_cache[idx_x]
|
||||
sin_x = sin_cache[idx_x]
|
||||
cos_y = cos_cache[idx_y]
|
||||
sin_y = sin_cache[idx_y]
|
||||
cos_z = cos_cache[idx_z]
|
||||
sin_z = sin_cache[idx_z]
|
||||
|
||||
# 构造旋转矩阵(展开矩阵乘法优化)
|
||||
# R = Rz @ Ry @ Rx
|
||||
# 计算矩阵元素(手动展开矩阵乘法)
|
||||
m00 = cos_z * cos_y
|
||||
m01 = cos_z * sin_y * sin_x - sin_z * cos_x
|
||||
m02 = cos_z * sin_y * cos_x + sin_z * sin_x
|
||||
|
||||
m10 = sin_z * cos_y
|
||||
m11 = sin_z * sin_y * sin_x + cos_z * cos_x
|
||||
m12 = sin_z * sin_y * cos_x - cos_z * sin_x
|
||||
|
||||
m20 = -sin_y
|
||||
m21 = cos_y * sin_x
|
||||
m22 = cos_y * cos_x
|
||||
|
||||
# 计算所有点的Z坐标
|
||||
z_values = np.empty(points.shape[0], dtype=np.float64)
|
||||
for i in prange(points.shape[0]):
|
||||
x, y, z = points[i, 0], points[i, 1], points[i, 2]
|
||||
# 应用旋转矩阵
|
||||
rotated_z = m20 * x + m21 * y + m22 * z
|
||||
z_values[i] = rotated_z
|
||||
|
||||
# 计算重心Z(等效于平移后的重心)
|
||||
min_z = np.min(z_values)
|
||||
avg_z = np.mean(z_values)
|
||||
return avg_z - min_z # 等效于平移后的重心Z坐标
|
||||
|
||||
|
||||
# 并行优化主函数
|
||||
def parallel_rotation2(points, angle_step=3):
|
||||
"""
|
||||
参数:
|
||||
points : numpy.ndarray (N,3) - 三维点云
|
||||
angle_step : int - 角度搜索步长(度数)
|
||||
|
||||
返回:
|
||||
(best_angle_x, best_angle_y, best_angle_z, min_z)
|
||||
"""
|
||||
points = np.ascontiguousarray(points.astype(np.float64))
|
||||
|
||||
# 生成所有可能角度
|
||||
angles = np.arange(0, 360, angle_step)
|
||||
n_angles = len(angles)
|
||||
|
||||
# 预计算三角函数值(大幅减少重复计算)
|
||||
rads = np.radians(angles)
|
||||
cos_cache = np.cos(rads).astype(np.float64)
|
||||
sin_cache = np.sin(rads).astype(np.float64)
|
||||
|
||||
# 生成所有角度组合(内存优化版)
|
||||
total_combinations = n_angles ** 3
|
||||
print(f"Total combinations: {total_combinations:,}")
|
||||
|
||||
# 分块处理以避免内存溢出
|
||||
best_z = np.inf
|
||||
best_angles = (0, 0, 0)
|
||||
batch_size = 10 ** 6 # 根据可用内存调整
|
||||
|
||||
for x_chunk in range(0, n_angles, max(1, n_angles // 4)):
|
||||
angles_x = angles[x_chunk:x_chunk + max(1, n_angles // 4)]
|
||||
for y_chunk in range(0, n_angles, max(1, n_angles // 4)):
|
||||
angles_y = angles[y_chunk:y_chunk + max(1, n_angles // 4)]
|
||||
|
||||
# 生成当前分块的所有组合
|
||||
xx, yy, zz = np.meshgrid(angles_x, angles_y, angles)
|
||||
current_batch = np.stack([xx.ravel(), yy.ravel(), zz.ravel()], axis=1)
|
||||
|
||||
# 处理子批次
|
||||
for i in range(0, len(current_batch), batch_size):
|
||||
batch = current_batch[i:i + batch_size]
|
||||
results = np.zeros(len(batch), dtype=np.float64)
|
||||
_process_batch(batch, points, cos_cache, sin_cache, angle_step, results)
|
||||
|
||||
# 更新最佳结果
|
||||
min_idx = np.argmin(results)
|
||||
if results[min_idx] < best_z:
|
||||
best_z = results[min_idx]
|
||||
best_angles = tuple(batch[min_idx])
|
||||
print(f"New best: {best_angles} -> Z={best_z:.4f}")
|
||||
|
||||
return (*best_angles, best_z)
|
||||
|
||||
|
||||
@njit(parallel=True, fastmath=True)
|
||||
def _process_batch(batch, points, cos_cache, sin_cache, angle_step, results):
|
||||
for i in prange(len(batch)):
|
||||
ax, ay, az = batch[i]
|
||||
results[i] = calculate_rotation_z(
|
||||
ax, ay, az, points,
|
||||
cos_cache, sin_cache, angle_step
|
||||
)
|
||||
|
||||
|
||||
class ModelProcessor:
|
||||
def __init__(self):
|
||||
|
||||
# argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument(
|
||||
"--id",
|
||||
required=False,
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
self.id = args.id
|
||||
|
||||
self.mesh = None
|
||||
self.asset_dir = f"/home/algo/Documents/datasets/{self.id}"
|
||||
|
||||
def load_model(self):
|
||||
"""加载并初始化3D模型"""
|
||||
# model_path = f"{self.asset_dir}/baked/{self.id}.obj"
|
||||
# model_path = f"{self.asset_dir}/repair_{self.id}_mesh.ply"
|
||||
model_path = "/data/datasets_20t/8/88884_253283_P65951_6cm_x1.obj"
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(f"Model file not found: {model_path}")
|
||||
|
||||
print(model_path)
|
||||
|
||||
mesh_native = o3d.io.read_triangle_mesh(model_path, enable_post_processing=False)
|
||||
# self.mesh = o3d.io.read_triangle_mesh(model_path, enable_post_processing=False)
|
||||
|
||||
print("Open3D去重前顶点数:", len(mesh_native.vertices))
|
||||
self.mesh = mesh_native.merge_close_vertices(eps=1e-6)
|
||||
|
||||
vertices2 = np.asarray(self.mesh.vertices)
|
||||
print("Open3D去重后顶点数:", len(vertices2))
|
||||
vertices2_sorted = sorted(
|
||||
vertices2.tolist(),
|
||||
key=lambda x: (x[0], x[1], x[2])
|
||||
)
|
||||
|
||||
if not self.mesh.has_vertex_colors():
|
||||
num_vertices = len(self.mesh.vertices)
|
||||
self.mesh.vertex_colors = o3d.utility.Vector3dVector(
|
||||
np.ones((num_vertices, 3))
|
||||
)
|
||||
|
||||
self.uv_array = np.asarray(self.mesh.triangle_uvs)
|
||||
# print(f"UV 坐标形状:{self.uv_array.shape}, {self.uv_array[0][1]}")
|
||||
|
||||
def calculate_rotation_and_center_of_mass(self, angle_x, angle_y, angle_z, points):
|
||||
"""计算某一组旋转角度后的重心"""
|
||||
# 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||
R_x = np.array([
|
||||
[1, 0, 0],
|
||||
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||
])
|
||||
|
||||
R_y = np.array([
|
||||
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||
[0, 1, 0],
|
||||
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||
])
|
||||
|
||||
R_z = np.array([
|
||||
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||
[0, 0, 1]
|
||||
])
|
||||
|
||||
# 综合旋转矩阵
|
||||
R = R_z @ R_y @ R_x
|
||||
|
||||
# 执行旋转
|
||||
rotated_points = points @ R.T
|
||||
|
||||
# 计算最小z值
|
||||
min_z = np.min(rotated_points[:, 2])
|
||||
|
||||
# 计算平移向量,将最小Z值平移到0
|
||||
translation_vector = np.array([0, 0, -min_z])
|
||||
rotated_points += translation_vector
|
||||
|
||||
# 计算重心
|
||||
center_of_mass = np.mean(rotated_points, axis=0)
|
||||
|
||||
return center_of_mass[2], angle_x, angle_y, angle_z
|
||||
|
||||
def parallel_rotation(self, angle_step=3):
|
||||
"""顺序计算最优旋转角度(单线程)"""
|
||||
min_center_of_mass_y = float('inf')
|
||||
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
|
||||
# 遍历所有角度组合
|
||||
for angle_x in range(0, 360, angle_step):
|
||||
for angle_y in range(0, 360, angle_step):
|
||||
for angle_z in range(0, 360, angle_step):
|
||||
center_of_mass_z, ax, ay, az = self.calculate_rotation_and_center_of_mass(
|
||||
angle_x, angle_y, angle_z, self.mesh.vertices
|
||||
)
|
||||
if center_of_mass_z < min_center_of_mass_y:
|
||||
min_center_of_mass_y = center_of_mass_z
|
||||
best_angle_x, best_angle_y, best_angle_z = ax, ay, az
|
||||
|
||||
return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
|
||||
|
||||
def process(self):
|
||||
"""执行完整处理流程"""
|
||||
self.load_model()
|
||||
|
||||
try:
|
||||
start = time.time()
|
||||
|
||||
# mesh = o3d.geometry.TriangleMesh()
|
||||
# mesh.vertices = o3d.utility.Vector3dVector(np.random.rand(100, 3))
|
||||
# points = np.asarray(mesh.vertices)
|
||||
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(self.mesh.vertices)
|
||||
|
||||
# 自动计算合理体素大小
|
||||
raw_points = np.asarray(pcd.points)
|
||||
bounds = np.ptp(raw_points, axis=0)
|
||||
voxel_size = np.max(bounds) / 50 # 默认取最大边长的2%
|
||||
|
||||
# 执行下采样并验证
|
||||
pcd_downsampled = pcd.voxel_down_sample(voxel_size)
|
||||
if len(pcd_downsampled.points) < 10: # 最少保留10个点
|
||||
raise RuntimeError(f"下采样失败:voxel_size={voxel_size:.3f}过大")
|
||||
|
||||
print(f"下采样后点数: {len(pcd_downsampled.points)} (voxel_size={voxel_size:.3f})")
|
||||
|
||||
# pcd.paint_uniform_color([1,0,0]) # 原始红色
|
||||
# pcd_downsampled.paint_uniform_color([0,0,1]) # 采样后蓝色
|
||||
# o3d.visualization.draw_geometries([pcd, pcd_downsampled])
|
||||
|
||||
# 继续后续处理
|
||||
points = np.asarray(pcd_downsampled.points)
|
||||
|
||||
best_angle_x, best_angle_y, best_angle_z, min_z = parallel_rotation2(points, angle_step=5)
|
||||
print("best=", best_angle_x, best_angle_y, best_angle_z, min_z)
|
||||
print(time.time() - start)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error during processing: {str(e)}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ModelProcessor().process()
|
||||
@@ -1,19 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
printId = 84258
|
||||
url = f"https://mp.api.suwa3d.com/api/printOrder/infoByPrintId?printId={printId}"
|
||||
res = requests.get(url)
|
||||
print(res)
|
||||
|
||||
datas = res.json()["data"]["layout"]
|
||||
print(datas)
|
||||
angle_x = datas.get("angle_x",0)
|
||||
angle_y = datas.get("angle_y",0)
|
||||
angle_z = datas.get("angle_z",0)
|
||||
layout_z = datas.get("layout_z",0)
|
||||
print("angle_x",angle_x)
|
||||
print("angle_y",angle_y)
|
||||
print("angle_z",angle_z)
|
||||
print("layout_z",layout_z)
|
||||
# TODO 解析 res
|
||||
+10
-24
@@ -5,24 +5,8 @@ import os
|
||||
from PIL import Image
|
||||
import argparse
|
||||
|
||||
def custom_mesh_transform(vertices, transform_matrix):
|
||||
"""
|
||||
手动实现网格变换:对每个顶点应用齐次变换矩阵
|
||||
参数:
|
||||
vertices: 网格顶点数组 (N, 3)
|
||||
transform_matrix: 4x4 齐次变换矩阵
|
||||
返回:
|
||||
变换后的顶点数组 (N, 3)
|
||||
"""
|
||||
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
|
||||
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
|
||||
|
||||
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
|
||||
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
|
||||
|
||||
# 3. 转回非齐次坐标 (3xN) → (N, 3)
|
||||
transformed_vertices = transformed_homogeneous[:3, :].T
|
||||
return transformed_vertices
|
||||
from config import print_factory_type_dir
|
||||
from general import mesh_transform_by_matrix
|
||||
|
||||
def load_and_transform_models(base_path, dict_origin, blank_path, json_name):
|
||||
|
||||
@@ -46,6 +30,8 @@ def load_and_transform_models(base_path, dict_origin, blank_path, json_name):
|
||||
print(f"读取JSON文件失败: {e}")
|
||||
return []
|
||||
|
||||
print(f"选择机型={data.get('summary')['selected_machine']}")
|
||||
|
||||
# 处理每个模型
|
||||
for model in data.get('models', []):
|
||||
obj_name = model.get('file_name', '')
|
||||
@@ -86,7 +72,7 @@ def load_and_transform_models(base_path, dict_origin, blank_path, json_name):
|
||||
|
||||
# 手动变换顶点
|
||||
original_vertices = np.asarray(mesh.vertices)
|
||||
transformed_vertices = custom_mesh_transform(original_vertices, reconstructed_matrix)
|
||||
transformed_vertices = mesh_transform_by_matrix(original_vertices, reconstructed_matrix)
|
||||
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
|
||||
|
||||
# 添加到列表
|
||||
@@ -467,16 +453,16 @@ def load_show_save(base_path, dict_origin, blank_path, batch_id, is_show=False):
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--batch_id", type=str, required=True, help="batch_id")
|
||||
parser.add_argument("--batch_id", type=str, required=False, help="batch_id")
|
||||
args = parser.parse_args()
|
||||
|
||||
# batch_id = args.batch_id
|
||||
batch_id = "9" # 1113-MY-4
|
||||
batch_id = "test" # 1113-MY-4
|
||||
|
||||
print_factory_type_dir="/root/print_factory_type"
|
||||
base_path = f"{print_factory_type_dir}/{batch_id}/"
|
||||
# blank_path = "{print_factory_type_dir}/blank/blank_bias/blank2.obj"
|
||||
blank_path = f"{print_factory_type_dir}/blank/blank_bias/blank_small.obj"
|
||||
blank_path = f"{print_factory_type_dir}/blank/blank_bias/blank2.obj"
|
||||
# blank_path = f"{print_factory_type_dir}/blank/blank_bias/blank_small.obj"
|
||||
print(f"blank_path={blank_path}")
|
||||
|
||||
load_show_save(base_path, {}, blank_path, batch_id, True)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
import random
|
||||
import matplotlib.pyplot as plt
|
||||
import open3d as o3d
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
# ply_read_path="/data/datasets_20t/type_setting_test_data/print_bounds_compact_data/88884_253283_P65951_6cm_x1=7.811+11.043+25.699.ply"
|
||||
# # 读取点云
|
||||
# pcd = o3d.io.read_point_cloud(ply_read_path)
|
||||
#
|
||||
# # 获取点云的点数据
|
||||
# points = np.asarray(pcd.points)
|
||||
#
|
||||
# # 计算质心
|
||||
# centroid = np.mean(points, axis=0)
|
||||
#
|
||||
# # 计算 Y 轴最小值
|
||||
# min_y_value = np.min(points[:, 1]) # Y 轴最小值
|
||||
# max_y_value = np.max(points[:, 1])
|
||||
#
|
||||
# # 计算 X 轴最小值
|
||||
# min_x_value = np.min(points[:, 0]) # X 轴最小值
|
||||
#
|
||||
# print(f'min_x_value{min_x_value}')
|
||||
# min_x_value -385.08287729332403
|
||||
#
|
||||
ply_read_path="/data/datasets_20t/type_setting_test_data/print_bounds_compact_data/456450_260316_P65976_2.66cm_x1=21.778+22.904+26.333.ply"
|
||||
# 读取点云
|
||||
pcd = o3d.io.read_point_cloud(ply_read_path)
|
||||
|
||||
# 获取点云的点数据
|
||||
points = np.asarray(pcd.points)
|
||||
|
||||
# 计算质心
|
||||
centroid = np.mean(points, axis=0)
|
||||
|
||||
# 计算 Y 轴最小值
|
||||
min_y_value = np.min(points[:, 1]) # Y 轴最小值
|
||||
max_y_value = np.max(points[:, 1])
|
||||
|
||||
# 计算 X 轴最小值
|
||||
min_x_value = np.min(points[:, 0]) # X 轴最小值
|
||||
|
||||
print(f'min_x_value{min_x_value}')
|
||||
# min_x_value -385.08287729332403
|
||||
print(f'min_y_value{min_y_value}')
|
||||
|
||||
# -339
|
||||
@@ -1,152 +0,0 @@
|
||||
# import numpy as np
|
||||
# def calculate_rotation_and_center_of_mass(angle_x, angle_y, angle_z, points):
|
||||
# """计算某一组旋转角度后的重心"""
|
||||
# # 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||
# R_x = np.array([
|
||||
# [1, 0, 0],
|
||||
# [0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||
# [0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||
# ])
|
||||
#
|
||||
# R_y = np.array([
|
||||
# [np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||
# [0, 1, 0],
|
||||
# [-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||
# ])
|
||||
#
|
||||
# R_z = np.array([
|
||||
# [np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||
# [np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||
# [0, 0, 1]
|
||||
# ])
|
||||
#
|
||||
# # 综合旋转矩阵
|
||||
# R = R_z @ R_y @ R_x
|
||||
#
|
||||
# # 执行旋转
|
||||
# rotated_points = points @ R.T
|
||||
#
|
||||
# # 计算最小z值
|
||||
# min_z = np.min(rotated_points[:, 2])
|
||||
#
|
||||
# # 计算平移向量,将最小Z值平移到0
|
||||
# translation_vector = np.array([0, 0, -min_z])
|
||||
# rotated_points += translation_vector
|
||||
#
|
||||
# # 计算重心
|
||||
# center_of_mass = np.mean(rotated_points, axis=0)
|
||||
#
|
||||
# return center_of_mass[2], angle_x, angle_y, angle_z
|
||||
#
|
||||
# def parallel_rotation(points, angle_step=3):
|
||||
# """顺序计算最优旋转角度(单线程)"""
|
||||
# min_center_of_mass_y = float('inf')
|
||||
# best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||
#
|
||||
# # 遍历所有角度组合
|
||||
# for angle_x in range(0, 360, angle_step):
|
||||
# for angle_y in range(0, 360, angle_step):
|
||||
# for angle_z in range(0, 360, angle_step):
|
||||
# center_of_mass_z, ax, ay, az = calculate_rotation_and_center_of_mass(
|
||||
# angle_x, angle_y, angle_z, points
|
||||
# )
|
||||
# if center_of_mass_z < min_center_of_mass_y:
|
||||
# min_center_of_mass_y = center_of_mass_z
|
||||
# best_angle_x, best_angle_y, best_angle_z = ax, ay, az
|
||||
#
|
||||
# return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
|
||||
|
||||
|
||||
import bpy
|
||||
import time
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def clear_scene():
|
||||
"""清空当前场景中的所有对象"""
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
bpy.ops.object.delete()
|
||||
|
||||
|
||||
def test_bpy_io(obj_path, output_path):
|
||||
"""测试 bpy 的 OBJ 读写性能"""
|
||||
# 读取 OBJ
|
||||
start_time = time.time()
|
||||
bpy.ops.import_scene.obj(filepath=obj_path)
|
||||
read_time = time.time() - start_time
|
||||
|
||||
# 确保场景中有对象
|
||||
if not bpy.context.scene.objects:
|
||||
raise ValueError("未成功导入 OBJ 文件")
|
||||
|
||||
# 写入 OBJ
|
||||
start_time = time.time()
|
||||
bpy.ops.export_scene.obj(
|
||||
filepath=output_path,
|
||||
use_selection=False, # 导出所有对象
|
||||
use_materials=False, # 不导出材质(加快速度)
|
||||
)
|
||||
write_time = time.time() - start_time
|
||||
|
||||
# 清理场景
|
||||
clear_scene()
|
||||
|
||||
return write_time, read_time
|
||||
|
||||
|
||||
def test_folder_objs_with_bpy(folder_path, output_folder="output_objs_bpy"):
|
||||
"""测试文件夹中所有 OBJ 文件的读写性能(使用 bpy)"""
|
||||
# 确保输出文件夹存在
|
||||
Path(output_folder).mkdir(exist_ok=True)
|
||||
|
||||
# 收集所有 OBJ 文件
|
||||
obj_files = [f for f in os.listdir(folder_path) if f.lower().endswith('.obj')]
|
||||
|
||||
if not obj_files:
|
||||
print(f"在文件夹 {folder_path} 中未找到 OBJ 文件")
|
||||
return
|
||||
|
||||
print(f"找到 {len(obj_files)} 个 OBJ 文件,开始测试...")
|
||||
|
||||
results = []
|
||||
|
||||
for obj_file in obj_files:
|
||||
input_path = os.path.join(folder_path, obj_file)
|
||||
output_path = os.path.join(output_folder, f"bpy_{obj_file}")
|
||||
|
||||
print(f"\n测试文件: {obj_file}")
|
||||
|
||||
try:
|
||||
write_time, read_time = test_bpy_io(input_path, output_path)
|
||||
file_size = os.path.getsize(input_path) / (1024 * 1024) # MB
|
||||
|
||||
print(f" 文件大小: {file_size:.2f} MB")
|
||||
print(f" bpy 读取时间: {read_time:.3f}s")
|
||||
print(f" bpy 写入时间: {write_time:.3f}s")
|
||||
|
||||
results.append({
|
||||
"filename": obj_file,
|
||||
"size_mb": file_size,
|
||||
"read_time": read_time,
|
||||
"write_time": write_time,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f" 处理 {obj_file} 时出错: {e}")
|
||||
|
||||
# 计算平均时间
|
||||
if results:
|
||||
avg_read = sum(r["read_time"] for r in results) / len(results)
|
||||
avg_write = sum(r["write_time"] for r in results) / len(results)
|
||||
print("\n=== 汇总结果 ===")
|
||||
print(f"平均读取时间: {avg_read:.3f}s")
|
||||
print(f"平均写入时间: {avg_write:.3f}s")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 设置包含OBJ文件的文件夹路径
|
||||
obj_folder = "/data/datasets_20t/9_big/" # 替换为你的OBJ文件夹路径
|
||||
|
||||
# 运行测试
|
||||
test_folder_objs_with_bpy(obj_folder)
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import os
|
||||
import time
|
||||
import open3d as o3d
|
||||
import trimesh
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_folder_objs(folder_path, output_folder="output_objs"):
|
||||
"""测试文件夹中所有OBJ文件的读写性能"""
|
||||
# 确保输出文件夹存在
|
||||
Path(output_folder).mkdir(exist_ok=True)
|
||||
|
||||
# 收集文件夹中所有OBJ文件
|
||||
obj_files = [f for f in os.listdir(folder_path) if f.lower().endswith('.obj')]
|
||||
|
||||
if not obj_files:
|
||||
print(f"在文件夹 {folder_path} 中未找到OBJ文件")
|
||||
return
|
||||
|
||||
print(f"找到 {len(obj_files)} 个OBJ文件,开始测试...")
|
||||
|
||||
# 准备结果记录
|
||||
results = []
|
||||
|
||||
for obj_file in obj_files:
|
||||
file_path = os.path.join(folder_path, obj_file)
|
||||
output_path = os.path.join(output_folder, obj_file)
|
||||
|
||||
print(f"\n测试文件: {obj_file}")
|
||||
|
||||
# 测试open3d
|
||||
o3d_write, o3d_read = test_open3d_io(file_path, output_path.replace('.obj', '_o3d.obj'))
|
||||
|
||||
# 测试trimesh
|
||||
tm_write, tm_read = test_trimesh_io(file_path, output_path.replace('.obj', '_tm.obj'))
|
||||
|
||||
# 记录结果
|
||||
file_stats = {
|
||||
'filename': obj_file,
|
||||
'o3d_write': o3d_write,
|
||||
'o3d_read': o3d_read,
|
||||
'tm_write': tm_write,
|
||||
'tm_read': tm_read,
|
||||
'write_ratio': o3d_write / tm_write if tm_write > 0 else 0,
|
||||
'read_ratio': o3d_read / tm_read if tm_read > 0 else 0
|
||||
}
|
||||
results.append(file_stats)
|
||||
|
||||
# 打印当前文件结果
|
||||
print(f" open3d | 写入: {o3d_write:.3f}s | 读取: {o3d_read:.3f}s")
|
||||
print(f" trimesh | 写入: {tm_write:.3f}s | 读取: {tm_read:.3f}s")
|
||||
print(f" 写入速度比(trimesh/open3d): {file_stats['write_ratio']:.1f}x")
|
||||
print(f" 读取速度比(trimesh/open3d): {file_stats['read_ratio']:.1f}x")
|
||||
|
||||
# 打印汇总结果
|
||||
print("\n=== 汇总结果 ===")
|
||||
avg_write_ratio = sum(r['write_ratio'] for r in results) / len(results)
|
||||
avg_read_ratio = sum(r['read_ratio'] for r in results) / len(results)
|
||||
print(f"平均写入速度比(trimesh/open3d): {avg_write_ratio:.1f}x")
|
||||
print(f"平均读取速度比(trimesh/open3d): {avg_read_ratio:.1f}x")
|
||||
|
||||
|
||||
def test_open3d_io(input_path, output_path):
|
||||
"""测试open3d的读写性能"""
|
||||
# 读取
|
||||
start = time.time()
|
||||
mesh = o3d.io.read_triangle_mesh(input_path)
|
||||
read_time = time.time() - start
|
||||
|
||||
# 写入
|
||||
start = time.time()
|
||||
o3d.io.write_triangle_mesh(output_path, mesh)
|
||||
write_time = time.time() - start
|
||||
|
||||
return write_time, read_time
|
||||
|
||||
|
||||
def test_trimesh_io(input_path, output_path):
|
||||
"""测试trimesh的读写性能"""
|
||||
# 读取
|
||||
start = time.time()
|
||||
mesh = trimesh.load(input_path)
|
||||
read_time = time.time() - start
|
||||
|
||||
# 写入
|
||||
start = time.time()
|
||||
mesh.export(output_path)
|
||||
write_time = time.time() - start
|
||||
|
||||
return write_time, read_time
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 设置包含OBJ文件的文件夹路径
|
||||
obj_folder = "/data/datasets_20t/9_big/" # 替换为你的OBJ文件夹路径
|
||||
|
||||
# 运行测试
|
||||
test_folder_objs(obj_folder)
|
||||
Reference in new issue
Block a user