Compare commits

..
18 Commits
Author SHA1 Message Date
hesuicong 9bf614a024 代码更新 2025-12-15 10:36:49 +08:00
hesuicong 0dc83ba187 代码更新 2025-12-15 10:33:13 +08:00
hesuicong 469155434d 代码更新 2025-12-15 09:57:16 +08:00
hesuicong 5f5fe44b6e 测试 2025-12-13 17:03:06 +08:00
hesuicong 07759efad3 代码更新 2025-12-12 17:58:33 +08:00
hesuicong 4bb714a3ed 代码整理 2025-12-12 17:56:38 +08:00
hesuicong 09d6c3106b 代码更新 2025-12-12 17:26:04 +08:00
hesuicong c93c9b630e 代码更新 2025-12-12 14:36:42 +08:00
hesuicong e05386b1d6 修改.gitignore 2025-12-12 14:07:47 +08:00
hesuicong 032a6b065b 更新 2025-12-12 11:54:33 +08:00
hesuicong 1c27269610 更改目录结构 2025-12-12 11:53:31 +08:00
hesuicong 1a96ae17e2 代码更新 2025-12-12 11:46:43 +08:00
hesuicong dcc9a27cc2 代码更新 2025-12-12 11:43:09 +08:00
hesuicong 1b0ecb2727 代码更新 2025-12-11 17:46:07 +08:00
hesuicong dac97348bb 代码更新 2025-12-11 17:17:21 +08:00
hesuicong b663c7b31c 代码更新 2025-12-11 17:16:51 +08:00
hesuicong 4e8ba476f5 代码更新 2025-12-11 16:28:00 +08:00
hesuicong 4dffb90613 代码更新 2025-12-11 15:01:04 +08:00
25 changed files with 1207 additions and 3440 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
__pycache__/
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.
+3 -43
View File
@@ -18,46 +18,6 @@ from config import print_data_dir
from general import is_run_local_data
def find_free_display():
"""
寻找一个可用的显示编号。
从 99 开始尝试,最大尝试到 199。
"""
display_num = 99
max_attempts = 100 # 最大尝试次数,避免无限循环
for attempt in range(max_attempts):
display = f":{display_num}"
lock_file = f"/tmp/.X{display_num}-lock"
# 检查锁文件是否存在
if os.path.exists(lock_file):
print(f"显示设备 {display} 被锁定,尝试下一个...")
display_num += 1
continue
# 尝试启动一个短暂的Xvfb进程来测试端口是否真的空闲
test_proc = subprocess.Popen(
["Xvfb", display, "-screen", "0", "1x1x1"],
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE
)
sleep(0.5) # 稍等片刻
# 如果进程很快结束了,通常意味着启动失败(可能是端口被占用或其他错误)
return_code = test_proc.poll()
if return_code is not None:
# 启动失败,尝试下一个端口
display_num += 1
test_proc.terminate()
continue
else:
# 启动成功!终止测试进程,并返回这个可用的显示编号
test_proc.terminate()
return display_num
raise RuntimeError(f"在尝试了 {max_attempts} 次后,未能找到可用的显示设备端口。")
# 如果没有 DISPLAY,自动启动 Xvfb
if "DISPLAY" not in os.environ:
import atexit
@@ -69,7 +29,7 @@ if "DISPLAY" not in os.environ:
os.environ["XDG_RUNTIME_DIR"] = runtime_dir
# 使用函数查找空闲的显示端口
free_display_num = find_free_display()
free_display_num = 99
display_env = f":{free_display_num}"
# 启动 Xvfb,使用找到的空闲端口
@@ -224,7 +184,7 @@ def clear_directory_recursive(dir_path):
# 如果是文件或符号链接,直接删除
try:
os.unlink(item_path)
print(f"已删除: {item_path}")
# print(f"已删除: {item_path}")
except Exception as e:
print(f"删除失败 {item_path}: {e}")
elif os.path.isdir(item_path):
@@ -332,7 +292,7 @@ def process_clound_print(data):
)
list_print_model_info.append(batch_model_info)
print("-" * 40)
# print("-" * 40)
workdir = f"{print_data_dir}{pre_batch_id}"
+75 -491
View File
@@ -3,354 +3,14 @@ import numpy as np
import copy
import time
import argparse
import os
from general import *
# ------------------------ 开始:对外接口,获取bbox数据 --------------------
"""
对外部提供的获取bbox数据的接口
compute_bbox_out
参数:
obj_path, 模型数据路径
返回:
total_matrix: 旋转矩阵, 16位浮点型, 例如, [[ 0.13644984 0.99064698 0. -49.71074343]
[ -0.99064698 0.13644984 0. -28.80249299]
[ 0. 0. 1. 3.26326203]
[ 0. 0. 0. 1. ]]
z_max : z最高点, 1位浮点型, 例如, 1.0
min_bound : bbox最低点, 3位浮点型, 例如, [-1.0, -1.0, 0.0]
max_bound : bbox最低点, 3位浮点型, 例如, [0.0, 0.0, 1.0]
ply_name : ply的名字, 字符串, 例如, 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply
"""
def compute_bbox_out(mesh_obj):
return compute_bbox_ext(mesh_obj)
# -------------------------- 结束:对外接口,获取bbox数据 ----------------
# -------------------------- 开始:获取z值最低 --------------------------
def get_lowest_position_of_z_ext(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 mesh_obj={mesh_obj}")
temp_matrix, z_max = get_lowest_position_of_z_ext(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值最低 --------------------------
from compute_print_net_out import get_volume_centroid
from compute_print_net_out import get_lowest_position_of_z_ext
from compute_print_net_out import get_lowest_position_of_z_ext
from compute_print_net_out import compute_bbox_ext
from compute_print_net_out import voxel_size
# -------------------------- 开始:bbox --------------------------
@@ -432,7 +92,7 @@ def compute_bbox_all(dict_mesh_obj,is_downsample):
dict_total_matrix[obj_name] = total_matrix
dict_pcd_fix[ply_name] = pcd_fix
print(f"compute_bbox obj_name={obj_name} ply_name={ply_name} time={time.time()-start_time}")
# print(f"compute_bbox obj_name={obj_name} ply_name={ply_name} time={time.time()-start_time}")
# dict_mesh_obj.clear()
# del dict_mesh_obj
@@ -441,12 +101,29 @@ def compute_bbox_all(dict_mesh_obj,is_downsample):
return dict_total_matrix,all_models
def compute_bbox(mesh_obj, obj_name="", is_downsample=True):
def compute_bbox(mesh_obj, obj_name, is_downsample=True):
# return compute_bbox_ext(mesh_obj, obj_name, is_downsample)
mesh_obj_origin = copy.deepcopy(mesh_obj)
total_matrix, z_max, min_bound, max_bound, ply_name = compute_bbox_ext(mesh_obj, obj_name, is_downsample)
if is_compute_bbox_net:
# 从网络上获取数据
printId = ""
match = re.search(r"P(\d+)", obj_name) # 匹配 "P" 后的连续数字
if match:
printId = match.group(1)
succ, total_matrix, z_max, min_bound, max_bound, ply_name = compute_bbox_net(printId)
if not succ :
total_matrix, z_max, min_bound, max_bound, ply_name = compute_bbox_ext(mesh_obj, obj_name, is_downsample)
print(f"compute_bbox_net fail {printId}")
else:
ply_name = f"{os.path.splitext(obj_name)[0]}={ply_name.split('=')[1]}"
print(f"compute_bbox_net succ {printId}")
# print(f"compute_bbox_net printId = {printId}, total_matrix = {total_matrix}, ply_name = {ply_name}")
else :
total_matrix, z_max, min_bound, max_bound, ply_name = compute_bbox_ext(mesh_obj, obj_name, is_downsample)
print(f"compute_bbox_ext ply_name = {ply_name}")
transformed_vertices = mesh_transform_by_matrix(np.asarray(mesh_obj_origin.vertices), total_matrix)
@@ -460,141 +137,36 @@ def compute_bbox(mesh_obj, obj_name="", is_downsample=True):
return total_matrix, pcd_fix, ply_name
def compute_bbox_ext(mesh_obj, obj_name="", is_downsample=True):
import requests
import ast
def compute_bbox_net(printId):
total_matrix = np.eye(4)
total_matrix, z_max= get_lowest_position_of_center_ext(mesh_obj, total_matrix)
url = f"{url_infoByPrintId}{printId}"
res = requests.get(url)
transformed_vertices = mesh_transform_by_matrix(np.asarray(mesh_obj.vertices), total_matrix)
datas = res.json()["data"]["layout"]
# print("datas=", datas)
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)
total_matrix, min_bound, max_bound, ply_name, pcd_fix = get_new_bbox(obj_transformed_second,obj_name,voxel_size,is_downsample,total_matrix)
del obj_transformed
del obj_transformed_second
if not "ply_name" in datas:
return False, np.eye(4), 0.0, np.zeros(3), np.zeros(3), ""
# return total_matrix, z_max, min_bound, max_bound, ply_name, pcd_fix
return total_matrix, z_max, min_bound, max_bound, 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)
homo_matrix_str = datas.get("homo_matrix")
# print("homo_matrix_str=", homo_matrix_str)
# 降采样与特征计算
pcd_downsampled = down_sample(pcd, voxel_size)
points = np.asarray(pcd_downsampled.points)
cov = np.cov(points.T)
try:
matrix_list = ast.literal_eval(homo_matrix_str) # 直接转换为二维列表
reconstructed_matrix = np.array(matrix_list, dtype=float)
# print("矩阵形状:", reconstructed_matrix.shape) # 应该是 (4, 4)
except (ValueError, SyntaxError) as e:
print(f"解析错误: {e}")
center = obj_transformed.get_center()
layout_z = datas.get("layout_z", 0)
max_bound = datas.get("max_bound",0)
min_bound = datas.get("min_bound",0)
ply_name = datas.get("ply_name",0)
# print(f"layout_z={layout_z}, max_bound={max_bound}, min_bound={min_bound}, ply_name={ply_name}, ")
# 特征分解与方向约束(关键修改点)
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,is_downsample,total_matrix):
# 计算点云的边界
points = np.asarray(obj_transformed_second.vertices)
min_bound = np.min(points, axis=0) # 获取点云的最小边界
max_bound = np.max(points, axis=0) # 获取点云的最大边界
# print(f"get_new_bbox1: min_bound={min_bound}, max_bound={max_bound}")
# 确保包围盒的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 is_downsample:
pcd_downsampled = down_sample(pcd, voxel_size, False)
pcd_fix = pcd_downsampled
else:
pcd_fix = pcd
min_bound = np.min(pcd.points, axis=0) # 获取点云的最小边界
max_bound = np.max(pcd.points, axis=0) # 获取点云的最大边界
# print(f"get_new_bbox2: min_bound={min_bound}, max_bound={max_bound}")
if (not obj_name == ""):
ply_print_pid = obj_name.replace(".obj","")
ply_name = f"{ply_print_pid}={z_length}+{y_length}+{x_length}.ply"
else:
ply_name = ""
return total_matrix, min_bound, max_bound, ply_name, pcd_fix
return True, reconstructed_matrix, layout_z, max_bound, min_bound, ply_name
class Platform:
def __init__(self, width, depth, height):
@@ -645,7 +217,7 @@ class Platform:
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}")
# print(f"can_place False 1 cross_border {x}, {y}, {z}, {model}, {self.width}, {self.depth}, {self.height}")
return False
# 碰撞检测(正确逻辑与间距处理)
@@ -660,7 +232,7 @@ class Platform:
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)
# 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
@@ -686,7 +258,7 @@ class Platform:
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'])
# print("model position1", model['name'], model['position'])
self.placed_models.append(model)
return True
@@ -698,7 +270,7 @@ class Platform:
else:
px = pre_px - pre_mx - extend_dist_model_x
model['first_line'] = False
print(model['name'], "px", px, pre_px, pre_mx)
# print(model['name'], "px", px, pre_px, pre_mx)
reach_limit_x = False
if self.first_line:
@@ -726,13 +298,13 @@ class Platform:
return False
model['position'] = (px, y, z)
print("model position2", model['name'], model['position'])
# 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)
# 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
@@ -744,7 +316,7 @@ class Platform:
return False
model['position'] = (px, y, z)
print("model position2", model['name'], model['position'])
# print("model position2", model['name'], model['position'])
self.placed_models.append(model)
return True
@@ -785,12 +357,24 @@ class Platform:
# -------------------------- 结束:bbox --------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--obj_path", type=str, required=True, help="batchobj_path_id")
args = parser.parse_args()
# -------------------------- 开始:oss --------------------------
obj_path = args.obj_path
max, z = get_lowest_position_of_z_ext(obj_path)
import yaml
import oss2
def get_oss_client(cfg_path):
with open(os.path.expanduser(cfg_path), "r") as config:
cfg = yaml.safe_load(config)
AccessKeyId_down = cfg["run"]["down"]["AccessKeyId"]
AccessKeySecret_down = cfg["run"]["down"]["AccessKeySecret"]
Endpoint_down = cfg["run"]["down"]["Endpoint"]
Bucket_down = cfg["run"]["down"]["Bucket"]
oss_client = oss2.Bucket(
oss2.Auth(AccessKeyId_down, AccessKeySecret_down), Endpoint_down, Bucket_down
)
return oss_client
# -------------------------- 结束:oss --------------------------
+594
View File
@@ -0,0 +1,594 @@
import open3d as o3d
import numpy as np
import copy
import time
import argparse
import os
# ------------------------ 开始:对外接口,获取bbox数据 --------------------
"""
对外部提供的获取bbox数据的接口
compute_bbox_out
参数:
obj_path: 模型数据路径
返回:
total_matrix: 旋转矩阵, 16位浮点型, 例如, [[ 0.13644984 0.99064698 0. -49.71074343]
[ -0.99064698 0.13644984 0. -28.80249299]
[ 0. 0. 1. 3.26326203]
[ 0. 0. 0. 1. ]]
z_max : z最高点, 1位浮点型, 例如, 1.0
min_bound : bbox最低点, 3位浮点型, 例如, [-1.0, -1.0, 0.0]
max_bound : bbox最低点, 3位浮点型, 例如, [0.0, 0.0, 1.0]
ply_name : ply的名字, 字符串, 例如, 857420_268473_P85240_5cm_x1=9.41+49.997+49.997.ply
"""
def compute_bbox_out(obj_path):
obj_name = os.path.basename(obj_path)
mesh_obj = read_mesh(obj_path)
return compute_bbox_ext(mesh_obj, obj_name)
# -------------------------- 结束:对外接口,获取bbox数据 ----------------
# -------------------------- 开始:获取z值最低 --------------------------
def get_lowest_position_of_z_ext(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])
volume_centroid = get_volume_centroid(pcd_transformed)
z_volume_center1 = volume_centroid[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)
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 mesh_obj={mesh_obj}")
temp_matrix, z_max = get_lowest_position_of_z_ext(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 compute_bbox_ext(mesh_obj, obj_name="", is_downsample=True):
total_matrix = np.eye(4)
total_matrix, z_max= 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)
total_matrix, min_bound, max_bound, ply_name, pcd_fix = get_new_bbox(obj_transformed_second,obj_name,voxel_size,is_downsample,total_matrix)
del obj_transformed
del obj_transformed_second
# return total_matrix, z_max, min_bound, max_bound, ply_name, pcd_fix
return total_matrix, z_max, min_bound, max_bound, 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)
points = np.asarray(pcd.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,is_downsample,total_matrix):
# 计算点云的边界
points = np.asarray(obj_transformed_second.vertices)
min_bound = np.min(points, axis=0) # 获取点云的最小边界
max_bound = np.max(points, axis=0) # 获取点云的最大边界
# print(f"get_new_bbox1: obj_name={obj_name}, min_bound={min_bound}, max_bound={max_bound}")
# 确保包围盒的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 is_downsample:
pcd_downsampled = down_sample(pcd, voxel_size, False)
pcd_fix = pcd_downsampled
else:
pcd_fix = pcd
min_bound = np.min(pcd.points, axis=0) # 获取点云的最小边界
max_bound = np.max(pcd.points, axis=0) # 获取点云的最大边界
# print(f"get_new_bbox2: min_bound={min_bound}, max_bound={max_bound}")
if (not obj_name == ""):
ply_print_pid = obj_name.replace(".obj","")
ply_name = f"{ply_print_pid}={z_length}+{y_length}+{x_length}.ply"
else:
ply_name = ""
return total_matrix, min_bound, max_bound, ply_name, pcd_fix
# -------------------------- 结束:bbox --------------------------
# -------------------------- 开始:general --------------------------
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 read_mesh(obj_path, enable_post_processing=False):
mesh_obj = o3d.io.read_triangle_mesh(obj_path, enable_post_processing)
return mesh_obj
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
del homogeneous_vertices
del transformed_homogeneous
return transformed_vertices
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)
voxel_size = 3
# -------------------------- 结束:general --------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--obj_path", type=str, required=True, help="obj 路径")
args = parser.parse_args()
obj_path = args.obj_path
total_matrix, z_max, min_bound, max_bound, ply_name = compute_bbox_out(obj_path)
print(f"total_matrix={total_matrix}, z_max={z_max}, min_bound={min_bound}, max_bound={max_bound}, ply_name={ply_name}")
+15 -2
View File
@@ -1,6 +1,8 @@
# -------------------------- 开始:运行模式 -------------------------------
version = "0.11.251212.1"
from enum import Enum
class run_mode(Enum):
formal = 1
@@ -24,11 +26,17 @@ redis_config = {
url_send_layout = 'https://mp.api.suwa3d.com/api/printTypeSettingOrder/printTypeSettingOrderSuccess'
url_get_info_by_printIds = "https://mp.api.suwa3d.com/api/printOrder/getInfoByPrintIds?print_ids="
url_get_oss_suffix_by_orderId = f"https://mp.api.suwa3d.com/api/order/getOssSuffixByOrderId?order_id="
url_get_info_by_print_batch_id = f"https://mp.api.suwa3d.com/api/printOrder/getInfoByPrintBatchId?batch_id="
# 获取单个模型的相转换信息
url_infoByPrintId = f"https://mp.api.suwa3d.com/api/printOrder/infoByPrintId?printId="
# -------------------------- 结束:网络配置 -------------------------------
# -------------------------- 开始:路径配置 -------------------------------
# print_factory_type_dir="/root/git/"
# print_factory_type_dir="/home/ecs-user/git/"
print_factory_type_dir = "/home/algo/Documents/print_factory_type/git"
print_data_dir = f"{print_factory_type_dir}/data/"
@@ -46,7 +54,12 @@ small_machine_size = [380, 345, 250]
# -------------------------- 开始:测试相关 -------------------------------
# local_data= {'machine_print_counts': 150, 'machine_type': 'big_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
# local_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}]}
local_data= {'machine_print_counts': 130, 'machine_type': 'big_machine', 'pre_batch_id': 10188, 'print_machine_id': 15, 'print_orders': [{'counts': 1, 'layout_z': 32.65071311811036, 'model_weight': 68.084, 'order_id': 926400, 'pid': 389256, 'print_id': 134168}, {'counts': 1, 'layout_z': 31.780598561603746, 'model_weight': 25.511, 'order_id': 926573, 'pid': 389465, 'print_id': 134170}, {'counts': 1, 'layout_z': 5.202356934547428, 'model_weight': 11.705, 'order_id': 925909, 'pid': 388682, 'print_id': 134178}, {'counts': 1, 'layout_z': 5.1447470188140905, 'model_weight': 12.186, 'order_id': 925910, 'pid': 388683, 'print_id': 134179}, {'counts': 1, 'layout_z': 5.013986945152283, 'model_weight': 11.364, 'order_id': 925932, 'pid': 388707, 'print_id': 134180}, {'counts': 1, 'layout_z': 5.020000934600831, 'model_weight': 11.396, 'order_id': 926770, 'pid': 389842, 'print_id': 134183}, {'counts': 1, 'layout_z': 40.05588230441501, 'model_weight': 60.578, 'order_id': 926776, 'pid': 389895, 'print_id': 134185}, {'counts': 1, 'layout_z': 46.23669371110479, 'model_weight': 56.196, 'order_id': 926778, 'pid': 389918, 'print_id': 134186}, {'counts': 1, 'layout_z': 24.822566986083984, 'model_weight': 31.522, 'order_id': 926268, 'pid': 389067, 'print_id': 134211}, {'counts': 1, 'layout_z': 25.085425667575343, 'model_weight': 31.077, 'order_id': 926267, 'pid': 389066, 'print_id': 134212}, {'counts': 1, 'layout_z': 18.154759649173364, 'model_weight': 6.256, 'order_id': 927059, 'pid': 390269, 'print_id': 134216}, {'counts': 1, 'layout_z': 24.2420189021486, 'model_weight': 14.472, 'order_id': 927065, 'pid': 390275, 'print_id': 134220}, {'counts': 1, 'layout_z': 16.757099114319693, 'model_weight': 5.335, 'order_id': 927078, 'pid': 390299, 'print_id': 134226}, {'counts': 1, 'layout_z': 18.796780948141503, 'model_weight': 7.295, 'order_id': 927079, 'pid': 390300, 'print_id': 134228}, {'counts': 1, 'layout_z': 17.920740666248633, 'model_weight': 7.869, 'order_id': 927082, 'pid': 390303, 'print_id': 134229}, {'counts': 1, 'layout_z': 16.320665859825066, 'model_weight': 4.73, 'order_id': 927081, 'pid': 390302, 'print_id': 134230}, {'counts': 1, 'layout_z': 23.250497020255544, 'model_weight': 15.567, 'order_id': 927100, 'pid': 390331, 'print_id': 134242}, {'counts': 1, 'layout_z': 26.276874511435977, 'model_weight': 19.813, 'order_id': 927103, 'pid': 390334, 'print_id': 134246}, {'counts': 1, 'layout_z': 17.08049576865031, 'model_weight': 5.858, 'order_id': 927111, 'pid': 390342, 'print_id': 134254}, {'counts': 1, 'layout_z': 19.196685578562278, 'model_weight': 6.465, 'order_id': 927117, 'pid': 390347, 'print_id': 134261}, {'counts': 1, 'layout_z': 26.507062216602122, 'model_weight': 14.287, 'order_id': 927116, 'pid': 390346, 'print_id': 134262}, {'counts': 1, 'layout_z': 16.440782827635232, 'model_weight': 5.493, 'order_id': 927118, 'pid': 390348, 'print_id': 134263}, {'counts': 1, 'layout_z': 41.42868301509347, 'model_weight': 68.274, 'order_id': 927122, 'pid': 390354, 'print_id': 134264}, {'counts': 1, 'layout_z': 15.705368176518945, 'model_weight': 4.423, 'order_id': 927119, 'pid': 390349, 'print_id': 134265}, {'counts': 1, 'layout_z': 16.363223697461702, 'model_weight': 5.634, 'order_id': 927120, 'pid': 390351, 'print_id': 134266}, {'counts': 1, 'layout_z': 24.653417686230497, 'model_weight': 17.851, 'order_id': 927121, 'pid': 390353, 'print_id': 134267}, {'counts': 1, 'layout_z': 24.327745611835496, 'model_weight': 28.23, 'order_id': 926040, 'pid': 388832, 'print_id': 134268}, {'counts': 1, 'layout_z': 27.301779030909188, 'model_weight': 23.926, 'order_id': 923281, 'pid': 385001, 'print_id': 134269}, {'counts': 1, 'layout_z': 25.869120288866796, 'model_weight': 32.329, 'order_id': 926077, 'pid': 388864, 'print_id': 134270}, {'counts': 1, 'layout_z': 36.37214874852985, 'model_weight': 65.744, 'order_id': 926086, 'pid': 388873, 'print_id': 134271}, {'counts': 1, 'layout_z': 37.44403689090713, 'model_weight': 80.67, 'order_id': 926097, 'pid': 388884, 'print_id': 134273}, {'counts': 1, 'layout_z': 27.890566642299536, 'model_weight': 22.715, 'order_id': 927127, 'pid': 390372, 'print_id': 134274}, {'counts': 1, 'layout_z': 17.392635113631542, 'model_weight': 5.297, 'order_id': 927128, 'pid': 390373, 'print_id': 134275}, {'counts': 1, 'layout_z': 26.139986312446986, 'model_weight': 15.312, 'order_id': 927129, 'pid': 390375, 'print_id': 134276}, {'counts': 1, 'layout_z': 17.491576609126483, 'model_weight': 6.495, 'order_id': 927130, 'pid': 390376, 'print_id': 134277}, {'counts': 1, 'layout_z': 15.57976295844962, 'model_weight': 5.443, 'order_id': 927131, 'pid': 390377, 'print_id': 134278}, {'counts': 1, 'layout_z': 16.006999969482422, 'model_weight': 4.683, 'order_id': 927132, 'pid': 390378, 'print_id': 134279}, {'counts': 1, 'layout_z': 18.770485206437296, 'model_weight': 6.114, 'order_id': 927137, 'pid': 390383, 'print_id': 134280}, {'counts': 1, 'layout_z': 21.480148458997267, 'model_weight': 9.586, 'order_id': 927133, 'pid': 390379, 'Line truncated
local_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},{'counts': 1, 'layout_z': 33.24255595077689, 'model_weight': 40.467, 'order_id': 935050, 'pid': 401243, 'print_id': 144072}, {'counts': 1, 'layout_z': 25.454279899597168, 'model_weight': 27.469, 'order_id': 935045, 'pid': 401238, 'print_id': 144294}, {'counts': 1, 'layout_z': 25.866534150187974, 'model_weight': 18.187, 'order_id': 934966, 'pid': 401158, 'print_id': 144299}]}
# local_data= {'machine_print_counts': 130, 'machine_type': 'big_machine', 'pre_batch_id': 9910188, 'print_machine_id': 15, 'print_orders': [{'counts': 1, 'layout_z': 32.65071311811036, 'model_weight': 68.084, 'order_id': 926400, 'pid': 389256, 'print_id': 134168}, {'counts': 1, 'layout_z': 31.780598561603746, 'model_weight': 25.511, 'order_id': 926573, 'pid': 389465, 'print_id': 134170}, {'counts': 1, 'layout_z': 5.202356934547428, 'model_weight': 11.705, 'order_id': 925909, 'pid': 388682, 'print_id': 134178}, {'counts': 1, 'layout_z': 5.1447470188140905, 'model_weight': 12.186, 'order_id': 925910, 'pid': 388683, 'print_id': 134179}, {'counts': 1, 'layout_z': 5.013986945152283, 'model_weight': 11.364, 'order_id': 925932, 'pid': 388707, 'print_id': 134180}, {'counts': 1, 'layout_z': 5.020000934600831, 'model_weight': 11.396, 'order_id': 926770, 'pid': 389842, 'print_id': 134183}, {'counts': 1, 'layout_z': 40.05588230441501, 'model_weight': 60.578, 'order_id': 926776, 'pid': 389895, 'print_id': 134185}, {'counts': 1, 'layout_z': 46.23669371110479, 'model_weight': 56.196, 'order_id': 926778, 'pid': 389918, 'print_id': 134186}, {'counts': 1, 'layout_z': 24.822566986083984, 'model_weight': 31.522, 'order_id': 926268, 'pid': 389067, 'print_id': 134211}, {'counts': 1, 'layout_z': 25.085425667575343, 'model_weight': 31.077, 'order_id': 926267, 'pid': 389066, 'print_id': 134212}, {'counts': 1, 'layout_z': 18.154759649173364, 'model_weight': 6.256, 'order_id': 927059, 'pid': 390269, 'print_id': 134216}, {'counts': 1, 'layout_z': 24.2420189021486, 'model_weight': 14.472, 'order_id': 927065, 'pid': 390275, 'print_id': 134220}, {'counts': 1, 'layout_z': 16.757099114319693, 'model_weight': 5.335, 'order_id': 927078, 'pid': 390299, 'print_id': 134226}, {'counts': 1, 'layout_z': 18.796780948141503, 'model_weight': 7.295, 'order_id': 927079, 'pid': 390300, 'print_id': 134228}, {'counts': 1, 'layout_z': 17.920740666248633, 'model_weight': 7.869, 'order_id': 927082, 'pid': 390303, 'print_id': 134229}, {'counts': 1, 'layout_z': 16.320665859825066, 'model_weight': 4.73, 'order_id': 927081, 'pid': 390302, 'print_id': 134230}, {'counts': 1, 'layout_z': 23.250497020255544, 'model_weight': 15.567, 'order_id': 927100, 'pid': 390331, 'print_id': 134242}, {'counts': 1, 'layout_z': 26.276874511435977, 'model_weight': 19.813, 'order_id': 927103, 'pid': 390334, 'print_id': 134246}, {'counts': 1, 'layout_z': 17.08049576865031, 'model_weight': 5.858, 'order_id': 927111, 'pid': 390342, 'print_id': 134254}, {'counts': 1, 'layout_z': 19.196685578562278, 'model_weight': 6.465, 'order_id': 927117, 'pid': 390347, 'print_id': 134261}, {'counts': 1, 'layout_z': 26.507062216602122, 'model_weight': 14.287, 'order_id': 927116, 'pid': 390346, 'print_id': 134262}, {'counts': 1, 'layout_z': 16.440782827635232, 'model_weight': 5.493, 'order_id': 927118, 'pid': 390348, 'print_id': 134263}, {'counts': 1, 'layout_z': 41.42868301509347, 'model_weight': 68.274, 'order_id': 927122, 'pid': 390354, 'print_id': 134264}, {'counts': 1, 'layout_z': 15.705368176518945, 'model_weight': 4.423, 'order_id': 927119, 'pid': 390349, 'print_id': 134265}, {'counts': 1, 'layout_z': 16.363223697461702, 'model_weight': 5.634, 'order_id': 927120, 'pid': 390351, 'print_id': 134266}, {'counts': 1, 'layout_z': 24.653417686230497, 'model_weight': 17.851, 'order_id': 927121, 'pid': 390353, 'print_id': 134267}, {'counts': 1, 'layout_z': 24.327745611835496, 'model_weight': 28.23, 'order_id': 926040, 'pid': 388832, 'print_id': 134268}, {'counts': 1, 'layout_z': 27.301779030909188, 'model_weight': 23.926, 'order_id': 923281, 'pid': 385001, 'print_id': 134269}, {'counts': 1, 'layout_z': 25.869120288866796, 'model_weight': 32.329, 'order_id': 926077, 'pid': 388864, 'print_id': 134270}, {'counts': 1, 'layout_z': 36.37214874852985, 'model_weight': 65.744, 'order_id': 926086, 'pid': 388873, 'print_id': 134271}, {'counts': 1, 'layout_z': 37.44403689090713, 'model_weight': 80.67, 'order_id': 926097, 'pid': 388884, 'print_id': 134273}, {'counts': 1, 'layout_z': 27.890566642299536, 'model_weight': 22.715, 'order_id': 927127, 'pid': 390372, 'print_id': 134274}, {'counts': 1, 'layout_z': 17.392635113631542, 'model_weight': 5.297, 'order_id': 927128, 'pid': 390373, 'print_id': 134275}, {'counts': 1, 'layout_z': 26.139986312446986, 'model_weight': 15.312, 'order_id': 927129, 'pid': 390375, 'print_id': 134276}, {'counts': 1, 'layout_z': 17.491576609126483, 'model_weight': 6.495, 'order_id': 927130, 'pid': 390376, 'print_id': 134277}, {'counts': 1, 'layout_z': 15.57976295844962, 'model_weight': 5.443, 'order_id': 927131, 'pid': 390377, 'print_id': 134278}, {'counts': 1, 'layout_z': 16.006999969482422, 'model_weight': 4.683, 'order_id': 927132, 'pid': 390378, 'print_id': 134279}, {'counts': 1, 'layout_z': 18.770485206437296, 'model_weight': 6.114, 'order_id': 927137, 'pid': 390383, 'print_id': 134280}, {'counts': 1, 'layout_z': 21.480148458997267, 'model_weight': 9.586, 'order_id': 927133, 'pid': 39037Line truncated
# local_data= {'machine_print_counts': 60, 'machine_type': 'small_machine', 'pre_batch_id': 9910284, 'print_machine_id': 1, 'print_orders': [{'counts': 1, 'layout_z': 6.981930971145635, 'model_weight': 26.776, 'order_id': 929174, 'pid': 393697, 'print_id': 138265}, {'counts': 1, 'layout_z': 4.955404043197634, 'model_weight': 9.22, 'order_id': 931997, 'pid': 397013, 'print_id': 138846}, {'counts': 1, 'layout_z': 4.771067023277285, 'model_weight': 11.786, 'order_id': 932000, 'pid': 397016, 'print_id': 138851}, {'counts': 1, 'layout_z': 5.249161005020142, 'model_weight': 10.024, 'order_id': 932003, 'pid': 397019, 'print_id': 138854}, {'counts': 1, 'layout_z': 5.251940011978151, 'model_weight': 10.104, 'order_id': 932005, 'pid': 397021, 'print_id': 138856}, {'counts': 1, 'layout_z': 5.263678073883059, 'model_weight': 10.688, 'order_id': 932009, 'pid': 397025, 'print_id': 138875}, {'counts': 1, 'layout_z': 23.813366889953613, 'model_weight': 24.641, 'order_id': 929102, 'pid': 393961, 'print_id': 139071}, {'counts': 1, 'layout_z': 4.554790019989014, 'model_weight': 11.507, 'order_id': 933586, 'pid': 399522, 'print_id': 140121}, {'counts': 1, 'layout_z': 4.795650005340576, 'model_weight': 11.042, 'order_id': 933568, 'pid': 399331, 'print_id': 140150}, {'counts': 1, 'layout_z': 18.085672736889748, 'model_weight': 7.277, 'order_id': 933876, 'pid': 399901, 'print_id': 140261}, {'counts': 1, 'layout_z': 19.534520236131655, 'model_weight': 6.937, 'order_id': 933878, 'pid': 399903, 'print_id': 140264}, {'counts': 1, 'layout_z': 19.559980709635155, 'model_weight': 7.79, 'order_id': 933877, 'pid': 399902, 'print_id': 140265}, {'counts': 1, 'layout_z': 16.488399505615234, 'model_weight': 5.349, 'order_id': 933882, 'pid': 399907, 'print_id': 140271}, {'counts': 1, 'layout_z': 18.02717763893993, 'model_weight': 5.892, 'order_id': 933885, 'pid': 399910, 'print_id': 140277}, {'counts': 1, 'layout_z': 26.876644549545638, 'model_weight': 21.486, 'order_id': 933886, 'pid': 399911, 'print_id': 140278}, {'counts': 1, 'layout_z': 28.074640440717047, 'model_weight': 16.17, 'order_id': 933887, 'pid': 399912, 'print_id': 140280}, {'counts': 1, 'layout_z': 27.306154248118705, 'model_weight': 20.977, 'order_id': 933889, 'pid': 399927, 'print_id': 140281}, {'counts': 1, 'layout_z': 26.094746051465723, 'model_weight': 34.197, 'order_id': 933888, 'pid': 399926, 'print_id': 140282}, {'counts': 1, 'layout_z': 15.87796963527322, 'model_weight': 4.735, 'order_id': 933892, 'pid': 399930, 'print_id': 140287}, {'counts': 1, 'layout_z': 18.191209446277444, 'model_weight': 5.246, 'order_id': 933893, 'pid': 399931, 'print_id': 140288}, {'counts': 1, 'layout_z': 17.777126572389776, 'model_weight': 6.334, 'order_id': 933894, 'pid': 399932, 'print_id': 140289}, {'counts': 1, 'layout_z': 17.58385879418167, 'model_weight': 5.8, 'order_id': 933895, 'pid': 399933, 'print_id': 140290}, {'counts': 1, 'layout_z': 18.593739251800994, 'model_weight': 6.955, 'order_id': 933897, 'pid': 399935, 'print_id': 140293}, {'counts': 1, 'layout_z': 14.231568581173965, 'model_weight': 3.705, 'order_id': 933898, 'pid': 399936, 'print_id': 140294}, {'counts': 1, 'layout_z': 13.998784146949038, 'model_weight': 3.533, 'order_id': 933899, 'pid': 399937, 'print_id': 140295}, {'counts': 1, 'layout_z': 15.9964307861582, 'model_weight': 4.301, 'order_id': 933901, 'pid': 399939, 'print_id': 140297}, {'counts': 1, 'layout_z': 16.18931739014667, 'model_weight': 5.019, 'order_id': 933903, 'pid': 399941, 'print_id': 140299}, {'counts': 1, 'layout_z': 24.79611153168641, 'model_weight': 20.601, 'order_id': 933905, 'pid': 399943, 'print_id': 140301}, {'counts': 1, 'layout_z': 24.306803898330127, 'model_weight': 16.784, 'order_id': 933906, 'pid': 399944, 'print_id': 140302}, {'counts': 1, 'layout_z': 29.3158714015686, 'model_weight': 19.351, 'order_id': 933907, 'pid': 399945, 'print_id': 140303}, {'counts': 1, 'layout_z': 17.005620048049607, 'model_weight': 5.169, 'order_id': 933910, 'pid': 399948, 'print_id': 140305}, {'counts': 1, 'layout_z': 16.464274211211865, 'model_weight': 4.866, 'order_id': 933909, 'pid': 399947, 'print_id': 140306}, {'counts': 1, 'layout_z': 23.198866489386088, 'model_weight': 10.524, 'order_id': 933911, 'pid': 399949, 'print_id': 140307}, {'counts': 1, 'layout_z': 26.516445616407367, 'model_weight': 23.957, 'order_id': 933912, 'pid': 399950, 'print_id': 140308}, {'counts': 1, 'layout_z': 17.937848519147412, 'model_weight': 5.282, 'order_id': 933914, 'pid': 399952, 'print_id': 140310}, {'counts': 1, 'layout_z': 26.754205819816015, 'model_weight': 20.607, 'order_id': 933921, 'pid': 399957, 'print_id': 140315}, {'counts': 1, 'layout_z': 20.002697843362704, 'model_weight': 7.63, 'order_id': 933924, 'pid': 399960, 'print_id': 140318}, {'counts': 1, 'layout_z': 30.231639651233593, 'model_weight': 24.508, 'order_id': 933925, 'pid': 399961, 'print_id': 140319}, {'counts': 1, 'layout_z': 16.061075531610975, 'model_weight': 4.195, 'order_id': 933928, 'pid': 399964, 'print_iLine truncated
test_print_max = 0
is_compute_bbox_net = True
# -------------------------- 结束:测试相关 -------------------------------
+37 -1386
View File
File diff suppressed because it is too large. Load diff
+247 -78
View File
@@ -5,16 +5,25 @@ import os
import numpy as np
import os
import argparse
import bpy
# import bpy
import sys
import open3d as o3d
from pathlib import Path
import requests
import json
import shutil
def download_transform_save_by_json(pid_file, workdir, oss_config):
layout_data = download_datas_by_json(pid_file, workdir, oss_config)
from config import url_get_oss_suffix_by_orderId
from general import transform_save_bpy
from general import read_models_from_json
from compute_print_net import get_oss_client
def download_transform_save_by_json(json_name, workdir, oss_config):
layout_data = download_datas_by_json(json_name, workdir, oss_config)
original_obj_pid_dir = workdir
transform_save_bpy(layout_data, original_obj_pid_dir)
@@ -31,6 +40,46 @@ class DataTransfer:
self.local_path = local_path
self.oss_path = oss_path.lstrip('/')
self.oss_client = oss_client
# self.description = description
# @log_execution(self.description)
def download_data(self):
"""
从 OSS 下载数据到本地,保持原有目录结构
"""
# 列出所有对象
objects = []
prefix = self.oss_path.lstrip('/') # 移除开头的 '/' 以匹配 OSS 格式
for obj in oss2.ObjectIterator(self.oss_client, prefix=prefix):
if obj.key != prefix: # 跳过目录本身
objects.append(obj.key)
# 下载所有文件,添加进度条
for obj_key in tqdm(objects, desc="下载进度"):
if obj_key.endswith('/'):
continue
if "printId" in obj_key:
continue
# 计算相对路径
rel_path = obj_key[len(prefix):].lstrip('/')
# 构建本地完整路径
local_path = os.path.join(self.local_path, rel_path)
# 创建必要的目录
os.makedirs(os.path.dirname(local_path), exist_ok=True)
# 下载文件
self.oss_client.get_object_to_file(obj_key, local_path)
print("download_data local_path=" + local_path)
order_id: str
pid: str
model_height: str
def download_data_rename_json(self, json_model_info):
"""
@@ -134,7 +183,7 @@ class DataTransfer:
new_name = f"{json_model_info.order_id}_{old_name}"
lines[i] = f"map_Kd {new_name}"
mtllib_modified = True
print(f"已更新材质库引用: {old_name} -> {new_name}")
# print(f"已更新材质库引用: {old_name} -> {new_name}")
break # 找到第一个后立即退出
# 批量写入,减少I/O操作 [6](@ref)
@@ -148,6 +197,197 @@ class DataTransfer:
print(f"下载文件: {obj_key} -> {local_path}")
def download_data_rename_batch(self, batch_model_info):
"""
从 OSS 下载数据到本地,保持原有目录结构
"""
# 列出所有对象
objects = []
prefix = self.oss_path.lstrip('/') # 移除开头的 '/' 以匹配 OSS 格式
prefix_exists = False
for obj in oss2.ObjectIterator(self.oss_client, prefix=prefix):
prefix_exists = True
if obj.key != prefix: # 跳过目录本身
objects.append(obj.key)
# print(f"obj.key={obj.key}")
if not prefix_exists:
print(f"前缀 '{prefix}' 下没有找到任何文件或目录。")
return False
else:
print(f"前缀 '{prefix}' 存在,共找到 {len(objects)} 个对象。")
# 下载所有文件,添加进度条
for obj_key in tqdm(objects, desc="下载进度"):
if obj_key.endswith('/'):
# print("下载 endswith('/'")
continue
if "printId" in obj_key:
# print(f"下载 in obj_key")
continue
# 计算相对路径
rel_path = obj_key[len(prefix):].lstrip('/')
file_dir, file_name = os.path.split(rel_path)
file_base, file_ext = os.path.splitext(file_name)
# 根据文件后缀名进行重命名
if file_ext.lower() in ['.mtl', '.jpg', '.jpeg', '.png']:
# 对于.mtl和图片文件,在原名前加order_id
new_file_name = f"{batch_model_info.order_id}_{file_name}"
# new_file_name = file_name
elif file_ext.lower() == '.obj':
# 对于.obj文件,完全重命名
new_file_name = f"{batch_model_info.order_id}_{batch_model_info.pid}_P{batch_model_info.print_order_id}_{batch_model_info.model_size}{file_ext}"
else:
# 其他文件类型保持原名
new_file_name = file_name
# 构建新的相对路径
if file_dir: # 如果有子目录
new_rel_path = os.path.join(file_dir, new_file_name)
else:
new_rel_path = new_file_name
# 构建本地完整路径
local_path = os.path.join(self.local_path, new_rel_path)
# 创建必要的目录
os.makedirs(os.path.dirname(local_path), exist_ok=True)
# 下载文件
self.oss_client.get_object_to_file(obj_key, local_path)
if file_ext == '.obj': # 10MB以上
try:
# 使用临时文件避免内存问题 [8](@ref)
temp_path = local_path + '.tmp'
with open(local_path, 'r', encoding='utf-8') as f_in, \
open(temp_path, 'w', encoding='utf-8') as f_out:
mtllib_modified = False
for line in f_in:
if not mtllib_modified and line.strip().startswith('mtllib '):
parts = line.split(' ', 1)
if len(parts) > 1:
old_mtl_name = parts[1].strip()
new_mtl_name = f"{batch_model_info.order_id}_{old_mtl_name}"
f_out.write(f"mtllib {new_mtl_name}\n")
mtllib_modified = True
print("len(parts) > 1")
continue
f_out.write(line)
os.replace(temp_path, local_path) # 原子性替换
except IOError as e:
print(f"处理大文件 {local_path} 时出错: {e}")
if os.path.exists(temp_path):
os.remove(temp_path)
# 优化后的.obj文件处理逻辑
if file_ext == '.mtl':
try:
# 使用更高效的文件读取方式 [6,8](@ref)
with open(local_path, 'r', encoding='utf-8') as f:
content = f.read()
# 使用字符串方法直接查找和替换,避免不必要的循环 [9](@ref)
lines = content.split('\n')
mtllib_modified = False
for i, line in enumerate(lines):
stripped_line = line.strip()
if not mtllib_modified and stripped_line.startswith('map_Kd '):
# 更高效的分割方式 [9](@ref)
parts = line.split(' ', 1)
if len(parts) > 1:
old_name = parts[1].strip()
new_name = f"{batch_model_info.order_id}_{old_name}"
lines[i] = f"map_Kd {new_name}"
mtllib_modified = True
# print(f"已更新材质库引用: {old_name} -> {new_name}")
break # 找到第一个后立即退出
# 批量写入,减少I/O操作 [6](@ref)
with open(local_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(lines))
except IOError as e:
print(f"处理文件 {local_path} 时出错: {e}")
except UnicodeDecodeError as e:
print(f"文件编码错误 {local_path}: {e}")
print(f"下载文件: {obj_key} -> {local_path}")
return True
def download_single_file(self):
"""
下载单个文件从OSS到本地
"""
# 确保本地目录存在
os.makedirs(os.path.dirname(self.local_path), exist_ok=True)
# 直接下载文件
try:
self.oss_client.get_object_to_file(self.oss_path, self.local_path)
print(f"文件已下载到: {self.local_path}")
except oss2.exceptions.NoSuchKey:
print(f"OSS文件不存在: {self.oss_path}")
def upload_data(self):
'''
上传数据到OSS
'''
# 检测本地路径是否存在
if not os.path.exists(self.local_path):
raise FileNotFoundError(f"本地路径不存在: {self.local_path}")
# 判断本地路径是文件还是目录
if os.path.isfile(self.local_path):
local_suffix = Path(self.local_path).suffix
oss_suffix = Path(self.oss_path).suffix
if oss_suffix and oss_suffix != local_suffix:
# 后缀名不一致,上传到指定文件夹下的同名文件
oss_dir = os.path.dirname(self.oss_path)
oss_target_path = os.path.join(oss_dir, os.path.basename(self.local_path))
else:
# 后缀名一致,上传到指定OSS路径
oss_target_path = self.oss_path
# 上传文件
self.oss_client.put_object_from_file(oss_target_path, self.local_path)
print(f"文件已上传到: {oss_target_path}")
elif os.path.isdir(self.local_path):
oss_suffix = Path(self.oss_path).suffix
if oss_suffix:
raise ValueError("不能将目录上传到具有后缀名的OSS路径。")
# 遍历本地目录并上传
for root, dirs, files in os.walk(self.local_path):
for file in files:
local_file_path = os.path.join(root, file)
relative_path = os.path.relpath(local_file_path, self.local_path)
oss_file_path = os.path.join(self.oss_path, relative_path).replace("\\", "/")
# 创建必要的目录
oss_dir = os.path.dirname(oss_file_path)
# 上传文件
self.oss_client.put_object_from_file(oss_file_path, local_file_path)
print(f"文件已上传到: {oss_file_path}")
else:
raise ValueError(f"无效的本地路径类型: {self.local_path}")
def get_api(url):
try:
response = requests.get(url)
@@ -160,68 +400,13 @@ def get_api(url):
except requests.exceptions.RequestException as e:
raise Exception(f"Error fetching URL {url}: {e}")
from dataclasses import dataclass
@dataclass
class JSONModelInfo:
obj_name: str
order_id: str
pid: str
print_order_id: str
model_height: str
def read_pids_from_json(pid_file):
"""从文件读取所有PID"""
json_path = pid_file
"""
加载JSON文件,读取所有模型信息,应用变换后返回模型列表
"""
# 检查JSON文件是否存在
if not os.path.exists(json_path):
print(f"错误: JSON文件不存在 - {json_path}")
return []
# 读取JSON文件
try:
with open(json_path, 'r') as f:
data = json.load(f)
except Exception as e:
print(f"读取JSON文件失败: {e}")
return []
list_model_info = []
# 处理每个模型
for model in data.get('models', []):
obj_name = model.get('file_name', '')
parts = obj_name.split('_')
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
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}"
url = f"https://mp.api.suwa3d.com/api/order/getOssSuffixByOrderId?order_id={model_info.order_id}"
url = f"{url_get_oss_suffix_by_orderId}{model_info.order_id}"
res = requests.get(url)
data = res.json()["data"]
@@ -239,37 +424,21 @@ def download_data_by_json(model_info, workdir, oss_client ):
except Exception as e:
print(f"下载失败: {pid}, 错误: {str(e)}")
pass
def get_oss_client(cfg_path):
with open(os.path.expanduser(cfg_path), "r") as config:
cfg = yaml.safe_load(config)
AccessKeyId_down = cfg["run"]["down"]["AccessKeyId"]
AccessKeySecret_down = cfg["run"]["down"]["AccessKeySecret"]
Endpoint_down = cfg["run"]["down"]["Endpoint"]
Bucket_down = cfg["run"]["down"]["Bucket"]
oss_client = oss2.Bucket(
oss2.Auth(AccessKeyId_down, AccessKeySecret_down), Endpoint_down, Bucket_down
)
return oss_client
def download_datas_by_json(pid_file, workdir, oss_config):
oss_client = get_oss_client(oss_config)
# json_path = os.path.join(workdir, "3DPrintLayout.json")
json_path = os.path.join(workdir, f"{pid_file}.json")
# 读取所有PID
list_model_info, data = read_pids_from_json(json_path)
list_model_info, json_data = read_models_from_json(json_path)
print(f"从文件读取了 {len(list_model_info)} 个PID")
# 批量下载
for model_info in list_model_info:
print(f"开始下载PID: {model_info}")
download_data_by_json(model_info, args.workdir, oss_client)
return data
return json_data
if __name__ == "__main__":
+99 -85
View File
@@ -3,6 +3,13 @@ import numpy as np
import re
from config import *
from compute_print_net_out import mesh_transform_by_matrix
from compute_print_net_out import down_sample
from compute_print_net_out import voxel_size
from compute_print_net_out import read_mesh
import shutil
# -------------------------- 开始:运行 ----------------------------------
def is_run_local_data():
@@ -23,64 +30,6 @@ def is_use_debug_oss():
# -------------------------- 结束:运行 ----------------------------------
# -------------------------- 开始:模型 ----------------------------------
def read_mesh(obj_path, enable_post_processing=False):
mesh_obj = o3d.io.read_triangle_mesh(obj_path, enable_post_processing)
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)
@@ -101,29 +50,6 @@ def mesh_to_pcd(mesh, is_down_sample=True):
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
del homogeneous_vertices
del transformed_homogeneous
return transformed_vertices
import os
@@ -240,8 +166,6 @@ def transform_save_o3d(layout_data, original_obj_pid_dir):
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)
@@ -258,7 +182,7 @@ 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}")
# print(f"pre_name1={pre_name1}, pre_name2={pre_name2}")
if (pre_name1==pre_name2):
return True
@@ -271,12 +195,34 @@ def get_blank_path(is_small_machine=False):
else:
return os.path.join(print_factory_type_dir, "print_setting/blank/blank_big.obj")
def copy_obj_2x(base_obj_dir):
obj_list = [aa for aa in os.listdir(base_obj_dir) if aa.endswith(".obj")]
for obj_name in obj_list:
if "_F" in obj_name:
continue
obj_count = obj_name.split("x")[-1].replace(".obj","")
if not obj_count.isnumeric():
match = re.search(r"x(\d+)", obj_name)
if match:
obj_count = match.group(1)
else:
print("未找到 x 后的数字")
obj_count_num = int(obj_count)
if obj_count_num!=1:
for i in range(obj_count_num-1):
origin_path = os.path.join(base_obj_dir,obj_name)
dis_path = os.path.join(base_obj_dir,obj_name.replace(".obj",f"_F{i+1}.obj"))
if not os.path.exists(dis_path):
shutil.copy(origin_path,dis_path)
print(dis_path,"复制成功")
# -------------------------- 结束:模型 ----------------------------------
# -------------------------- 开始:碰撞检测和越界 --------------------------
extend_dist_border_x_min = 6
extend_dist_border_x_max = 3
extend_dist_border_x_max = 6
extend_dist_border_z_max = 3
extend_dist_border_y_min = 6
extend_dist_border_y_max = 6
@@ -284,6 +230,8 @@ extend_dist_model_x = 4
extend_dist_model_y = 2
extend_dist_model_z = 2
extend_dist_box = 1
extend_dist_min_collision = 3
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
@@ -294,3 +242,69 @@ def is_cross_border_c(x, y, z, mx, my, mz, max_x, max_y, max_z):
return False
# -------------------------- 结束:碰撞检测和越界 --------------------------
# -------------------------- 开始:JSON --------------------------
import json
from dataclasses import dataclass
@dataclass
class JSONModelInfo:
obj_name: str
order_id: str
pid: str
print_order_id: str
model_height: str
def read_from_json(json_path):
if not os.path.exists(json_path):
print(f"错误: JSON文件不存在 - {json_path}")
return []
try:
with open(json_path, 'r') as f:
json_data = json.load(f)
except Exception as e:
print(f"读取JSON文件失败: {e}")
return []
return json_data
def read_models_from_json(json_path):
json_data = read_from_json(json_path)
list_model_info = []
# 处理每个模型
for model in json_data.get('models', []):
obj_name = model.get('file_name', '')
parts = obj_name.split('_')
order_id = parts[0]
pid = parts[1]
model_height = parts[3]
model_info = JSONModelInfo(
obj_name=obj_name,
order_id=order_id,
pid=pid,
model_height=model_height
)
list_model_info.append(model_info)
return list_model_info, json_data
def get_summary(json_data):
return json_data.get('summary')
def get_selected_machine(json_data):
return get_summary(json_data)['selected_machine']
def is_small_machine(json_data):
selected_machine = get_selected_machine(json_data)
is_small_machine = True if selected_machine=="小机型" else False
return is_small_machine
# -------------------------- 结束:JSON --------------------------
+61 -383
View File
@@ -9,18 +9,19 @@ import open3d as o3d
import numpy as np
from plyfile import PlyData, PlyElement
from config import print_factory_type_dir
from general import mesh_tranform_to_pcd
from general import need_upload_result
from general import read_mesh
from general import extend_dist_min_collision
from general import extend_dist_border_x_min
from general import extend_dist_border_x_max
from general import extend_dist_border_y_min
from general import extend_dist_border_y_max
from compute_print_net import get_models_bbox
from compute_print_net import arrange_models_on_platform
from compute_print_net import Platform
from compute_print_net import down_sample
from compute_print_net import compute_bbox_all
from compute_print_net import compute_bbox_all_ext
from compute_print_net_out import read_mesh
def make_bbox_for_print(base_original_obj_dir,dict_bad,dict_origin,is_downsample):
"""获取需要的盒子大小"""
@@ -66,6 +67,7 @@ def get_dict_pcd(dict_mesh_obj,dict_total_matrix,all_models):
dict_ply_name = {}
for model in all_models:
ply_name = model['name']
# print(f"get_dict_pcd {ply_name.split("=")[0]}.obj, {ply_name}")
dict_ply_name[f"{ply_name.split("=")[0]}.obj"] = ply_name
dict_pcd_fix = get_pcd_by_matrix(dict_mesh_obj,dict_total_matrix,dict_ply_name)
@@ -87,7 +89,6 @@ def get_pcd_by_matrix(dict_mesh_obj,dict_total_matrix,dict_ply_name):
return dict_pcd_fix
def ply_print_layout_platform(dict_pcd_fix,dict_pcd_fix2,machine_size,dict_total_matrix,all_models):
"""根据排版结果移动点云到指定位置"""
# placed_models,unplaced_models = get_models_box_size(dict_fix,machine_size)
@@ -144,32 +145,6 @@ def ply_print_layout_platform(dict_pcd_fix,dict_pcd_fix2,machine_size,dict_total
return placed_models
def get_models_bbox_net(dict_pcd_fix):
return get_models_bbox(dict_pcd_fix)
"""
def get_models_box_size(dict_fix,machine_size):
#获取排版的盒子大小
models = []
for ply_file in dict_fix:
bbox_with_text = ply_file.split("=")
bbox_with = bbox_with_text[-1]
split_text = bbox_with.replace(".ply","").split("+")
extend_dist = 2
x_length = int(float(split_text[2])*100) + extend_dist
y_length = int(float(split_text[0])*100) + extend_dist
z_length = int(float(split_text[1])*100) + extend_dist
#print("get_models_box_size",x_length,y_length,z_length)
models.append({'name':ply_file,'dimensions':(int(x_length/100),int(z_length/100),int(y_length/100))})
print(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()
"""
def compute_distance(pcd1, pcd2):
"""
正确计算两个点云之间距离的函数。
@@ -187,44 +162,6 @@ def compute_distance(pcd1, pcd2):
# return min_dist, mean_dist, distances
return min_dist
def compute_distance_x(pcd1, pcd2):
points1 = np.asarray(pcd1.points)[:, 0] # 提取所有X坐标[3](@ref)
points2 = np.asarray(pcd2.points)[:, 0]
x_diff = np.abs(points1[:, np.newaxis] - points2)
return np.min(x_diff)
def compute_distance_y(pcd1, pcd2):
points1 = np.asarray(pcd1.points)[:, 1] # 提取所有Y坐标[3](@ref)
points2 = np.asarray(pcd2.points)[:, 1]
y_diff = np.abs(points1.reshape(-1, 1) - points2)
return np.min(y_diff)
def check_collision_x(pcd_moving, static_pcds,collision_threshold):
moving_points = np.asarray(pcd_moving.points)
min_distance_to_x_axis = np.min(np.abs(moving_points[:, 1])) # Y 坐标即为与 X 轴的距离
#print(f"与 X 轴的最小距离: {min_distance_to_x_axis}")
#print(f"与 Y 轴的最小距离: {min_distance_to_y_axis}")
#print(f"pcd_moving{len(static_pcds)}")
if min_distance_to_x_axis < collision_threshold:
print(f"与 X 轴发生碰撞! 最小距离: {min_distance_to_x_axis}")
return True
return check_collision_all(pcd_moving, static_pcds,collision_threshold)
def check_collision_y(pcd_moving, static_pcds,collision_threshold):
moving_points = np.asarray(pcd_moving.points)
#print(f"与 X 轴的最小距离: {min_distance_to_x_axis}")
min_distance_to_y_axis = np.min(np.abs(moving_points[:, 0])) # X 坐标即为与 Y 轴的距离
#print(f"与 Y 轴的最小距离: {min_distance_to_y_axis}")
#print(f"pcd_moving{len(static_pcds)}")
if min_distance_to_y_axis < collision_threshold:
print(f"与 Y 轴发生碰撞! 最小距离: {min_distance_to_y_axis}")
return True
return check_collision_all(pcd_moving, static_pcds,collision_threshold)
import numpy as np
def compute_aabb(pcd):
@@ -307,12 +244,14 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
y_step=1
x_step=1
x_step_big = x_step * 2
y_step_big = y_step * 2
delta = 10
edge_x_min=0 + 4
edge_y_min=0 + 4
edge_x_max=machine_size[0]
edge_y_max=machine_size[1]
edge_x_min=0 + extend_dist_border_x_min
edge_y_min=0 + extend_dist_border_y_min
edge_x_max=machine_size[0] - extend_dist_border_x_max
edge_y_max=machine_size[1] - extend_dist_border_y_max
collision_threshold=2
move_last = True
@@ -333,31 +272,32 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
min_x = 0
max_delta_x = 0
x_top_delta = 1
border_delta = 4
pcd_first= []
pcd_second= []
index = 0
len_all = len(placed_models)
len_first = len_all * 0.7
for model in placed_models:
pcd = dict_bounds_fix[model['name']]
pcd_all.append(pcd)
if (get_axis_aligned_bbox(pcd)['y_min']>edge_y_max*0.3 or True):
# is_first = get_axis_aligned_bbox(pcd)['y_min']>edge_y_max*0.3 or True
is_first = index >= len_first
if (is_first or True):
pcd_first.append(pcd)
else:
pcd_second.append(pcd)
# pcd_all.append(pcd_downsampled)
# name_list.append(model['name'])
# model_list.append(model)
dict_name[pcd] = model['name']
dict_model[pcd] = model
dx = model['dimensions'][0]
x = model['position'][0]
if (x>=edge_x_max-x_top_delta) :
if (x>=edge_x_max) :
pcd_processed_x_top.append(pcd)
print("pcd_processed_x_top", model['name'])
if dx > max_x:
@@ -374,7 +314,6 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
if max_delta_x < 10:
draw_down = False
# for idx, pcd in enumerate(pcd_all):
for idx, pcd in enumerate(pcd_first):
if dict_model[pcd]['first_line']:
@@ -399,7 +338,7 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
dist_x = x - edge_x_min
if y - 10 < edge_y_min:
dist_y = y - edge_y_min
if (x<edge_x_max-x_top_delta) :
if (x<edge_x_max) :
if draw_down:
pcd.translate([-dist_x, -dist_y, 0])
@@ -412,20 +351,19 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
pcd_processed_curr = pcd_processed
else:
is_x_top = True
if check_collision_all(pcd, pcd_processed_no_x_top,1):
if check_collision_all(pcd, pcd_processed_no_x_top, extend_dist_min_collision):
name = ply_file_name
print("fail to place (x=0)", ply_file_name, dx)
dict_unplaced[name]=name
pcd_processed_curr = pcd_processed
x_step_big = x_step * 2
y_step_big = y_step * 2
collision_threshold_big = collision_threshold + 10
collision_threshold_big = collision_threshold + 6
y_init_big = 5
if dx > 80:
y_init_big = 10
x_init_big = y_init_big - 1
if check_collision_all(pcd, pcd_processed_curr, 1):
if check_collision_all(pcd, pcd_processed_curr, extend_dist_min_collision):
while True:
step = 25
pcd.translate([0, -step, 0])
@@ -511,8 +449,7 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
if bbox['y_min'] <= edge_y_min + collision_threshold_init and False:
pcd.translate([0, y_step, 0])
break
# if bbox['y_max'] >= edge_y_max - collision_threshold_init:
if bbox['y_max'] >= edge_y_max - border_delta:
if bbox['y_max'] >= edge_y_max:
pcd.translate([0, -y_step, 0])
T_transTemp = np.eye(4)
T_transTemp[:3, 3] = [0, -y_step, 0]
@@ -536,7 +473,7 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
pcd.translate([x_step, 0, 0])
break
# if bbox['x_max'] >= edge_x_max - collision_threshold_init:
if bbox['x_max'] >= edge_x_max - border_delta:
if bbox['x_max'] >= edge_x_max:
# print("1pcd.translate([-x_step, 0, 0])",name_list[idx])
pcd.translate([-x_step, 0, 0])
T_transTemp = np.eye(4)
@@ -663,9 +600,11 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
pcd_second2 = [pcd_second[i] for i in sorted_indices]
# print("pcd_second2 len", len(pcd_second2))
check_top_right = False
for idx, pcd in enumerate(pcd_second2):
ply_file_name = dict_name[pcd]
name = dict_name[pcd]
obj_name = ply_file_name.split("=")[0]+".obj"
# print("pcd_second2", obj_name)
@@ -679,11 +618,27 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
tx = edge_x_min - min_x
ty = -max_y - 0.001
T_transTemp = move_to_top_left(pcd, edge_x_min+2, edge_y_max-2)
if (check_top_right):
T_transTemp, tx, ty = move_to_top_right(pcd, edge_x_max, edge_y_max)
check_top_right = False
collision_threshold_init = collision_threshold+1
if not check_collision_all(pcd, last_pcd_processed, collision_threshold_init):
print("succ move_top_right",name,bbox['x_max'],bbox['y_max'],len(last_pcd_processed))
T_trans1 = T_transTemp @ T_trans1
last_pcd_processed.append(pcd)
dict_total_matrix[obj_name]= T_trans1 @ dict_total_matrix[obj_name]
continue
else:
pcd.translate((-tx, -ty, 0), relative=True)
T_transTemp = move_to_top_left(pcd, edge_x_min, edge_y_max)
T_trans1 = T_transTemp @ T_trans1
name = dict_name[pcd]
# print("pcd_second2",name,"tx",tx,"ty",ty)
succ_move = True
y_accum = 0
@@ -699,7 +654,7 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
if (finish_move2):
break
else:
if not check_collision_all(pcd, last_pcd_processed,collision_threshold_init):
if not check_collision_all(pcd, last_pcd_processed, collision_threshold_init):
print("succ_move1",name,bbox['x_max'],bbox['y_max'],len(last_pcd_processed))
break
@@ -1001,15 +956,6 @@ def compact_mode_for_min_dis_json(placed_models,dict_unplaced,dict_bounds_fix,ma
#"""
last_pcd_processed.append(pcd)
"""
print("is_x_top",is_x_top)
if not is_x_top:
last_pcd_processed.append(pcd)
print("last_pcd_processed.append",name)
else:
print("fail last_pcd_processed.append",name, is_x_top)
"""
# o3d.io.write_point_cloud(os.path.join(output_dir, name), pcd)
dict_total_matrix[obj_name]= T_trans1 @ dict_total_matrix[obj_name]
@@ -1029,18 +975,20 @@ def move_to_top_left(pcd, edge_x_min, edge_y_max):
return T_transTemp
def move_to_bottom_left(pcd, edge_x_min, edge_y_min):
def move_to_top_right(pcd, edge_x_max, edge_y_max):
points = np.asarray(pcd.points)
min_x = np.min(points[:, 0])
min_y = np.min(points[:, 1])
tx = edge_x_min - min_x
ty = edge_y_min - min_y
pcd.translate((tx, ty, 0), relative=True)
min_x = np.min(points[:, 0])
max_y = np.max(points[:, 1]) # 当前最大y值
tx = edge_x_max - min_x
ty = edge_y_max - max_y
T_transTemp = np.eye(4)
T_transTemp[:3, 3] = [tx, ty, 0]
return T_transTemp
pcd.translate((tx, ty, 0), relative=True)
return T_transTemp, tx, ty
def move_to_bottom_right(pcd, edge_x_max, edge_y_min):
@@ -1066,229 +1014,8 @@ def get_axis_aligned_bbox(pcd):
'z_max': np.max(points[:,2])
}
def down_obj_data_to_ply(weight_fix_out_obj_dir,weight_fix_out_ply_dir):
""""""
obj_file_list = [aa for aa in os.listdir(weight_fix_out_obj_dir) if aa.endswith(".obj")]
for obj_name in obj_file_list:
obj_path = os.path.join(weight_fix_out_obj_dir,obj_name)
mesh_obj = read_mesh(obj_path)
vertices = np.asarray(mesh_obj.vertices)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
voxel_size = 3 # 设置体素的大小,决定下采样的密度
pcd_downsampled = down_sample(pcd, voxel_size)
ply_out_path = os.path.join(weight_fix_out_ply_dir,obj_name.replace(".obj",".ply"))
o3d.io.write_point_cloud(ply_out_path, pcd_downsampled)
print(ply_out_path,"下采样完成。")
def compute_centroid_compact(pcd):
points = np.asarray(pcd.points)
centroid = np.mean(points, axis=0)
return centroid
def compute_base_point(pcd):
points = np.asarray(pcd.points)
x_center = np.mean(points[:, 0])
y_center = np.mean(points[:, 1])
min_z = np.min(points[:, 2])
return np.array([x_center, y_center, min_z])
import copy
def 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):
""""""
# obj_file_list = [aa for aa in os.listdir(weight_fix_out_obj_dir) if aa.endswith(".obj")]
obj_file_list = list(dict_mesh_obj.keys())
ply_path_dict = {}
# meshes = []
# for ply_file_name in os.listdir(bounds_fix_out_dir):
for ply_file_name in dict_bounds_fix:
ply_dict_key = ply_file_name.split("=")[0]
ply_path_dict[ply_dict_key] = ply_file_name
for obj_name in obj_file_list:
obj_path = os.path.join(weight_fix_out_obj_dir, obj_name)
# mesh_obj = read_mesh(obj_path, False)
mesh_obj = dict_mesh_obj[obj_name]
# mesh_obj = dict_origin[obj_origin_path]
original_obj_pid_dir = base_original_obj_dir
obj_origin_path = os.path.join(original_obj_pid_dir, obj_name)
obj_origin = dict_origin[obj_origin_path]
# obj_origin = copy.deepcopy(dict_origin[obj_origin_path])
ply_name_pid = obj_name.replace(".obj","")
# ply_name = ply_path_dict[ply_name_pid]
ply_name = ply_path_dict.get(ply_name_pid,None)
print(ply_name_pid,ply_name)
if ply_name is None:
continue
print("move_obj_to_compact_bounds",ply_name,len(dict_unplaced))
if not ply_name or ply_name in dict_unplaced:
print("unplaced",ply_name)
continue
ply_fix_path = os.path.join(bounds_fix_out_dir,ply_name)
ply_compact_path = os.path.join(bounds_compact_out_dir, ply_name)
# pcd_fix = o3d.io.read_point_cloud(ply_fix_path)
pcd_fix = dict_bounds_fix[ply_name]
vertices = np.asarray(obj_origin.vertices)
pcd_origin = o3d.geometry.PointCloud()
pcd_origin.points = o3d.utility.Vector3dVector(vertices)
# pcd_compact = o3d.io.read_point_cloud(ply_compact_path)
pcd_compact = dict_compact[ply_name]
centroid_fix = compute_centroid_compact(pcd_fix)
centroid_compact = compute_centroid_compact(pcd_compact)
centroid_origin = compute_centroid_compact(pcd_origin)
displacement = centroid_compact - centroid_fix
# displacement = centroid_compact - centroid_origin
vertices = np.asarray(mesh_obj.vertices)
vertices_translated = vertices + displacement # 将位移应用到每个顶点
mesh_obj.vertices = o3d.utility.Vector3dVector(vertices_translated) # 更新网格顶点
obj_pid = obj_name.split("_P")[0]
#compact_obj_pid_out_dir = os.path.join(compact_obj_out_dir,obj_pid)
compact_obj_pid_out_dir= compact_obj_out_dir
if not os.path.exists(compact_obj_pid_out_dir):
os.makedirs(compact_obj_pid_out_dir)
obj_path_compact = os.path.join(compact_obj_pid_out_dir,obj_name)
mesh_obj.compute_vertex_normals()
o3d.io.write_triangle_mesh(obj_path_compact, mesh_obj,write_triangle_uvs=True)
print(obj_path_compact, "移动后obj保存完成", displacement)
# meshes.append(mesh_obj)
#original_obj_pid_dir = os.path.join(base_original_obj_dir,obj_pid)
original_obj_pid_dir = base_original_obj_dir
for mtl in os.listdir(compact_obj_pid_out_dir):
if mtl.endswith(".mtl"):
if obj_pid in mtl:
mtl_path = os.path.join(compact_obj_pid_out_dir,mtl)
os.remove(mtl_path)
mtl_name = None
tex_name = None
for file_name in os.listdir(original_obj_pid_dir):
if file_name.endswith(".mtl"):
if obj_pid in file_name:
mtl_name = file_name
if file_name.endswith(".jpg"):
if obj_pid in file_name:
tex_name = file_name
if file_name.endswith(".png"):
if obj_pid in file_name:
tex_name = file_name
for file in os.listdir(original_obj_pid_dir):
#print(f"file{file}")
if file.endswith(".obj"):
continue
if obj_pid not in file:
continue
origin_path = os.path.join(original_obj_pid_dir,file)
dis_path = os.path.join(compact_obj_pid_out_dir,file)
if os.path.isfile(origin_path):
#print(f'origin_path{origin_path}')
#print(f'dis_path{dis_path}')
shutil.copy(origin_path,dis_path)
time.sleep(1)
#print("-"*50)
base_origin_obj_path = os.path.join(original_obj_pid_dir,obj_name)
#print(f"base_origin_obj_path{base_origin_obj_path}")
#print(f"obj_path_compact{obj_path_compact}")
update_obj_file(base_origin_obj_path, obj_path_compact)
placed_remove_obj_path = os.path.join(placed_remove_dir, obj_name)
shutil.copy(base_origin_obj_path,placed_remove_obj_path)
os.remove(base_origin_obj_path)
exist_obj_any = False
exist_obj = False
delete_mtl = False
for file_name in os.listdir(original_obj_pid_dir):
if file_name.endswith(".obj"):
if obj_pid in file_name:
exist_obj = True
exist_obj_any = True
if not exist_obj_any:
delete_mtl = True
if not exist_obj:
delete_mtl = True
if delete_mtl:
print("delete_mtl",mtl_name,tex_name)
if mtl_name!=None:
base_origin_mtl_path = os.path.join(original_obj_pid_dir,mtl_name)
placed_remove_mtl_path = os.path.join(placed_remove_dir, mtl_name)
shutil.copy(base_origin_mtl_path,placed_remove_mtl_path)
os.remove(base_origin_mtl_path)
if tex_name!=None:
base_origin_tex_path = os.path.join(original_obj_pid_dir,tex_name)
placed_remove_tex_path = os.path.join(placed_remove_dir, tex_name)
shutil.copy(base_origin_tex_path,placed_remove_tex_path)
os.remove(base_origin_tex_path)
print(f"排版错误模型数量::{len(dict_bad)}")
for obj_name in dict_bad:
print("--错误模型名:", obj_name)
process_obj_files(original_obj_pid_dir,bad_dir,obj_name)
print(f"排版剩余模型数量::{len(dict_unplaced)}")
for ply_file_name in dict_unplaced:
obj_name = ply_file_name.split("=")[0]+".obj"
print("--剩余模型名:", obj_name)
process_obj_files(original_obj_pid_dir,full_dir,obj_name)
import json
def extract_angles(R):
# 提取绕X、Y、Z轴的旋转角度(弧度)
rx_rad = np.arctan2(R[2, 1], R[2, 2]) # X轴旋转
ry_rad = np.arcsin(-R[2, 0]) # Y轴旋转
rz_rad = np.arctan2(R[1, 0], R[0, 0]) # Z轴旋转
# 将弧度转换为角度(度数)
rx_deg = np.degrees(rx_rad)
ry_deg = np.degrees(ry_rad)
rz_deg = np.degrees(rz_rad)
return rx_deg, ry_deg, rz_deg
def compute_mesh_center(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
# 遍历所有顶点累加坐标值
for vertex in vertices:
sum_x += vertex[0]
sum_y += vertex[1]
sum_z += vertex[2]
# 计算各坐标轴的平均值
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
return centroid
import re
def extract_numbers_from_filename(filename):
"""
@@ -1313,7 +1040,7 @@ def extract_numbers_from_filename(filename):
import requests
def move_obj_to_compact_bounds_json(base_original_obj_dir,dict_mesh_obj,dict_unplaced,dict_bad,bad_dir,full_dir,dict_bounds_fix,
dict_total_matrix,save_mesh,batch_id, print_start_time,selected_machine,selected_mode,version):
dict_total_matrix,batch_id, print_start_time,selected_machine,selected_mode,version):
"""生成3D打印布局的JSON数据并保存为3DPrintLayout.json"""
# 创建符合3DPrintLayout规范的JSON数据结构
layout_data = {
@@ -1429,10 +1156,6 @@ def move_obj_to_compact_bounds_json(base_original_obj_dir,dict_mesh_obj,dict_unp
print("--剩余模型名:", obj_name)
process_obj_files(original_obj_pid_dir,full_dir,obj_name)
if save_mesh:
cache_type_setting_dir=f"{base_original_obj_dir}/arrange"
transform_save_o3d(layout_data, original_obj_pid_dir, cache_type_setting_dir)
return send_layout_data
def process_obj_files(original_obj_pid_dir,placed_remove_dir,obj_name):
@@ -1494,31 +1217,6 @@ def process_obj_files(original_obj_pid_dir,placed_remove_dir,obj_name):
shutil.copy(base_origin_tex_path, placed_remove_tex_path)
os.remove(base_origin_tex_path)
def update_obj_file(original_obj_path,compact_obj_path):
""""""
with open(original_obj_path, "r") as f:
lines_original = f.readlines()
mtllib_name = None
mat_name = None
for line in lines_original:
if line.startswith("mtllib"):
mtllib_name = line.split(" ")[1]
elif line.startswith("usemtl"):
mat_name = line.split(" ")[1]
if mtllib_name and mat_name:
break
with open(compact_obj_path, "r") as f:
lines = f.readlines()
new_lines = []
for line2 in lines:
if line2.startswith("mtllib"):
line2 = f"mtllib {mtllib_name}\n" # 替换为原始 MTL 文件路径
elif line2.startswith("usemtl"):
line2 = f"usemtl {mat_name}\n" # 替换为原始贴图路径
new_lines.append(line2)
with open(compact_obj_path, "w") as f:
f.writelines(new_lines)
def pass_for_min_dis(placed_models, dict_unplaced, dict_bounds_fix):
pcd_all = []
@@ -1546,23 +1244,3 @@ def pass_for_min_dis(placed_models, dict_unplaced, dict_bounds_fix):
name = name_list[idx]
print("fail to place (x=0)", name_list[idx], y, dy)
dict_unplaced[name]=name
if __name__ == '__main__':
out_dir = "/data/datasets_20t/type_setting_test_data/"
weight_fix_out_obj_dir = f"{out_dir}/print_weight_fix_data_obj"
weight_fix_out_ply_dir = f"{out_dir}/data/datasets_20t/type_ssetting_test_data/print_weight_fix_data_ply"
base_original_obj_dir=f"{print_factory_type_dir}/8/"
if not os.path.exists(weight_fix_out_ply_dir):
os.makedirs(weight_fix_out_ply_dir)
bounds_fix_out_dir = f"{out_dir}/print_bounds_fix_data"
bounds_compact_out_dir = f"{out_dir}/print_bounds_compact_data"
compact_obj_out_dir = f"{out_dir}//print_compact_obj"
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)
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)
+14 -28
View File
@@ -7,19 +7,19 @@ import argparse
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, script_dir)
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 print_data_dir
from config import url_send_layout
from config import big_machine_size
from config import small_machine_size
from config import oss_config
from general import copy_obj_2x
from general import need_upload_result
from general import get_blank_path
from point_cloud_layout import make_bbox_for_print
@@ -101,12 +101,11 @@ def print_type_setting_obj(base_original_obj_dir=None,batch_id=0,selected_mode="
os.makedirs(full_dir)
is_small_machine = True if selected_machine=="小机型" else False
save_mesh = True if output_format=="模型" else False
version = "print_type_setting25.local"
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)
dict_total_matrix,batch_id,print_start_time,selected_machine,selected_mode,version)
end_time4 = time.time()
print("排版完成")
@@ -144,14 +143,13 @@ def print_type_setting_obj(base_original_obj_dir=None,batch_id=0,selected_mode="
is_upload_result = True
if is_upload_result:
print(f"执行上 传-parent_dir={print_factory_type_dir},base_original_obj_dir={base_original_obj_dir},batch_id={batch_id}")
print(f"执行上传: base_original_obj_dir={base_original_obj_dir}, batch_id={batch_id}")
upload_result(base_original_obj_dir, oss_config, batch_id)
print(f"need_upload_result={need_upload_result()}")
if need_upload_result():
print(f"send_layout_data={send_layout_data}")
# 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)
@@ -180,35 +178,23 @@ def print_type_setting_obj(base_original_obj_dir=None,batch_id=0,selected_mode="
return 0
def preview(base_original_obj_dir=None, batch_id=0):
load_show_save(base_original_obj_dir, {}, batch_id, True)
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 = "9910032"
is_use_parser = False
if is_use_parser:
parser = argparse.ArgumentParser()
parser.add_argument("--batch_id", type=str, required=True, help="batch_id")
args = parser.parse_args()
batch_id = args.batch_id
else:
batch_id = "1"
src_dir = batch_id
selected_mode="紧凑" # 标准 紧凑
output_format="JSON" # 模型 JSON
selected_machine = "大机型" # 小机型 大机型
base_original_obj_dir = f"{print_factory_type_dir}/{src_dir}"
base_original_obj_dir = f"{print_data_dir}/{src_dir}"
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)
-808
View File
@@ -1,808 +0,0 @@
import os.path
import shutil
import open3d as o3d
import numpy as np
import copy
from tqdm import tqdm
import time
import multiprocessing
import oss2
from joblib import Parallel, delayed
import itertools
from numba import njit, prange
#import bpy
from plyfile import PlyData, PlyElement
from multiprocessing import Pool, RawArray
import ctypes
import itertools
from compute_print_net import down_sample
def make_pcd_plane():
# 创建Y=0的平面点云
width = 60.0 # 平面的宽度
height = 60.0 # 平面的高度
resolution = 50 # 点云的分辨率,越高越密集
# 生成平面点云
x = np.linspace(-width / 2, width / 2, resolution)
y = np.linspace(-height / 2, height / 2, resolution)
xv, yv = np.meshgrid(x, y)
zv = np.zeros_like(xv) # Y 坐标恒为 0
# 将网格转换为点云的顶点数组
points_plane = np.vstack((xv.flatten(), yv.flatten(), zv.flatten())).T
# 创建Open3D点云对象
pcd_plane = o3d.geometry.PointCloud()
pcd_plane.points = o3d.utility.Vector3dVector(points_plane)
pcd_plane.paint_uniform_color([0, 1, 0])
return pcd_plane
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
#
# # 计算每个角度的组合
# angles_x = range(0, 360, angle_step)
# angles_y = range(0, 360, angle_step)
# angles_z = range(0, 360, angle_step)
#
# # 创建一个进程池并行处理
# with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
# results = []
#
# # 提交任务
# for angle_x in angles_x:
# for angle_y in angles_y:
# for angle_z in angles_z:
# results.append(
# pool.apply_async(calculate_rotation_and_center_of_mass, (angle_x, angle_y, angle_z, points)))
#
# # 获取所有结果
# for result in results:
# center_of_mass_z, angle_x, angle_y, angle_z = result.get()
#
# # 更新最优旋转角度
# 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 = angle_x, angle_y, angle_z
#
# return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
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
#"""
@njit
def safe_min(arr):
if arr.size == 0:
return np.inf
return np.min(arr)
# 核心计算函数(支持Numba加速)
@njit(fastmath=True, cache=True)
def calculate_rotation_z(angle_x, angle_y, angle_z, points, cos_cache, sin_cache, angle_step):
#计算单个旋转组合后的重心Z坐标(无显式平移)
if points.shape[0] == 0:
return np.inf # 返回极大值避免干扰最优解
# 获取预计算的三角函数值
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)
min_z = safe_min(z_values)
avg_z = np.mean(z_values)
return avg_z - min_z # 等效于平移后的重心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
)
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 = np.asarray(points)
points_float64 = points_np.astype(np.float64)
points = np.ascontiguousarray(points_float64)
# 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)
#"""
#"""
def rotate_x(angle):
theta = np.radians(angle)
return np.array([
[1, 0, 0],
[0, np.cos(theta), -np.sin(theta)],
[0, np.sin(theta), np.cos(theta)]
])
def rotate_y(angle):
theta = np.radians(angle)
return np.array([
[np.cos(theta), 0, np.sin(theta)],
[0, 1, 0],
[-np.sin(theta), 0, np.cos(theta)]
])
def rotate_z(angle):
theta = np.radians(angle)
return np.array([
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]
])
def compute_z_height_and_center(rotated_points):
z_min, z_max = rotated_points[:, 2].min(), rotated_points[:, 2].max()
y_min, y_max = rotated_points[:, 1].min(), rotated_points[:, 1].max()
return (z_max - z_min), (y_min + y_max) / 2
def init_worker(shared_array, shape, dtype):
global global_points
global_points = np.frombuffer(shared_array, dtype=dtype).reshape(shape)
def compute_rotation(args):
angle_x, angle_y, angle_z = args
R = rotate_z(angle_z) @ rotate_y(angle_y) @ rotate_x(angle_x)
rotated_points = global_points @ R.T
z_height, center_y = compute_z_height_and_center(rotated_points)
return (angle_x, angle_y, angle_z, z_height, center_y)
def parallel_rotation3(points, angle_step=5):
# 生成所有旋转角度组合
angles = itertools.product(
np.arange(0, 360, angle_step),
np.arange(0, 360, angle_step),
np.arange(0, 360, angle_step)
)
# 共享内存初始化
shape, dtype = points.shape, points.dtype
shared_array = RawArray(ctypes.c_double, points.size)
shared_points = np.frombuffer(shared_array, dtype=dtype).reshape(shape)
np.copyto(shared_points, points)
# 多进程计算
with Pool(initializer=init_worker, initargs=(shared_array, shape, dtype)) as pool:
results = pool.imap_unordered(compute_rotation, angles, chunksize=100)
# 寻找最优解
best_angle = (0, 0, 0)
min_z_height = float('inf')
min_center_y = 0
for result in results:
if result[3] < min_z_height:
best_angle = result[:3]
min_z_height = result[3]
min_center_y = result[4]
return (*best_angle, min_center_y)
#"""
#"""
def parallel_rotation4(points, angle_step=4):
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
min_center = float('inf')
best_angle = 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
"""
#"""
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)
#"""
def compute_mesh_center(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
# 遍历所有顶点累加坐标值
for vertex in vertices:
sum_x += vertex[0]
sum_y += vertex[1]
sum_z += vertex[2]
# 计算各坐标轴的平均值
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
return centroid
def get_lowest_position_of_center(obj_path,voxel_size,dict_origin,total_matrix):
mesh_obj = read_mesh(obj_path)
dict_origin[obj_path] = mesh_obj
# dict_origin[obj_path] = copy.deepcopy(mesh_obj)
#o3d.visualization.draw_geometries([mesh_obj])
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: {obj_path}")
return None
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
# o3d.visualization.draw_geometries([pcd])
# pcd = o3d.io.read_point_cloud(ply_file_path)
# 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)
# 确保下采样后有点云
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
start_time = time.time()
# 旋转并计算最优角度:绕X、Y、Z轴进行每度的旋转
# best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation(points, angle_step=3)
# best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation2(points, angle_step=3)
# best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation3(points, angle_step=3)
best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation4(points, angle_step=3)
print("get_lowest_position_of_center", obj_path, best_angle_x,best_angle_y,best_angle_z,"time",time.time()-start_time)
"""
# if best_angle_x >= 180:
if best_angle_x >= 155 and best_angle_x <= 325:
best_angle_x += 180
if best_angle_y >= 180:
best_angle_y += 180
if best_angle_z >= 180:
best_angle_z += 180
#"""
# 记录结束时间
end_time = time.time()
elapsed_time = end_time - start_time
# print(f"Time taken: {elapsed_time:.2f} seconds")
# 输出最佳的旋转角度
# print(f"Best Rotation Angles: angle_x = {best_angle_x}, angle_y = {best_angle_y}, angle_z = {best_angle_z}")
# print(f"Minimum Y Center of Mass: {min_center_of_mass_y}")
#time.sleep(1000)
# 使用最佳角度进行旋转并平移obj
pcd_transformed = copy.deepcopy(mesh_obj)
#"""
center = pcd_transformed.get_center()
#arrow = o3d.geometry.TriangleMesh.create_arrow(0.05, 0.1)
#arrow.translate(center)
#o3d.visualization.draw_geometries([pcd_transformed, arrow])
# 最佳角度旋转
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)
# centroid = pcd.get_center()
centroid = pcd_transformed.get_center()
# z_mean1 = centroid[2]
T_x = np.eye(4)
T_x[:3, :3] = R_x
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
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
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
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
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
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
#arrow = o3d.geometry.TriangleMesh.create_arrow(0.05, 0.1)
#arrow.translate(center)
#o3d.visualization.draw_geometries([pcd_transformed, arrow])
#"""
#"""
#试着旋转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])
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])
# centroid = pcd.get_center()
centroid = pcd_transformed.get_center()
# z_mean2 = centroid[2]
#
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])
# 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)
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
translation = total_matrix[:3, 3]
# print(3,translation)
return pcd_transformed, total_matrix
def axis_angle_to_rotation_matrix(axis, angle):
"""手动生成旋转矩阵"""
axis = axis / np.linalg.norm(axis) # 单位化
cos_a = np.cos(angle)
sin_a = np.sin(angle)
return np.array([
[cos_a + axis[0]**2*(1-cos_a),
axis[0]*axis[1]*(1-cos_a) - axis[2]*sin_a,
axis[0]*axis[2]*(1-cos_a) + axis[1]*sin_a],
[axis[1]*axis[0]*(1-cos_a) + axis[2]*sin_a,
cos_a + axis[1]**2*(1-cos_a),
axis[1]*axis[2]*(1-cos_a) - axis[0]*sin_a],
[axis[2]*axis[0]*(1-cos_a) - axis[1]*sin_a,
axis[2]*axis[1]*(1-cos_a) + axis[0]*sin_a,
cos_a + axis[2]**2*(1-cos_a)]
])
def load_obj_data(get_pid,base_out_dir):
"""下载obj文件"""
access_key_id = os.getenv('OSS_TEST_ACCESS_KEY_ID', 'LTAI5tBWbfkZntfJij4Fg9gz')
access_key_secret = os.getenv('OSS_TEST_ACCESS_KEY_SECRET', 'sYDps1i9NSge6hn130EgpPJezKM6Gx')
bucket_name = os.getenv('OSS_TEST_BUCKET', 'suwa3d-securedata')
endpoint = os.getenv('OSS_TEST_ENDPOINT', 'https://oss-cn-shanghai.aliyuncs.com')
bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name)
# if not os.path.exists(base_out_dir):
# os.makedirs(base_out_dir)
prefix = f"objs/print/{get_pid}/"
out_pid_dir = os.path.join(base_out_dir,str(get_pid))
for obj in oss2.ObjectIterator(bucket, prefix=prefix):
# 如果迭代器至少返回一个对象,那么表示该"文件夹"存在
if not os.path.exists(out_pid_dir):
os.makedirs(out_pid_dir)
#print("Folder exists.")
filename = obj.key[len(prefix):]
if get_pid not in filename:
continue
print("获取的文件名称",filename)
local_path = os.path.join(out_pid_dir, filename)
# 下载对象到本地文件
try:
bucket.get_object_to_file(obj.key, local_path)
print(f"Downloaded {obj.key} to {local_path}")
except:
print("下载错误",get_pid)
record_text = "./error_load.txt"
with open(record_text, 'a') as f:
f.write(get_pid + '\n')
else:
# 如果迭代器没有返回任何对象,那么表示该"文件夹"不存在
print("ossFolder does not exist.",get_pid)
import re
def copy_obj_2x(base_obj_dir):
obj_list = [aa for aa in os.listdir(base_obj_dir) if aa.endswith(".obj")]
for obj_name in obj_list:
if "_F" in obj_name:
continue
obj_count = obj_name.split("x")[-1].replace(".obj","")
if not obj_count.isnumeric():
match = re.search(r"x(\d+)", obj_name)
if match:
obj_count = match.group(1)
else:
print("未找到 x 后的数字")
obj_count_num = int(obj_count)
if obj_count_num!=1:
for i in range(obj_count_num-1):
origin_path = os.path.join(base_obj_dir,obj_name)
dis_path = os.path.join(base_obj_dir,obj_name.replace(".obj",f"_F{i+1}.obj"))
if not os.path.exists(dis_path):
shutil.copy(origin_path,dis_path)
print(dis_path,"复制成功")
def save_mesh_with_blender(obj_transformed, obj_path, apply_transform=True):
# o3d.io.write_triangle_mesh(obj_path, obj_transformed,write_triangle_uvs=True)
# return
"""使用 Blender 导出变换后的网格"""
# 创建新网格对象
mesh = bpy.data.meshes.new("TempMesh")
mesh.from_pydata(
np.asarray(obj_transformed.vertices),
[],
np.asarray(obj_transformed.triangles).tolist()
)
obj = bpy.data.objects.new("TempObject", mesh)
# 链接到场景
bpy.context.collection.objects.link(obj)
# 设置上下文
original_selection = bpy.context.selected_objects.copy()
original_active = bpy.context.view_layer.objects.active
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# 应用变换
if apply_transform:
bpy.ops.object.transform_apply(
location=True,
rotation=True,
scale=True
)
# 配置导出参数
export_settings = {
'filepath': obj_path,
'export_selected_objects': True,
'export_triangulated_mesh': True,
'forward_axis': 'Y',
'up_axis': 'Z',
'global_scale': 1.0
}
# 执行导出
try:
bpy.ops.wm.obj_export(**export_settings)
finally:
# 清理临时对象
bpy.data.objects.remove(obj, do_unlink=True)
bpy.data.meshes.remove(mesh)
# 恢复原始上下文
bpy.ops.object.select_all(action='DESELECT')
for o in original_selection:
o.select_set(True)
bpy.context.view_layer.objects.active = original_active
if __name__ == '__main__':
out_dir = "/data/datasets_20t/type_setting_test_data/"
base_out_dir = f"{out_dir}/8/"
weight_fix_out_dir = f"{out_dir}/print_weight_fix_data_obj"
weight_fix_out_ply_dir = f"{out_dir}/print_weight_fix_data_ply"
copy_obj_2x(base_out_dir)
+61 -136
View File
@@ -6,13 +6,15 @@ from PIL import Image
import argparse
import gc
from config import print_factory_type_dir
from config import print_data_dir
from config import test_print_max
from config import version
from general import mesh_transform_by_matrix
from general import read_mesh
from general import get_blank_path
from compute_print_net_out import read_mesh
def load_and_transform_models(base_path, dict_origin, json_name):
meshes = [] # 存储所有变换后的网格
@@ -96,8 +98,9 @@ def load_and_transform_models(base_path, dict_origin, json_name):
print(f"已加载并变换: {index} {os.path.basename(obj_path)}")
index = index + 1
# if (index > 80):
# break
if test_print_max > 0:
if (index > test_print_max):
break
add_plank = True
if add_plank:
@@ -149,127 +152,6 @@ def compute_mesh_center(vertices):
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
return centroid
def load_and_show(base_path, json_name="3DPrintLayout.json"):
# 加载并变换所有模型
transformed_meshes = load_and_transform_models(base_path, {}, json_name)
if not transformed_meshes:
print("没有加载到任何模型,请检查错误信息")
else:
print(f"成功加载并变换了 {len(transformed_meshes)} 个模型")
# 可视化所有模型
print("显示所有模型... (按'Q'退出)")
try:
from packaging import version
o3d_version = version.parse(o3d.__version__)
# 新版本 draw_geometries 参数
if o3d_version >= version.parse("0.13.0"):
o3d.visualization.draw_geometries(
transformed_meshes,
window_name="模型展示",
mesh_show_back_face=True,
mesh_show_wireframe=False
)
# 旧版本 draw_geometries 参数
else:
o3d.visualization.draw_geometries(
transformed_meshes,
window_name="模型展示",
point_show_normal=False,
mesh_show_back_face=True
)
except Exception as e:
print(f"使用 draw_geometries 可视化失败: {e}")
def setup_orthographic_camera(vis, meshes, ortho_width=15.0, camera_height=20.0):
"""
设置精确的正交投影相机
"""
view_control = vis.get_view_control()
# 计算场景边界框以确定合适的正交参数
all_points = []
for mesh in meshes:
if hasattr(mesh, 'vertices'):
points = np.asarray(mesh.vertices)
else:
points = np.asarray(mesh.points)
all_points.append(points)
if all_points:
all_points = np.vstack(all_points)
bbox_min = np.min(all_points, axis=0)
bbox_max = np.max(all_points, axis=0)
scene_center = (bbox_min + bbox_max) / 2
scene_size = np.max(bbox_max - bbox_min)
# 设置观察点为场景中心
view_control.set_lookat(scene_center)
# 设置相机在场景上方,俯视场景
view_control.set_front([0, 0, -1]) # 看向负Z轴(从上向下)
view_control.set_up([0, 1, 0]) # Y轴向上
try:
# 启用正交投影
view_control.set_orthogonal(True)
# 根据场景大小设置正交投影宽度
view_control.set_orthogonal_width(max(scene_size * 1.2, ortho_width))
print(f"正交投影已设置: 宽度={max(scene_size * 1.2, ortho_width):.2f}")
except AttributeError:
# 回退到透视投影模拟
view_control.set_zoom(0.1)
print("使用透视投影模拟正交效果")
return True
def auto_fit_to_view(vis, meshes):
"""
自动调整视图以显示所有模型
"""
view_control = vis.get_view_control()
# 方法1: 使用 Open3D 的自动适配功能
try:
view_control.fit_to_geometry(meshes)
print("已自动适配视图以显示所有模型")
return True
except:
pass
# 方法2: 手动计算并设置
all_points = []
for mesh in meshes:
if hasattr(mesh, 'vertices'):
points = np.asarray(mesh.vertices)
elif hasattr(mesh, 'points'):
points = np.asarray(mesh.points)
else:
continue
all_points.append(points)
if all_points:
all_points = np.vstack(all_points)
bbox_min = np.min(all_points, axis=0)
bbox_max = np.max(all_points, axis=0)
scene_center = (bbox_min + bbox_max) / 2
scene_size = np.max(bbox_max - bbox_min)
# 设置合适的视角和缩放
view_control.set_lookat(scene_center)
# 根据场景大小设置 zoom
zoom_level = max(0.05, min(1.0, 10.0 / scene_size))
zoom_level = 0.5
view_control.set_zoom(zoom_level)
print(f"手动适配视图: 场景大小 {scene_size:.2f}, zoom {zoom_level:.3f}")
return True
return False
def set_orthographic_camera(view_control, desired_width=1920, desired_height=1080):
"""
通过相机参数设置正交投影,并确保尺寸匹配
@@ -356,7 +238,7 @@ def set_orthographic(meshes, width=1920, height=1080,
del mesh
del points
gc.collect()
print(f"set_orthographic {index}")
# print(f"set_orthographic {index}")
index = index + 1
del meshes
@@ -405,7 +287,6 @@ def set_orthographic(meshes, width=1920, height=1080,
view_control.set_up([0, 1, 0])
ortho_width = ortho_width or 10.0
print("set_orthographic 1")
#for _ in range(2):
# vis.poll_events()
# vis.update_renderer()
@@ -413,19 +294,19 @@ def set_orthographic(meshes, width=1920, height=1080,
# 设置正交投影
try:
set_orthographic_camera(view_control)
print("set_orthographic 2")
except AttributeError:
set_orthographic_projection(view_control, ortho_scale=15.0)
print("set_orthographic 3")
#for _ in range(5):
# vis.poll_events()
# vis.update_renderer()
print("set_orthographic End")
# print("set_orthographic End")
return vis
from PIL import Image, ImageDraw, ImageFont
def render_to_texture(meshes, output_image_path):
vis = set_orthographic(meshes)
@@ -433,6 +314,48 @@ def render_to_texture(meshes, output_image_path):
# 渲染并保存
vis.capture_screen_image(output_image_path, do_render=True)
text = f"v{version}" # 使用PIL添加文字
try:
image = Image.open(output_image_path)
draw = ImageDraw.Draw(image)
font = ImageFont.load_default()
font_families = [
'DejaVu Sans', # 许多 Linux 发行版的标准配置[2,5](@ref)
'Liberation Sans', # 作为 Arial 等字体的开源替代,预装可能性高[1](@ref)
'Ubuntu', # Ubuntu 系统默认字体之一[3,5](@ref)
'FreeSans', # 另一个开源无衬线字体
'Source Sans Pro' # 一款清晰易读的开源字体
]
for font_family in font_families:
try:
font = ImageFont.truetype(font_family, 30)
break
except IOError:
continue
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 设置位置:左下角,左边距50像素,下边距10像素[1,3](@ref)
width, height = image.size
x = 50 # 左边距
y = height - text_height - 50 # 图片高度减去文字高度再减去10像素边距
fill_color = (255, 0, 0)
# 添加文字
draw.text((x, y), text, fill=fill_color, font=font)
# 保存带文字的图片
image.save(output_image_path)
except Exception as e:
print(f"添加文字时出错: {e}")
print(f"高级渲染图片已保存到: {output_image_path}")
return vis
@@ -484,12 +407,14 @@ def load_show_save(base_path, dict_origin, batch_id, is_show=False):
# 主程序
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--batch_id", type=str, required=False, help="batch_id")
args = parser.parse_args()
# batch_id = args.batch_id
batch_id = "9910032"
is_use_parser = False
if is_use_parser:
parser = argparse.ArgumentParser()
parser.add_argument("--batch_id", type=str, required=False, help="batch_id")
args = parser.parse_args()
batch_id = args.batch_id
else:
batch_id = "9910032"
base_path = f"{print_data_dir}{batch_id}/"