Browse Source

初始化代码

main
hesuicong 4 weeks ago
commit
daf8667065
  1. 346
      clound_print.py
  2. 1569
      download_print.py
  3. 6
      download_print/run.yaml
  4. 354
      download_print_out.py
  5. 847
      get_lowest_position_of_center_ext.py
  6. 352
      get_lowest_position_of_z_out.py
  7. 179
      grid_near_three.py
  8. 6
      print.sh
  9. 291
      print_factory_type_setting_obj_run.py
  10. 193
      print_factory_type_setting_obj_run_GUI.py
  11. 162
      print_merged_many_obj.py
  12. 3619
      print_mplot3d_point_cloud_layout.py
  13. 127
      print_setting_run.py
  14. 51
      print_setting_ui.py
  15. 1120
      print_show_weight_max_obj.py
  16. 346
      print_type_setting_gui.py
  17. 38
      print_type_setting_gui.spec
  18. 126
      print_type_setting_gui_multi.py
  19. 25
      qt5_demo.py
  20. 44
      qt5_demo.spec
  21. 275
      sui_01.py
  22. 19
      test.py
  23. 482
      test_load_json.py
  24. 52
      x_y_min_test.py
  25. 152
      读写时间测试.py
  26. 98
      读写时间测试2.py

346
clound_print.py

File diff suppressed because one or more lines are too long

1569
download_print.py

File diff suppressed because it is too large Load Diff

6
download_print/run.yaml

@ -0,0 +1,6 @@
run:
down:
AccessKeyId: 'LTAI5tJDLxK6wBdHE9Nu443G'
AccessKeySecret: 'sBN7IK4ozSE9nNtmD3dmDSuiS24SZq'
Endpoint: 'oss-cn-shanghai.aliyuncs.com'
Bucket: 'suwa3d-securedata'

354
download_print_out.py

@ -0,0 +1,354 @@
import yaml
import oss2
import os
from tqdm import tqdm
import os
from pathlib import Path
import numpy as np
import os
import argparse
import open3d as o3d
def custom_mesh_transform(vertices, transform_matrix):
"""
手动实现网格变换对每个顶点应用齐次变换矩阵
参数:
vertices: 网格顶点数组 (N, 3)
transform_matrix: 4x4 齐次变换矩阵
返回:
变换后的顶点数组 (N, 3)
"""
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
# 3. 转回非齐次坐标 (3xN) → (N, 3)
transformed_vertices = transformed_homogeneous[:3, :].T
return transformed_vertices
class DataTransfer:
'''
数据传输类
'''
def __init__(self, local_path: str, oss_path: str, oss_client: oss2.Bucket):
'''
local_path: 本地输出路径
oss_path: oss路径
oss_client: oss客户端
'''
self.local_path = local_path
self.oss_path = oss_path.lstrip('/')
self.oss_client = oss_client
order_id: str
pid: str
model_height: str
def download_data_rename_json(self, json_model_info):
"""
OSS 下载数据到本地保持原有目录结构
"""
# 列出所有对象
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('/')
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"{json_model_info.order_id}_{file_name}"
# new_file_name = file_name
elif file_ext.lower() == '.obj':
# 对于.obj文件,完全重命名
new_file_name = f"{json_model_info.obj_name}"
else:
# 其他文件类型保持原名
new_file_name = file_name
print("new_file_name=", new_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"{json_model_info.order_id}_{old_mtl_name}"
f_out.write(f"mtllib {new_mtl_name}\n")
mtllib_modified = True
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"{json_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}")
import requests
import json
import shutil
def get_api(url):
try:
response = requests.get(url)
response.raise_for_status() # 检查请求是否成功
response = json.loads(response.text)
if response.get("code") != 1000:
raise Exception(f"Error fetching URL {url}: {response.get('message')}")
else:
return response
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
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]
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, 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}/{pid}_image"
target_dir = f"{workdir}"
url = f"https://mp.api.suwa3d.com/api/order/getOssSuffixByOrderId?order_id={model_info.order_id}"
res = requests.get(url)
data = res.json()["data"]
# print("datas=",data)
data = data.replace("/init_obj", "")
print("target_dir=", target_dir)
download_textures = DataTransfer(target_dir, f"objs/download/print/{pid}/{data}/{model_height}/", oss_client)
download_textures.download_data_rename_json(model_info)
# 下载后检查目标文件夹是否为空
if os.path.exists(target_dir) and not os.listdir(target_dir):
shutil.rmtree(target_dir)
print(f"下载后检查发现目标文件夹为空,已删除: {target_dir}")
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)
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
def download_transform_save_by_json(pid_file, workdir, oss_config):
layout_data = download_datas_by_json(pid_file, workdir, oss_config)
original_obj_pid_dir = workdir
cache_type_setting_dir = os.path.join(workdir, "arrange")
Path(cache_type_setting_dir).mkdir(exist_ok=True)
print(f"original_obj_pid_dir={original_obj_pid_dir}, cache_type_setting_dir={cache_type_setting_dir}")
transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir)
def transform_save(layout_data, original_obj_pid_dir, cache_type_setting_dir):
meshes = []
# 小打印机380*345,需要偏移-380,-345
need_offset = True
for model in layout_data["models"]:
transform = model.get('transform', {})
homo_matrix = transform["homo_matrix"] # 获取存储的列表
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
obj_name = model.get('file_name', '')
obj_path = os.path.join(original_obj_pid_dir, obj_name)
# 加载网格
try:
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
if not mesh.has_vertices():
print(f"警告: 网格无有效顶点 - {obj_path}")
continue
except Exception as e:
print(f"加载模型失败: {obj_path} - {e}")
continue
original_vertices = np.asarray(mesh.vertices)
transformed_vertices = custom_mesh_transform(original_vertices, reconstructed_matrix)
# 如果 need_offset 为 True,应用额外的偏移
if need_offset:
# 应用偏移 (-380, -345, 0)
offset = np.array([-380, -345, 0])
transformed_vertices += offset
print(f"已对模型 {obj_name} 应用偏移: {offset}")
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
meshes.append(mesh)
# obj_path_arrange = os.path.join(original_obj_pid_dir, "arrange")
obj_path_arrange = cache_type_setting_dir
if not os.path.exists(obj_path_arrange):
os.mkdir(obj_path_arrange)
obj_path_arrange_obj = os.path.join(obj_path_arrange, obj_name)
print("obj_path_arrange_obj", obj_path_arrange_obj)
mesh.compute_vertex_normals()
o3d.io.write_triangle_mesh(obj_path_arrange_obj, mesh,write_triangle_uvs=True)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--pid_file", type=str, required=True, help="批次号, 也是json文件名")
parser.add_argument("--workdir", type=str, required=True, help="本代码文件所在的目录")
parser.add_argument("--oss_config", type=str, required=True)
args = parser.parse_args()
download_transform_save_by_json(args.pid_file, args.workdir, args.oss_config)

847
get_lowest_position_of_center_ext.py

@ -0,0 +1,847 @@
import open3d as o3d
import numpy as np
import copy
import time
from get_lowest_position_of_z_out import get_lowest_position_of_z_out
# 对外部提供的获取最低z的接口
def get_lowest_position_of_center_out2(obj_path):
total_matrix = np.eye(4)
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
voxel_size = 3
return get_lowest_position_of_center(mesh_obj, obj_path, total_matrix, voxel_size)
def calculate_rotation_and_center_of_mass(angle_x, angle_y, angle_z, points):
"""计算某一组旋转角度后的重心"""
# 计算绕X轴、Y轴和Z轴的旋转矩阵
R_x = np.array([
[1, 0, 0],
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
])
R_y = np.array([
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
[0, 1, 0],
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
])
R_z = np.array([
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
[0, 0, 1]
])
# 综合旋转矩阵
R = R_z @ R_y @ R_x
# 执行旋转
rotated_points = points @ R.T
# 计算最小z值
min_z = np.min(rotated_points[:, 2])
# 计算平移向量,将最小Z值平移到0
translation_vector = np.array([0, 0, -min_z])
rotated_points += translation_vector
# 计算重心
center_of_mass = np.mean(rotated_points, axis=0)
return center_of_mass[2], angle_x, angle_y, angle_z
def parallel_rotation4(points, angle_step=4):
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
min_center = float('inf')
for angle_x in range(-45, 45, angle_step):
for angle_y in range(0, 360, angle_step):
center_z, ax, ay, _ = calculate_rotation_and_center_of_mass(angle_x, angle_y, 0, points)
if center_z < min_center:
min_center = center_z
best_angle_x = ax
best_angle_y = ay
return (best_angle_x, best_angle_y, 0, min_center)
import numpy as np
from numba import cuda
import math
# CUDA核函数:计算所有旋转角度下的重心高度
@cuda.jit
def compute_centers_kernel(points, centers, angle_x_start, angle_x_step, angle_y_start, angle_y_step, num_x, num_y):
i = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x
j = cuda.blockIdx.y * cuda.blockDim.y + cuda.threadIdx.y
if i >= num_x or j >= num_y:
return
# 获取整数角度值
angle_x = angle_x_start + i * angle_x_step
angle_y = angle_y_start + j * angle_y_step
rx = math.radians(float(angle_x)) # 使用 float() 进行转换
ry = math.radians(float(angle_y))
rz = 0.0
# 计算旋转矩阵
cos_x = math.cos(rx)
sin_x = math.sin(rx)
cos_y = math.cos(ry)
sin_y = math.sin(ry)
cos_z = math.cos(rz)
sin_z = math.sin(rz)
# 旋转矩阵: R = R_z * R_y * R_x
R00 = cos_z * cos_y
R01 = cos_z * sin_y * sin_x - sin_z * cos_x
R02 = cos_z * sin_y * cos_x + sin_z * sin_x
R10 = sin_z * cos_y
R11 = sin_z * sin_y * sin_x + cos_z * cos_x
R12 = sin_z * sin_y * cos_x - cos_z * sin_x
R20 = -sin_y
R21 = cos_y * sin_x
R22 = cos_y * cos_x
n = points.shape[0]
min_z = 1e10
# 第一遍:计算最小Z值
for k in range(n):
x = points[k, 0]
y = points[k, 1]
z = points[k, 2]
x_rot = R00 * x + R01 * y + R02 * z
y_rot = R10 * x + R11 * y + R12 * z
z_rot = R20 * x + R21 * y + R22 * z
if z_rot < min_z:
min_z = z_rot
total_z = 0.0
# 第二遍:平移并计算Z坐标和
for k in range(n):
x = points[k, 0]
y = points[k, 1]
z = points[k, 2]
x_rot = R00 * x + R01 * y + R02 * z
y_rot = R10 * x + R11 * y + R12 * z
z_rot = R20 * x + R21 * y + R22 * z
z_trans = z_rot - min_z
total_z += z_trans
center_z = total_z / n
centers[i, j] = center_z
# CUDA版本的并行旋转计算
def parallel_rotation4_cuda(points, angle_step=4):
angle_x_start = -45
angle_x_end = 45
angle_y_start = 0
angle_y_end = 360
num_x = int((angle_x_end - angle_x_start) / angle_step)
num_y = int((angle_y_end - angle_y_start) / angle_step)
# 将点云数据复制到GPU
d_points = cuda.to_device(points.astype(np.float32))
d_centers = cuda.device_array((num_x, num_y), dtype=np.float32)
# 配置线程块和网格
threadsperblock = (16, 16)
blockspergrid_x = (num_x + threadsperblock[0] - 1) // threadsperblock[0]
blockspergrid_y = (num_y + threadsperblock[1] - 1) // threadsperblock[1]
blockspergrid = (blockspergrid_x, blockspergrid_y)
# 启动核函数
compute_centers_kernel[blockspergrid, threadsperblock](
d_points, d_centers, angle_x_start, angle_step, angle_y_start, angle_step, num_x, num_y
)
# 将结果复制回主机
centers = d_centers.copy_to_host()
# 找到最小重心值的索引
min_index = np.argmin(centers)
i = min_index // num_y
j = min_index % num_y
best_angle_x = angle_x_start + i * angle_step
best_angle_y = angle_y_start + j * angle_step
min_center = centers[i, j]
return best_angle_x, best_angle_y, 0, min_center
def read_mesh(obj_path, simple=True):
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
return mesh_obj
def compute_mesh_center2(vertices):
"""
计算网格质心
参数:
vertices: 顶点坐标数组形状为(N, 3)的NumPy数组或列表
返回:
centroid: 质心坐标的NumPy数组 [x, y, z]
"""
if len(vertices) == 0:
raise ValueError("顶点数组不能为空")
n = len(vertices) # 顶点数量
# 初始化坐标累加器
sum_x, sum_y, sum_z = 0.0, 0.0, 0.0
start_time1 = time.time()
# 遍历所有顶点累加坐标值
for vertex in vertices:
sum_x += vertex[0]
sum_y += vertex[1]
sum_z += vertex[2]
print("compute_mesh_center1 time", time.time()-start_time1)
# 计算各坐标轴的平均值
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
return centroid
def compute_mesh_center(vertices):
"""
计算网格质心优化版
参数:
vertices: 顶点坐标数组形状为(N, 3)的NumPy数组或列表
返回:
centroid: 质心坐标的NumPy数组 [x, y, z]
"""
if len(vertices) == 0:
raise ValueError("顶点数组不能为空")
# 确保vertices是NumPy数组
vertices_np = np.asarray(vertices)
# 使用NumPy的mean函数直接计算均值(向量化操作)
centroid = np.mean(vertices_np, axis=0)
return centroid
def get_lowest_position_of_center_ext3(mesh_obj, obj_path, voxel_size = 3):
best_angle_x, best_angle_y, best_angle_z, z_mean_min, pcd_transformed= get_lowest_position_of_center2(mesh_obj, obj_path, voxel_size)
return best_angle_x, best_angle_y, best_angle_z, z_mean_min
def get_lowest_position_of_center_ext2(mesh_obj, obj_path, total_matrix, voxel_size):
# total_matrix, z_mean_min = get_lowest_position_of_center(obj_path, total_matrix, voxel_size)
temp_matrix, z_mean_min = get_lowest_position_of_center(mesh_obj, obj_path, np.eye(4), voxel_size)
# print("temp_matrix=",temp_matrix,voxel_size,mesh_obj)
total_matrix = temp_matrix @ total_matrix
return total_matrix, z_mean_min
def get_lowest_position_of_center_ext(obj_path, total_matrix):
temp_matrix, z_max = get_lowest_position_of_z_out(obj_path)
total_matrix = temp_matrix @ total_matrix
return total_matrix, z_max
def down_sample(pcd, voxel_size, farthest_sample = False):
original_num = len(pcd.points)
target_samples = 1500 # 1000
num_samples = min(target_samples, original_num)
# 第一步:使用体素下采样快速减少点数量
# voxel_size = 3
if farthest_sample:
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
else:
pcd_voxel = pcd.voxel_down_sample(voxel_size)
down_num = len(pcd_voxel.points)
# print(f"original_num={original_num}, down_num={down_num}")
# 第二步:仅在必要时进行最远点下采样
if len(pcd_voxel.points) > target_samples and False:
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
else:
pcd_downsampled = pcd_voxel
return pcd_downsampled
def get_lowest_position_of_center(mesh_obj, obj_path, total_matrix, voxel_size):
# print(f"obj_path={obj_path}, get_lowest_position_of_center voxel_size={voxel_size}")
start_time1 = time.time()
vertices = np.asarray(mesh_obj.vertices)
# 确保网格有顶点
if len(vertices) == 0:
# raise ValueError(f"Mesh has no vertices: {obj_path}")
print(f"Warning: Mesh has no vertices: {mesh_obj}")
return None
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
# print("voxel_size",voxel_size,obj_path, len(pcd.points), len(mesh_obj.vertices))
# 对点云进行下采样(体素网格法)
#"""
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
if len(np.asarray(pcd_downsampled.points)) <= 0:
bbox = pcd.get_axis_aligned_bounding_box()
volume = bbox.volume()
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
# 处理体积为零的情况
if volume <= 0:
# 计算点云的实际范围
points = np.asarray(pcd.points)
if len(points) > 0:
min_bound = np.min(points, axis=0)
max_bound = np.max(points, axis=0)
extent = max_bound - min_bound
# 确保最小维度至少为0.01
min_dimension = max(0.01, np.min(extent))
volume = min_dimension ** 3
else:
volume = 1.0 # 最后的安全回退
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
# 安全计算密度 - 防止除零错误
if len(pcd.points) > 0 and volume > 0:
original_density = len(pcd.points) / volume
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
else:
# 当点数为0或体积为0时使用默认体素大小
voxel_size = 1.0 # 默认值
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
original_num = len(pcd.points)
target_samples = 1000
num_samples = min(target_samples, original_num)
# print("get_lowest_position_of_center1 time", time.time()-start_time1)
start_time2 = time.time()
# 确保下采样后有点云
if len(np.asarray(pcd_downsampled.points)) == 0:
# 使用原始点云作为后备
pcd_downsampled = pcd
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
points = np.asarray(pcd_downsampled.points)
# 初始化最小重心Y的值
min_center_of_mass_y = float('inf')
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation4(points, angle_step=3)
# 使用最佳角度进行旋转并平移obj
pcd_transformed = copy.deepcopy(mesh_obj)
# 最佳角度旋转
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
pcd_transformed.rotate(R_x)
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
pcd_transformed.rotate(R_y)
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
pcd_transformed.rotate(R_z)
T_x = np.eye(4)
T_x[:3, :3] = R_x
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_x @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
T_y = np.eye(4)
T_y[:3, :3] = R_y
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_y @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
T_z = np.eye(4)
T_z[:3, :3] = R_z
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_z @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
#试着旋转180,让脸朝上
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
T_trans1 = np.eye(4)
T_trans1[:3, 3] = translation_vector
total_matrix = T_trans1 @ total_matrix
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean1 = np.mean(vertices[:, 2])
z_max1 = np.max(vertices[:, 2])
angle_rad = np.pi
#print("旋转前质心:", pcd_transformed.get_center())
#print("旋转前点示例:", np.asarray(pcd_transformed.vertices)[:3])
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
centroid = pcd_transformed.get_center()
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
center_point = compute_mesh_center(mesh_obj.vertices)
# 构建绕中心点旋转的变换矩阵[3](@ref)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
R_y180 = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
T_rotate = np.eye(4)
T_rotate[:3, :3] = R_y180
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_rotate @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
#print("旋转后质心:", pcd_transformed.get_center())
#print("旋转后点示例:", np.asarray(pcd_transformed.vertices)[:3])
#
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
max_z = np.max(vertices[:, 2])
# print("min_z1", min_z, obj_path)
translation_vector = np.array([0,0,-min_z,])
# translation_vector = np.array([0,0,-min_z + (min_z-max_z),])
# print("translation_vector1",translation_vector)
pcd_transformed.translate(translation_vector)
T_trans2 = np.eye(4)
T_trans2[:3, 3] = translation_vector
translation = total_matrix[:3, 3]
# print("translation_vector2",translation_vector)
# print(1,translation)
total_matrix = T_trans2 @ total_matrix
translation = total_matrix[:3, 3]
# print(2,translation)
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean2 = np.mean(vertices[:, 2])
z_max2 = np.max(vertices[:, 2])
# print(f"get_lowest_position_of_center z_max1={z_max1}, z_max2={z_max2}, len={len(pcd_transformed.vertices)}, obj_path={obj_path}")
if (z_mean2 > z_mean1):
# if (z_max2 > z_max1):
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
centroid = pcd_transformed.get_center()
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
center_point = compute_mesh_center(mesh_obj.vertices)
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
# 构建反向旋转矩阵
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
T_rotate_inv = np.eye(4)
T_rotate_inv[:3, :3] = R_y
# 完整的反向绕中心旋转矩阵
T_rot_center_inv = T_origin_to_center @ T_rotate_inv @ T_center_to_origin
total_matrix = T_rot_center_inv @ total_matrix
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
# print("min_z2", min_z, obj_path)
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
T_trans3 = np.eye(4)
T_trans3[:3, 3] = translation_vector
total_matrix = T_trans3 @ total_matrix
# z_mean_min = min(z_mean1, z_mean2)
z_max_min = min(z_max1, z_max2)
# print("get_lowest_position_of_center2 time", time.time()-start_time2)
return total_matrix, z_max_min
import requests
import json
import re
def is_valid_float_string(s):
# 匹配科学计数法或普通小数,允许开头和末尾的空格
pattern = r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$'
return bool(re.match(pattern, s.strip()))
def safe_convert_to_float(s):
"""
尝试将字符串安全转换为float处理一些不完整情况
"""
s = s.strip().lower() # 去除空格,统一为小写
# 处理空字符串或完全非数字的情况
if not s or s in ['na', 'nan', 'inf', 'null', 'none']:
return None # 或者可以根据需要返回 float('nan')
# 检查是否为有效的浮点数字符串
if is_valid_float_string(s):
return float(s)
# 处理类似 '0.00000000e' 的情况(缺少指数)
if s.endswith('e'):
# 尝试添加指数 '0' -> 'e0'
try:
return float(s + '0')
except ValueError:
pass # 如果添加'e0'后仍然失败,则继续下面的异常处理
# 更激进的清理:移除非数字、小数点、负号和指数e以外的所有字符
# 注意:这可能破坏有特定格式的字符串,慎用
cleaned_s = re.sub(r'[^\d\.eE-]', '', s)
try:
return float(cleaned_s)
except ValueError:
pass
# 如果所有尝试都失败,返回None或抛出异常
return None
def string_to_matrix(data_string):
"""
将字符串转换为NumPy浮点数矩阵并处理可能的转换错误
"""
# 分割字符串
lines = data_string.strip().split(';')
matrix = []
for line in lines:
num_list = line.split()
float_row = []
for num in num_list:
# 使用安全转换函数
value = safe_convert_to_float(num)
if value is None:
# 处理转换失败,例如记录日志、使用NaN代替
print(f"警告: 无法转换字符串 '{num}',将其替换为NaN。")
value = np.nan # 用NaN标记缺失或无效值
float_row.append(value)
matrix.append(float_row)
return np.array(matrix)
import ast
def get_lowest_position_of_center_net(printId, total_matrix):
print("get_lowest_position_of_center_net", printId)
url = f"https://mp.api.suwa3d.com/api/printOrder/infoByPrintId?printId={printId}"
res = requests.get(url)
datas = res.json()["data"]["layout"]
print("datas=", datas)
homo_matrix_str = datas.get("homo_matrix")
print("homo_matrix_str=", homo_matrix_str)
# 1. 去除字符串首尾的方括号
str_cleaned = homo_matrix_str.strip('[]')
# 2. 按行分割字符串
rows = str_cleaned.split('\n')
# 3. 修复:处理每行中的逗号问题
matrix_list = []
for row in rows:
if row.strip() == '':
continue
# 去除行首尾的方括号和空格
row_cleaned = row.strip(' []')
# 按逗号分割,但过滤掉空字符串
elements = [elem.strip() for elem in row_cleaned.split(',') if elem.strip() != '']
# 进一步清理每个元素:去除可能残留的逗号和方括号
cleaned_elements = []
for elem in elements:
# 去除元素中可能存在的逗号、方括号和空格
elem_cleaned = elem.strip(' ,[]')
if elem_cleaned != '':
cleaned_elements.append(elem_cleaned)
if cleaned_elements: # 只添加非空行
matrix_list.append(cleaned_elements)
print("matrix_list=", matrix_list)
# 4. 安全地转换为浮点数数组(带错误处理)
try:
reconstructed_matrix = np.array(matrix_list, dtype=float)
except ValueError as e:
print(f"转换矩阵时出错: {e}")
print("尝试逐个元素转换...")
# 逐个元素转换,便于定位问题元素
float_matrix = []
for i, row in enumerate(matrix_list):
float_row = []
for j, elem in enumerate(row):
try:
# 再次清理元素并转换
cleaned_elem = elem.strip(' ,')
float_val = float(cleaned_elem)
float_row.append(float_val)
except ValueError as ve:
print(f"无法转换的元素: 行{i}, 列{j}, 值'{elem}', 错误: {ve}")
# 可以选择设置为0或NaN,或者抛出异常
float_row.append(0.0) # 或者 np.nan
float_matrix.append(float_row)
reconstructed_matrix = np.array(float_matrix, dtype=float)
layout_z = datas.get("layout_z", 0)
print("layout_z", layout_z)
reconstructed_matrix = reconstructed_matrix @ total_matrix
print("reconstructed_matrix=", reconstructed_matrix)
return reconstructed_matrix, layout_z
def get_lowest_position_of_center2(mesh_obj, obj_path, voxel_size = 3):
# mesh_obj = read_mesh(obj_path)
vertices = np.asarray(mesh_obj.vertices)
# 确保网格有顶点
if len(vertices) == 0:
print(f"Warning: Mesh has no vertices: {obj_path}")
return None
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
# 对点云进行下采样(体素网格法
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
if len(np.asarray(pcd_downsampled.points)) <= 0:
bbox = pcd.get_axis_aligned_bounding_box()
volume = bbox.volume()
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
# 处理体积为零的情况
if volume <= 0:
# 计算点云的实际范围
points = np.asarray(pcd.points)
if len(points) > 0:
min_bound = np.min(points, axis=0)
max_bound = np.max(points, axis=0)
extent = max_bound - min_bound
# 确保最小维度至少为0.01
min_dimension = max(0.01, np.min(extent))
volume = min_dimension ** 3
else:
volume = 1.0 # 最后的安全回退
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
# 安全计算密度 - 防止除零错误
if len(pcd.points) > 0 and volume > 0:
original_density = len(pcd.points) / volume
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
else:
# 当点数为0或体积为0时使用默认体素大小
voxel_size = 1.0 # 默认值
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
# 确保下采样后有点云
if len(np.asarray(pcd_downsampled.points)) == 0:
# 使用原始点云作为后备
pcd_downsampled = pcd
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
points = np.asarray(pcd_downsampled.points)
# 初始化最小重心Y的值
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
# 旋转并计算最优角度:绕X、Y、Z轴进行每度的旋转
best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y = parallel_rotation4(points, angle_step=3)
# print("best_angle1", best_angle_x, best_angle_y, best_angle_z)
# 使用最佳角度进行旋转并平移obj
pcd_transformed = copy.deepcopy(mesh_obj)
# 最佳角度旋转
"""
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
pcd_transformed.rotate(R_x)
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
pcd_transformed.rotate(R_y)
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
pcd_transformed.rotate(R_z)
#"""
aabb = pcd_transformed.get_axis_aligned_bounding_box()
center_point = aabb.get_center()
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean1 = np.mean(vertices[:, 2])
angle_rad = np.pi
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
best_angle_y += angle_rad / np.pi * 180
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean2 = np.mean(vertices[:, 2])
# print("z_mean",z_mean1,z_mean2,len(pcd_transformed.vertices),obj_path)
if z_mean2 > z_mean1:
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
aabb = pcd_transformed.get_axis_aligned_bounding_box()
center_point = aabb.get_center()
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
best_angle_y += -angle_rad / np.pi * 180
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
z_mean_min = min(z_mean1, z_mean2)
angle_z_delta = arrange_box_correctly(pcd_transformed, voxel_size)
best_angle_z += angle_z_delta
# print("angle_z_delta", angle_z_delta, best_angle_z)
return best_angle_x, best_angle_y, best_angle_z, z_mean_min, pcd_transformed
def arrange_box_correctly(obj_transformed, voxel_size):
vertices = np.asarray(obj_transformed.vertices)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
# 降采样与特征计算
pcd_downsampled = down_sample(pcd, voxel_size)
original_num = len(pcd.points)
target_samples = 1000
num_samples = min(target_samples, original_num)
points = np.asarray(pcd_downsampled.points)
cov = np.cov(points.T)
center = obj_transformed.get_center()
# 特征分解与方向约束(关键修改点)
eigen_vals, eigen_vecs = np.linalg.eigh(cov)
max_axis = eigen_vecs[:, np.argmax(eigen_vals)]
# 强制主方向向量X分量为正(指向右侧)
if max_axis[0] < 0 or (max_axis[0] == 0 and max_axis[1] < 0):
max_axis = -max_axis
target_dir = np.array([1, 0]) # 目标方向为X正轴
current_dir = max_axis[:2] / np.linalg.norm(max_axis[:2])
dot_product = np.dot(current_dir, target_dir)
# print("dot_product", dot_product)
if dot_product < 0.8: # 阈值控制方向敏感性(建议0.6~0.9)
max_axis = -max_axis # 强制翻转方向
# 计算旋转角度
angle_z = np.arctan2(max_axis[1], max_axis[0]) % (2 * np.pi)
if max_axis[0] <= 0 and max_axis[1] <= 0:
angle_z += np.pi
# print("max_axis2", max_axis, -angle_z, np.rad2deg(-angle_z))
R = o3d.geometry.get_rotation_matrix_from_axis_angle([0, 0, -angle_z])
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = center - R.dot(center) # 保持中心不变
obj_transformed.transform(T)
return np.rad2deg(-angle_z)

352
get_lowest_position_of_z_out.py

@ -0,0 +1,352 @@
import open3d as o3d
import numpy as np
import copy
import time
import argparse
"""
对外部提供的获取最低z的接口
get_lowest_position_of_z_out
参数:
obj_path, 模型数据路径
返回:
total_matrix: 旋转矩阵
z_max: Z最高点
"""
def get_lowest_position_of_z_out(obj_path):
mesh_obj = o3d.io.read_triangle_mesh(obj_path)
total_matrix = np.eye(4)
voxel_size = 3
# print(f"obj_path={obj_path}, get_lowest_position_of_center voxel_size={voxel_size}")
start_time1 = time.time()
vertices = np.asarray(mesh_obj.vertices)
# 确保网格有顶点
if len(vertices) == 0:
# raise ValueError(f"Mesh has no vertices: {obj_path}")
print(f"Warning: Mesh has no vertices: {mesh_obj}")
return None
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(vertices)
# print("voxel_size",voxel_size,obj_path, len(pcd.points), len(mesh_obj.vertices))
# 对点云进行下采样(体素网格法)
#"""
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
if len(np.asarray(pcd_downsampled.points)) <= 0:
bbox = pcd.get_axis_aligned_bounding_box()
volume = bbox.volume()
# print(f"len(pcd.points)={len(pcd.points)}, volume={volume}")
# 处理体积为零的情况
if volume <= 0:
# 计算点云的实际范围
points = np.asarray(pcd.points)
if len(points) > 0:
min_bound = np.min(points, axis=0)
max_bound = np.max(points, axis=0)
extent = max_bound - min_bound
# 确保最小维度至少为0.01
min_dimension = max(0.01, np.min(extent))
volume = min_dimension ** 3
else:
volume = 1.0 # 最后的安全回退
print(f"Warning: Zero volume detected, using approximated volume {volume:.6f} for {obj_path}")
# 安全计算密度 - 防止除零错误
if len(pcd.points) > 0 and volume > 0:
original_density = len(pcd.points) / volume
voxel_size = max(0.01, min(10.0, 0.5 / (max(1e-6, original_density) ** 0.33)))
else:
# 当点数为0或体积为0时使用默认体素大小
voxel_size = 1.0 # 默认值
print(f"Recalculated voxel_size: {voxel_size} for {obj_path}")
pcd_downsampled = down_sample(pcd, voxel_size)
pcd_downsampled.paint_uniform_color([0, 0, 1])
original_num = len(pcd.points)
target_samples = 1000
num_samples = min(target_samples, original_num)
# print("get_lowest_position_of_center1 time", time.time()-start_time1)
start_time2 = time.time()
# 确保下采样后有点云
if len(np.asarray(pcd_downsampled.points)) == 0:
# 使用原始点云作为后备
pcd_downsampled = pcd
print(f"Warning: Using original point cloud for {obj_path} as downsampling produced no points")
points = np.asarray(pcd_downsampled.points)
# 初始化最小重心Y的值
max_z_of_mass_y = float('inf')
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
best_angle_x, best_angle_y, best_angle_z, max_z_of_mass_y = parallel_rotation(points, angle_step=3)
# 使用最佳角度进行旋转并平移obj
pcd_transformed = copy.deepcopy(mesh_obj)
# 最佳角度旋转
R_x = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(best_angle_x))
pcd_transformed.rotate(R_x)
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(best_angle_y))
pcd_transformed.rotate(R_y)
R_z = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(best_angle_z))
pcd_transformed.rotate(R_z)
T_x = np.eye(4)
T_x[:3, :3] = R_x
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_x @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
T_y = np.eye(4)
T_y[:3, :3] = R_y
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_y @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
T_z = np.eye(4)
T_z[:3, :3] = R_z
center_point = compute_mesh_center(mesh_obj.vertices)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_z @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
#试着旋转180,让脸朝上
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
T_trans1 = np.eye(4)
T_trans1[:3, 3] = translation_vector
total_matrix = T_trans1 @ total_matrix
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean1 = np.mean(vertices[:, 2])
z_max1 = np.max(vertices[:, 2])
angle_rad = np.pi
#print("旋转前质心:", pcd_transformed.get_center())
#print("旋转前点示例:", np.asarray(pcd_transformed.vertices)[:3])
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
centroid = pcd_transformed.get_center()
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
center_point = compute_mesh_center(mesh_obj.vertices)
# 构建绕中心点旋转的变换矩阵[3](@ref)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
R_y180 = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * angle_rad)
T_rotate = np.eye(4)
T_rotate[:3, :3] = R_y180
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
T_rot_center = T_origin_to_center @ T_rotate @ T_center_to_origin
total_matrix = T_rot_center @ total_matrix
#print("旋转后质心:", pcd_transformed.get_center())
#print("旋转后点示例:", np.asarray(pcd_transformed.vertices)[:3])
#
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
max_z = np.max(vertices[:, 2])
# print("min_z1", min_z, obj_path)
translation_vector = np.array([0,0,-min_z,])
# translation_vector = np.array([0,0,-min_z + (min_z-max_z),])
# print("translation_vector1",translation_vector)
pcd_transformed.translate(translation_vector)
T_trans2 = np.eye(4)
T_trans2[:3, 3] = translation_vector
translation = total_matrix[:3, 3]
# print("translation_vector2",translation_vector)
# print(1,translation)
total_matrix = T_trans2 @ total_matrix
translation = total_matrix[:3, 3]
# print(2,translation)
# 计算 z 坐标均值
vertices = np.asarray(pcd_transformed.vertices)
z_mean2 = np.mean(vertices[:, 2])
z_max2 = np.max(vertices[:, 2])
# print(f"get_lowest_position_of_center z_max1={z_max1}, z_max2={z_max2}, len={len(pcd_transformed.vertices)}, obj_path={obj_path}")
if (z_mean2 > z_mean1):
# if (z_max2 > z_max1):
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
centroid = pcd_transformed.get_center()
aabb = pcd_transformed.get_axis_aligned_bounding_box()
# center_point = aabb.get_center()
center_point = compute_mesh_center(mesh_obj.vertices)
pcd_transformed.translate(-center_point)
pcd_transformed.rotate(R_y, center=(0, 0, 0))
pcd_transformed.translate(center_point)
T_center_to_origin = np.eye(4)
T_center_to_origin[:3, 3] = -center_point
T_origin_to_center = np.eye(4)
T_origin_to_center[:3, 3] = center_point
# 构建反向旋转矩阵
R_y = pcd_transformed.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * -angle_rad)
T_rotate_inv = np.eye(4)
T_rotate_inv[:3, :3] = R_y
# 完整的反向绕中心旋转矩阵
T_rot_center_inv = T_origin_to_center @ T_rotate_inv @ T_center_to_origin
total_matrix = T_rot_center_inv @ total_matrix
vertices = np.asarray(pcd_transformed.vertices)
# 计算平移向量,将最小Y值平移到0
min_z = np.min(vertices[:, 2])
# print("min_z2", min_z, obj_path)
translation_vector = np.array([0,0,-min_z,])
pcd_transformed.translate(translation_vector)
T_trans3 = np.eye(4)
T_trans3[:3, 3] = translation_vector
total_matrix = T_trans3 @ total_matrix
# z_mean_min = min(z_mean1, z_mean2)
z_max = min(z_max1, z_max2)
# print("get_lowest_position_of_center2 time", time.time()-start_time2)
return total_matrix, z_max
def calculate_rotation_and_top_of_mass(angle_x, angle_y, angle_z, points):
"""计算某一组旋转角度后的重心"""
# 计算绕X轴、Y轴和Z轴的旋转矩阵
R_x = np.array([
[1, 0, 0],
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
])
R_y = np.array([
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
[0, 1, 0],
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
])
R_z = np.array([
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
[0, 0, 1]
])
# 综合旋转矩阵
R = R_z @ R_y @ R_x
# 执行旋转
rotated_points = points @ R.T
# 计算最小z值
min_z = np.min(rotated_points[:, 2])
# 计算平移向量,将最小Z值平移到0
translation_vector = np.array([0, 0, -min_z])
rotated_points += translation_vector
top_of_mass = np.max(rotated_points, axis=0)
return top_of_mass[2], angle_x, angle_y, angle_z
def parallel_rotation(points, angle_step=4):
"""仅绕 Y 轴旋转(假设 X/Z 轴不影响目标函数)"""
max_top = float('inf')
for angle_x in range(-90, 90, angle_step):
for angle_y in range(0, 360, angle_step):
max_z, ax, ay, _ = calculate_rotation_and_top_of_mass(angle_x, angle_y, 0, points)
if max_z < max_top:
max_top = max_z
best_angle_x = ax
best_angle_y = ay
return (best_angle_x, best_angle_y, 0, max_top)
def compute_mesh_center(vertices):
if len(vertices) == 0:
raise ValueError("顶点数组不能为空")
# 确保vertices是NumPy数组
vertices_np = np.asarray(vertices)
# 使用NumPy的mean函数直接计算均值(向量化操作)
centroid = np.mean(vertices_np, axis=0)
return centroid
def down_sample(pcd, voxel_size, farthest_sample = False):
original_num = len(pcd.points)
target_samples = 1500 # 1000
num_samples = min(target_samples, original_num)
# 第一步:使用体素下采样快速减少点数量
# voxel_size = 3
if farthest_sample:
pcd_voxel = pcd.farthest_point_down_sample(num_samples=num_samples)
else:
pcd_voxel = pcd.voxel_down_sample(voxel_size)
down_num = len(pcd_voxel.points)
# print(f"original_num={original_num}, down_num={down_num}")
# 第二步:仅在必要时进行最远点下采样
if len(pcd_voxel.points) > target_samples and False:
pcd_downsampled = pcd_voxel.farthest_point_down_sample(num_samples=num_samples)
else:
pcd_downsampled = pcd_voxel
return pcd_downsampled
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--obj_path", type=str, required=True, help="batchobj_path_id")
args = parser.parse_args()
obj_path = args.obj_path
max, z = get_lowest_position_of_z_out(obj_path)

179
grid_near_three.py

@ -0,0 +1,179 @@
import os
import time
import open3d as o3d
import numpy as np
def make_near_dict(base_dir,compact_dir):
""""""
# 用于存储结果的字典
results = {}
# 遍历目录中的所有 .ply 文件
for ply_file in os.listdir(base_dir):
# 检查文件是否为 .ply 格式
if ply_file.endswith('.ply'):
ply_path = os.path.join(base_dir, ply_file)
compact_ply_path = os.path.join(compact_dir, ply_file)
if os.path.exists(compact_ply_path):
ply_read_path = compact_ply_path
else:
ply_read_path = ply_path
# 读取点云
pcd = o3d.io.read_point_cloud(ply_read_path)
# 获取点云的点数据
points = np.asarray(pcd.points)
# 计算质心
centroid = np.mean(points, axis=0)
# 计算 Y 轴最小值
min_y_value = np.min(points[:, 1]) # Y 轴最小值
max_y_value = np.max(points[:, 1])
# 计算 X 轴最小值
min_x_value = np.min(points[:, 0]) # X 轴最小值
max_x_value = np.max(points[:, 0]) # X 轴最小值
#ply_pid = ply_file.split("_")[0]
# 将结果存入字典
results[ply_file] = {
"centroid": centroid,
"min_x_value": min_x_value,
"min_y_value": min_y_value,
"max_x_value": max_x_value,
"max_y_value": max_y_value,
}
# 打印结果
# for ply_file, values in results.items():
# print(f"文件: {ply_file}")
# print(f" 质心: {values['centroid']}")
# print(f" X 轴最小值: {values['min_x_value']}")
# print(f" Y 轴最小值: {values['min_y_value']}")
# 计算每个ply需要触碰检测的
check_touch_dict = {}
for ply_file in results.keys():
print(ply_file)
#ply_pid = ply_file.split("_")[0]
#print(ply_pid)
bounds_min_x = results[ply_file]["min_x_value"]
bounds_min_y = results[ply_file]["min_y_value"]
#bounds_center = results[ply_file]["centroid"]
need_check_list = []
need_values_dict = {}
for ply_file_near in results.keys():
#print(ply_file_near)
if ply_file!= ply_file_near:
bounds_max_x = results[ply_file_near]["max_x_value"]
bounds_max_y = results[ply_file_near]["max_y_value"]
# if ply_file == "151140_9cm_x1=30.578+41.705+90.753.ply":
# print("-"*50)
# print("主::",ply_file)
# print("从::",ply_file_near)
# print(f"center_x{bounds_center[0]}")
# print(f"center_y{bounds_center[1]}")
# print(f"bounds_max_x{bounds_max_x}")
# print(f"bounds_max_y{bounds_max_y}")
# time.sleep(3)
# 235605_12cm_x1=33.774+30.837+120.344.ply
# if bounds_center[0]<bounds_max_x and bounds_center[1]<bounds_max_y:
# print("添加",ply_file_near)
# need_check_list.append(ply_file_near)
#if bounds_near_center[0]>bounds_min_x and bounds_near_center[1]>bounds_min_y:
#print("-"*50)
# print(f"bounds_min_x{bounds_min_x}")
# print(f"bounds_max_x{bounds_max_x}")
x_dis = bounds_min_x - bounds_max_x
y_dis = bounds_min_y - bounds_max_y
#print(f"x_dis=={x_dis}")
#print(f"y_dis=={y_dis}")
#if ply_file=="158040_15cm_x1=80.682+89.345+152.468.ply":
#if ply_file == "235547_4.8cm_x1=29.339+39.528+57.63.ply":
# print("主::",ply_file)
# print("从::",ply_file_near)
#if ply_file == "158040_15cm_x1=80.682+89.345+152.468.ply":
#if ply_file == "151140_9cm_x1=30.578+41.705+90.753.ply":
# print("主::", ply_file)
# print("临近::", ply_file_near)
# time.sleep(3)
if x_dis<-10 and y_dis<-10:
need_check_list.append(ply_file_near)
need_values_dict["need_check_list"] = need_check_list
# need_values_dict["max_x_value"] = bounds_max_x
# need_values_dict["max_y_value"] = bounds_max_y
check_touch_dict[ply_file] = need_values_dict
# print(check_touch_dict)
# print("开始要计算触碰检测的数据")
# for ply_file, values in check_touch_dict.items():
# print("*"*50)
# print(ply_file)
# print(values)
# 去掉离比较远的数据
for check_touch_key,check_touch_values in check_touch_dict.items():
print("-"*50)
#print(check_touch_key)
#print(check_touch_values["need_check_list"])
need_check_list= check_touch_values["need_check_list"]
#print(len(need_check_list))
if len(need_check_list)>2:
ply_A_path = os.path.join(base_dir, check_touch_key)
compact_ply_path = os.path.join(compact_dir, check_touch_key)
if os.path.exists(compact_ply_path):
ply_read_path = compact_ply_path
else:
ply_read_path = ply_A_path
pcd_A = o3d.io.read_point_cloud(ply_read_path)
points_A = np.asarray(pcd_A.points)
distances = []
for i, check_touch in enumerate(need_check_list):
point = results[check_touch]['centroid']
ply_path = os.path.join(base_dir, check_touch)
# 读取当前点云
pcd = o3d.io.read_point_cloud(ply_path)
points = np.asarray(pcd.points)
# 计算点云之间最小点对距离(brute-force)
diff = points_A[:, np.newaxis, :] - points[np.newaxis, :, :] # (N, M, 3)
dists = np.linalg.norm(diff, axis=2) # (N, M)
min_distance = np.min(dists)
#print(f"check_touch: {check_touch}, centroid: {point}, min_distance: {min_distance:.4f}")
distances.append((i, point, min_distance, check_touch))
distances.sort(key=lambda x: x[2])
# 提取最近的 3 个点
nearest_points = distances[:5]
last_elements = [item[-1] for item in nearest_points]
# print(f"nearest_points---------{nearest_points}")
# print(f"check_touch_key--------{check_touch_key}")
# print(f"last_elements--------{last_elements}")
check_touch_dict[check_touch_key]["need_check_list"] = last_elements
return check_touch_dict
# for check_touch_key,check_touch_values in check_touch_dict.items():
# print("*"*50)
# print(check_touch_key)
# print(check_touch_values)
if __name__ == '__main__':
bounds_fix_out_dir = "/data/datasets_20t/type_setting_test_data/print_bounds_fix_data/"
check_touch_dict=make_near_dict(bounds_fix_out_dir)
print(f"check_touch_dict--------------{check_touch_dict}")
"""
{'need_check_list': ['131508_18cm_x1=51.412+87.921+181.446.ply', '239617_12cm_x1=43.987+54.233+120.691.ply']},
"""

6
print.sh

@ -0,0 +1,6 @@
#!/bin/bash
while true; do
python clound_print.py | tee -a output.log
echo "脚本执行完成,等待10秒后重新运行..."
sleep 10
done

291
print_factory_type_setting_obj_run.py

@ -0,0 +1,291 @@
import os
import shutil
import time
import sys
import argparse
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, script_dir)
from print_show_weight_max_obj import make_bbox_for_print,copy_obj_2x
from print_mplot3d_point_cloud_layout import *
from print_merged_many_obj import move_compact_obj_to_file
from test_load_json import load_show_save
from download_print import upload_result
def get_base_directory():
"""获取脚本或可执行文件的基础目录"""
if getattr(sys, 'frozen', False):
# 打包后的可执行文件环境
base_path = os.path.dirname(sys.executable)
else:
# 正常脚本运行环境
base_path = os.path.dirname(os.path.abspath(__file__))
return base_path
from datetime import datetime
import gc
def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=None,batch_id=0,show_chart=True,selected_mode="标准",output_format="JSON",selected_machine="大机型"):
print_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
weight_fix_out_obj_dir = f"{cache_type_setting_dir}/temp/print_weight_fix_data_obj"
weight_fix_out_ply_dir = f"{cache_type_setting_dir}/temp/print_weight_fix_data_ply"
bounds_fix_out_dir = f"{cache_type_setting_dir}/temp/print_bounds_fix_data"
bounds_compact_out_dir = f"{cache_type_setting_dir}/temp/print_bounds_compact_data"
compact_obj_out_dir = f"{cache_type_setting_dir}/temp/print_compact_obj" # 最后的结果
placed_remove_dir = f"{base_original_obj_dir}/place_remove_dir" # 已经放置的放到这个目录
# 获取基础目录
base_path = get_base_directory()
# 获取父目录
parent_dir = os.path.dirname(base_path)
bad_dir = os.path.join(parent_dir, "bad")
full_dir = os.path.join(parent_dir, "full")
# print(bad_dir)
# print(full_dir)
if output_format == "模型":
if os.path.exists(weight_fix_out_obj_dir):
shutil.rmtree(weight_fix_out_obj_dir)
if os.path.exists(weight_fix_out_ply_dir):
shutil.rmtree(weight_fix_out_ply_dir)
if os.path.exists(bounds_fix_out_dir):
shutil.rmtree(bounds_fix_out_dir)
if os.path.exists(bounds_compact_out_dir):
shutil.rmtree(bounds_compact_out_dir)
if os.path.exists(compact_obj_out_dir):
shutil.rmtree(compact_obj_out_dir)
# if os.path.exists(output_folder): # 不需要
# shutil.rmtree(output_folder)
time.sleep(1)
if not os.path.exists(weight_fix_out_obj_dir):
os.makedirs(weight_fix_out_obj_dir)
if not os.path.exists(weight_fix_out_ply_dir):
os.makedirs(weight_fix_out_ply_dir)
if not os.path.exists(bounds_fix_out_dir):
os.mkdir(bounds_fix_out_dir)
if not os.path.exists(bounds_compact_out_dir):
os.makedirs(bounds_compact_out_dir)
if not os.path.exists(compact_obj_out_dir):
os.makedirs(compact_obj_out_dir)
# if not os.path.exists(output_folder): # 不需要
# os.makedirs(output_folder)
print("selected_machine",selected_machine,"selected_mode",selected_mode,"output_format",output_format)
compact_min_dis = True
compact_min_dis2 = False
if selected_mode=="标准" :
compact_min_dis = False
# if output_format=="JSON":
compact_min_dis2 = True
else :
compact_min_dis = True
move_back = True
machine_size = [600, 500, 300]
if selected_machine=="小机型":
machine_size[0] = 380
machine_size[1] = 345
machine_size[2] = 250
start_time = time.time()
copy_obj_2x(base_original_obj_dir)
dict_bad = {}
dict_best_angel = {}
dict_fix = {}
dict_origin = {}
dict_origin_real = {}
dict_total_matrix= {}
dict_mesh_obj = make_bbox_for_print(base_original_obj_dir, weight_fix_out_obj_dir, weight_fix_out_ply_dir,show_chart,dict_bad, dict_best_angel,dict_fix,dict_origin,dict_origin_real,compact_min_dis or compact_min_dis2,dict_total_matrix)
mesh_count = len(dict_mesh_obj)
if mesh_count<=0:
print("选择的文件夹没有模型")
return -1
end_time1 = time.time()
dict_bounds_fix = {}
placed_models= ply_print_layout_platform(weight_fix_out_obj_dir,weight_fix_out_ply_dir,bounds_fix_out_dir,show_chart,dict_mesh_obj,dict_fix,dict_bounds_fix,machine_size,dict_total_matrix)
end_time2 = time.time()
dict_unplaced = {}
dict_compact = {}
if compact_min_dis:
# if output_format=="JSON" :
if True :
can_compact_json = True
if can_compact_json :
compact_mode_for_min_dis1_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
else :
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
else :
compact_mode_for_min_dis(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size)
else:
compact_min_dis2 = False
if compact_min_dis2:
compact_mode_for_min_dis2_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
else :
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
end_time3 = time.time()
#if os.path.exists(placed_remove_dir):
# shutil.rmtree(placed_remove_dir)
if not os.path.exists(placed_remove_dir):
os.makedirs(placed_remove_dir)
if not os.path.exists(bad_dir):
os.makedirs(bad_dir)
if not os.path.exists(full_dir):
os.makedirs(full_dir)
is_small_machine = True if selected_machine=="小机型" else False
use_json = True if output_format=="JSON" else False
save_mesh = True if output_format=="模型" else False
# if use_json:
version = "print_type_setting25.local"
layout_data, send_layout_data = move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,
base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,
placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,
dict_compact,dict_origin,dict_total_matrix,save_mesh,cache_type_setting_dir,
batch_id, print_start_time,selected_machine,selected_mode,version)
# else:
# move_obj_to_compact_bounds(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,placed_remove_dir,dict_bad,bad_dir,full_dir,dict_bounds_fix,dict_compact,dict_origin)
end_time4 = time.time()
#move_compact_obj_to_file(compact_obj_out_dir, output_folder) # 不需要
print("排版完成")
end_time = time.time()
elapsed_seconds = end_time - start_time
elapsed_seconds1 = end_time1 - start_time # 计算重心
elapsed_seconds2 = end_time2 - end_time1 #
elapsed_seconds3 = end_time3 - end_time2
elapsed_seconds4 = end_time4 - end_time3
elapsed_minutes = int(elapsed_seconds // 60)
elapsed_minutes1 = int(elapsed_seconds1 // 60)
elapsed_minutes2 = int(elapsed_seconds2 // 60)
elapsed_minutes3 = int(elapsed_seconds3 // 60)
elapsed_minutes4 = int(elapsed_seconds4 // 60)
print(f"排版总耗时::{elapsed_minutes} 分 / {elapsed_seconds}")
print(f"计算重心::{elapsed_minutes1} 分 / {elapsed_seconds1}")
print(f"排包围盒::{elapsed_minutes2} 分 / {elapsed_seconds2}")
print(f"挪紧凑::{elapsed_minutes3} 分 / {elapsed_seconds3}")
print(f"移动到位置::{elapsed_minutes4} 分 / {elapsed_seconds4}")
dict_mesh_obj.clear()
del dict_mesh_obj
dict_bad.clear()
del dict_bad
dict_fix.clear()
del dict_fix
dict_bounds_fix.clear()
del dict_bounds_fix
dict_unplaced.clear()
del dict_unplaced
dict_compact.clear()
del dict_compact
gc.collect()
# print(base_original_obj_dir,blank_dir,batch_id)
is_screenshot = True
if is_screenshot:
start_time = time.time()
blank_path = get_blank_path(parent_dir, is_small_machine)
load_show_save(base_original_obj_dir, dict_origin, blank_path, batch_id)
elapsed_seconds5 = time.time() - start_time
elapsed_minutes5 = int(elapsed_seconds5 // 60)
print(f"保存截图耗时::{elapsed_minutes5} 分 / {elapsed_seconds5}")
dict_origin.clear()
del dict_origin
gc.collect()
is_upload_result = True
if is_upload_result:
print(f"执行上传-parent_dir={parent_dir},base_original_obj_dir={base_original_obj_dir},batch_id={batch_id}")
# oss_config = f"{base_original_obj_dir}/../print_factory_type_setting_big/download_print/run.yaml"
oss_config = f"{parent_dir}/print_factory_type_setting_big/download_print/run.yaml"
upload_result(base_original_obj_dir, oss_config, batch_id)
print(f"is_test={is_test}")
if is_test :
is_send_layout_data = False
else :
is_send_layout_data = True
# is_send_layout_data = False
if is_send_layout_data:
print(f"send_layout_data={send_layout_data}")
url = 'https://mp.api.suwa3d.com/api/printTypeSettingOrder/printTypeSettingOrderSuccess'
# url = 'http://127.0.0.1:8199/api/typeSettingPrintOrder/printTypeSettingOrderSuccess'
try:
response = requests.post(url, json.dumps(send_layout_data), timeout=30)
#写入文件中 log/request.txt
# with open('log/request.txt', 'w+') as f:
# f.write(json.dumps(send_layout_data, ensure_ascii=False, indent=2))
# 检查响应状态码
if response.status_code == 200:
try:
result = response.json()
print(f"请求成功,返回结果: {result}")
except ValueError as e:
print(f"响应不是有效的JSON格式: {e}")
print(f"响应内容: {response.text}")
else:
print(f"请求失败,状态码: {response.status_code}")
print(f"响应内容: {response.text}")
except requests.exceptions.Timeout:
print(f"请求超时: 连接 {url} 超过30秒未响应")
except requests.exceptions.ConnectionError as e:
print(f"连接错误: 无法连接到服务器 {url}, 错误信息: {e}")
except requests.exceptions.RequestException as e:
print(f"请求异常: {e}")
except Exception as e:
print(f"未知错误: {e}")
return 0
def get_blank_path(parent_dir=None, is_small_machine=False):
if is_small_machine:
return os.path.join(parent_dir, "blank/blank_bias/blank_small.obj")
else:
return os.path.join(parent_dir, "blank/blank_bias/blank2.obj")
def preview(base_original_obj_dir=None, batch_id=0, is_small_machine=False):
base_path = get_base_directory()
parent_dir = os.path.dirname(base_path)
# blank_dir = os.path.join(parent_dir, "blank", "blank_bias")
blank_path = get_blank_path(parent_dir, is_small_machine)
load_show_save(base_original_obj_dir, {}, blank_path, batch_id, True)
if __name__ == '__main__':
# parser = argparse.ArgumentParser()
# parser.add_argument("--batch_id", type=str, required=True, help="batch_id")
# args = parser.parse_args()
# batch_id = args.batch_id
batch_id = "9"
src_dir = batch_id
selected_mode="紧凑" # 标准 紧凑
output_format="JSON" # 模型 JSON
selected_machine = "大机型" # 小机型 大机型
print_factory_type_dir="/root/print_factory_type"
# cache_type_setting_dir=f"/data/datasets_20t/type_setting_test_data/{src_dir}"
cache_type_setting_dir=f"{print_factory_type_dir}/{src_dir}/arrange"
base_original_obj_dir = f"{print_factory_type_dir}/{src_dir}"
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,
batch_id=batch_id,show_chart=False,selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)

193
print_factory_type_setting_obj_run_GUI.py

@ -0,0 +1,193 @@
import os
import shutil
import time
import sys
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, script_dir)
from print_show_weight_max_obj import make_bbox_for_print,copy_obj_2x
from print_mplot3d_point_cloud_layout import *
from print_merged_many_obj import move_compact_obj_to_file
from test_load_json import load_and_show
def get_base_directory():
"""获取脚本或可执行文件的基础目录"""
if getattr(sys, 'frozen', False):
# 打包后的可执行文件环境
base_path = os.path.dirname(sys.executable)
else:
# 正常脚本运行环境
base_path = os.path.dirname(os.path.abspath(__file__))
return base_path
from datetime import datetime
def print_type_setting_obj(base_original_obj_dir=None,cache_type_setting_dir=None,show_chart=True,selected_mode="标准",output_format="JSON",selected_machine="大机型"):
print_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
weight_fix_out_obj_dir = f"{cache_type_setting_dir}/print_weight_fix_data_obj"
weight_fix_out_ply_dir = f"{cache_type_setting_dir}/print_weight_fix_data_ply"
bounds_fix_out_dir = f"{cache_type_setting_dir}/print_bounds_fix_data"
bounds_compact_out_dir = f"{cache_type_setting_dir}/print_bounds_compact_data"
compact_obj_out_dir = f"{cache_type_setting_dir}/print_compact_obj" # 最后的结果
placed_remove_dir = f"{base_original_obj_dir}/place_remove_dir" # 已经放置的放到这个目录
# 获取基础目录
base_path = get_base_directory()
# 获取父目录
parent_dir = os.path.dirname(base_path)
bad_dir = os.path.join(parent_dir, "bad")
full_dir = os.path.join(parent_dir, "full")
blank_dir = os.path.join(parent_dir, "blank")
# print(bad_dir)
# print(full_dir)
# 测试代码
"""
selected_machine = "小机型" # 小机型 大机型
selected_mode="紧凑" # 标准 紧凑
output_format="模型" # 模型 JSON
"""
if output_format == "模型":
if os.path.exists(weight_fix_out_obj_dir):
shutil.rmtree(weight_fix_out_obj_dir)
if os.path.exists(weight_fix_out_ply_dir):
shutil.rmtree(weight_fix_out_ply_dir)
if os.path.exists(bounds_fix_out_dir):
shutil.rmtree(bounds_fix_out_dir)
if os.path.exists(bounds_compact_out_dir):
shutil.rmtree(bounds_compact_out_dir)
if os.path.exists(compact_obj_out_dir):
shutil.rmtree(compact_obj_out_dir)
# if os.path.exists(output_folder): # 不需要
# shutil.rmtree(output_folder)
time.sleep(1)
if not os.path.exists(weight_fix_out_obj_dir):
os.makedirs(weight_fix_out_obj_dir)
if not os.path.exists(weight_fix_out_ply_dir):
os.makedirs(weight_fix_out_ply_dir)
if not os.path.exists(bounds_fix_out_dir):
os.mkdir(bounds_fix_out_dir)
if not os.path.exists(bounds_compact_out_dir):
os.makedirs(bounds_compact_out_dir)
if not os.path.exists(compact_obj_out_dir):
os.makedirs(compact_obj_out_dir)
# if not os.path.exists(output_folder): # 不需要
# os.makedirs(output_folder)
print("selected_machine",selected_machine,"selected_mode",selected_mode,"output_format",output_format)
compact_min_dis = True
compact_min_dis2 = False
if selected_mode=="标准" :
compact_min_dis = False
if output_format=="JSON":
compact_min_dis2 = True
else :
compact_min_dis = True
move_back = True
machine_size = [600, 500, 300]
if selected_machine=="小机型":
machine_size[0] = 380
machine_size[1] = 345
machine_size[2] = 250
start_time = time.time()
copy_obj_2x(base_original_obj_dir)
dict_bad = {}
dict_best_angel = {}
dict_fix = {}
dict_origin = {}
dict_total_matrix= {}
dict_mesh_obj = make_bbox_for_print(base_original_obj_dir, weight_fix_out_obj_dir, weight_fix_out_ply_dir,show_chart,dict_bad, dict_best_angel,dict_fix,dict_origin,compact_min_dis or compact_min_dis2,dict_total_matrix)
mesh_count = len(dict_mesh_obj)
if mesh_count<=0:
print("选择的文件夹没有模型")
return -1
end_time1 = time.time()
dict_bounds_fix = {}
placed_models= ply_print_layout_platform(weight_fix_out_obj_dir,weight_fix_out_ply_dir,bounds_fix_out_dir,show_chart,dict_mesh_obj,dict_fix,dict_bounds_fix,machine_size,dict_total_matrix)
end_time2 = time.time()
dict_unplaced = {}
dict_compact = {}
if compact_min_dis:
if output_format=="JSON" :
can_compact_json = True
if can_compact_json :
compact_mode_for_min_dis1_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
else :
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
else :
compact_mode_for_min_dis(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size)
else:
if compact_min_dis2:
compact_mode_for_min_dis2_json(bounds_fix_out_dir,bounds_compact_out_dir,show_chart,move_back,placed_models,dict_unplaced,dict_bounds_fix,dict_compact,machine_size,dict_total_matrix)
else :
pass_for_min_dis(bounds_fix_out_dir, bounds_compact_out_dir,placed_models, dict_unplaced,dict_bounds_fix,dict_compact)
end_time3 = time.time()
#if os.path.exists(placed_remove_dir):
# shutil.rmtree(placed_remove_dir)
if not os.path.exists(placed_remove_dir):
os.makedirs(placed_remove_dir)
if not os.path.exists(bad_dir):
os.makedirs(bad_dir)
if not os.path.exists(full_dir):
os.makedirs(full_dir)
save_mesh = True if output_format=="模型" else False
# move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,dict_compact,save_mesh,dict_origin,dict_total_matrix,print_start_time)
version = "print_type_setting25.11.21.1"
batch_id = os.path.basename(base_path.rstrip('/'))
move_obj_to_compact_bounds_json(bounds_fix_out_dir,bounds_compact_out_dir,weight_fix_out_obj_dir,
base_original_obj_dir,compact_obj_out_dir,dict_mesh_obj,dict_unplaced,
placed_remove_dir,dict_bad,bad_dir,full_dir,dict_best_angel,dict_bounds_fix,
dict_compact,dict_origin,dict_total_matrix,save_mesh,cache_type_setting_dir,
batch_id, print_start_time,selected_machine,selected_mode,version)
end_time4 = time.time()
#move_compact_obj_to_file(compact_obj_out_dir, output_folder) # 不需要
print("排版完成")
end_time = time.time()
elapsed_seconds = end_time - start_time
elapsed_seconds1 = end_time1 - start_time # 计算重心
elapsed_seconds2 = end_time2 - end_time1 #
elapsed_seconds3 = end_time3 - end_time2
elapsed_seconds4 = end_time4 - end_time3
elapsed_minutes = int(elapsed_seconds // 60)
elapsed_minutes1 = int(elapsed_seconds1 // 60)
elapsed_minutes2 = int(elapsed_seconds2 // 60)
elapsed_minutes3 = int(elapsed_seconds3 // 60)
elapsed_minutes4 = int(elapsed_seconds4 // 60)
print(f"排版总耗时::{elapsed_minutes} 分 / {elapsed_seconds}")
print(f"计算重心::{elapsed_minutes1} 分 / {elapsed_seconds1}")
print(f"排包围盒::{elapsed_minutes2} 分 / {elapsed_seconds2}")
print(f"挪紧凑::{elapsed_minutes3} 分 / {elapsed_seconds3}")
print(f"移动到位置::{elapsed_minutes4} 分 / {elapsed_seconds4}")
# load_and_show(base_original_obj_dir,blank_dir)
return 0
def preview(base_original_obj_dir=None):
base_path = get_base_directory()
parent_dir = os.path.dirname(base_path)
blank_dir = os.path.join(parent_dir, "blank", "blank_bias")
load_and_show(base_original_obj_dir,blank_dir)
if __name__ == '__main__':
src_dir = "12-9" # 1 5.6.5 5.6.4 5.6.1 5.9 temp
cache_type_setting_dir=f"/data/datasets_20t/type_setting_test_data/{src_dir}"
base_original_obj_dir = f"{print_factory_type_dir}/{src_dir}"
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,show_chart=False)

162
print_merged_many_obj.py

@ -0,0 +1,162 @@
import os
import shutil
import time
def merged_obj_for_group(input_folder,output_folder):
""""""
group_size = 5
obj_pid_list = os.listdir(input_folder)
group_obj_list = [obj_pid_list[i:i + group_size] for i in range(0, len(obj_pid_list), group_size)]
print(group_obj_list)
for group_obj in group_obj_list:
print(group_obj)
group_pid = "_".join(group_obj)
print(group_pid)
output_group_folder = os.path.join(output_folder,group_pid)
os.makedirs(output_group_folder, exist_ok=True)
#input_root_folder = "/data/datasets_20t/obj_merger_test_data/"
#output_folder = "/data/datasets_20t/obj_merger_result/"
output_obj = os.path.join(output_group_folder, f"{group_pid}.obj")
output_mtl = os.path.join(output_group_folder, f"{group_pid}.mtl")
# 初始化
merged_obj = []
merged_mtl = []
texture_files = set()
material_offset = 0
vertex_offset = 0
texture_offset = 0
normal_offset = 0
current_materials = {}
# 遍历每个子文件夹
for folder_name in group_obj:
folder_path = os.path.join(input_folder, folder_name)
if not os.path.isdir(folder_path):
continue
obj_file = None
mtl_file = None
texture_file = None
# 寻找 .obj、.mtl 和 .jpg 文件
for file_name in os.listdir(folder_path):
if file_name.endswith(".obj"):
obj_file = os.path.join(folder_path, file_name)
elif file_name.endswith(".mtl"):
mtl_file = os.path.join(folder_path, file_name)
elif file_name.endswith(".jpg"):
texture_file = os.path.join(folder_path, file_name)
# 跳过不完整的文件夹
if not obj_file or not mtl_file or not texture_file:
print(f"跳过不完整的文件夹:{folder_path}")
continue
# 读取 .obj 文件
with open(obj_file, "r") as obj_f:
obj_lines = obj_f.readlines()
for line in obj_lines:
if line.startswith("mtllib"):
# 替换材质文件名
merged_obj.append(f"mtllib {os.path.basename(output_mtl)}\n")
elif line.startswith("usemtl"):
# 重命名材质名称,避免冲突
original_material = line.split()[1]
new_material = f"{original_material}_{material_offset}"
print(f"original_material---{original_material}")
print(f"new_material---{new_material}")
merged_obj.append(f"usemtl {new_material}\n")
current_materials[original_material] = new_material
elif line.startswith("v "): # 顶点
vertex = line.split()[1:]
merged_obj.append(f"v {' '.join(vertex)}\n")
elif line.startswith("vt "): # 纹理坐标
texture = line.split()[1:]
merged_obj.append(f"vt {' '.join(texture)}\n")
elif line.startswith("vn "): # 法线
normal = line.split()[1:]
merged_obj.append(f"vn {' '.join(normal)}\n")
elif line.startswith("f "): # 面数据
face = line.split()[1:]
updated_face = []
for vertex in face:
indices = vertex.split("/")
indices = [
str(int(indices[0]) + vertex_offset) if indices[0] else "",
str(int(indices[1]) + texture_offset) if len(indices) > 1 and indices[1] else "",
str(int(indices[2]) + normal_offset) if len(indices) > 2 and indices[2] else "",
]
updated_face.append("/".join(indices))
merged_obj.append(f"f {' '.join(updated_face)}\n")
# 更新偏移量
vertex_offset += sum(1 for line in obj_lines if line.startswith("v "))
texture_offset += sum(1 for line in obj_lines if line.startswith("vt "))
normal_offset += sum(1 for line in obj_lines if line.startswith("vn "))
# 读取 .mtl 文件
with open(mtl_file, "r") as mtl_f:
mtl_lines = mtl_f.readlines()
for line in mtl_lines:
if line.startswith("newmtl"):
# 重命名材质
original_material = line.split()[1]
new_material = current_materials.get(original_material, original_material)
merged_mtl.append(f"newmtl {new_material}\n")
elif line.startswith(("map_Kd", "map_Ka", "map_bump")):
# 替换贴图路径为相对路径
texture_name = os.path.basename(texture_file)
merged_mtl.append(f"{line.split()[0]} {texture_name}\n")
texture_files.add(texture_file)
else:
merged_mtl.append(line)
material_offset += 1
# 写入合并后的 .obj 和 .mtl 文件
with open(output_obj, "w") as obj_out:
obj_out.writelines(merged_obj)
with open(output_mtl, "w") as mtl_out:
mtl_out.writelines(merged_mtl)
print(f"texture_files====={texture_files}")
# 将纹理文件复制到输出目录
for texture_file in texture_files:
shutil.copy(texture_file, output_group_folder)
print(f"合并完成:{output_obj}{output_mtl}")
print(f"纹理文件已复制到:{output_group_folder}")
def move_compact_obj_to_file(input_folder,output_folder):
""""""
group_size = 50
obj_pid_list = os.listdir(input_folder)
group_obj_list = [obj_pid_list[i:i + group_size] for i in range(0, len(obj_pid_list), group_size)]
print(group_obj_list)
for group_obj in group_obj_list:
print(f"group_obj{group_obj}")
out_obj_file_name = group_obj[0]+"_"+group_obj[-1]
print(f"out_obj_file_name:::{out_obj_file_name}")
group_out_put_dir = os.path.join(output_folder,out_obj_file_name)
os.makedirs(group_out_put_dir,exist_ok=True)
for obj_name in group_obj:
original_obj_dir = os.path.join(input_folder,obj_name)
for file_name in os.listdir(original_obj_dir):
original_path = os.path.join(original_obj_dir,file_name)
dis_path = os.path.join(group_out_put_dir,file_name)
shutil.copy(original_path,dis_path)
print("分组完成。")
if __name__ == '__main__':
input_folder = "/data/datasets_20t/type_setting_test_data/print_compact_obj/"
output_folder = "/data/datasets_20t/type_setting_test_data/obj_merger_result/"
#merged_obj_for_group(input_folder,output_folder)
move_compact_obj_to_file(input_folder, output_folder)

3619
print_mplot3d_point_cloud_layout.py

File diff suppressed because it is too large Load Diff

127
print_setting_run.py

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
import pandas as pd
from threading import Thread
from PyQt5.QtWidgets import QApplication, QMainWindow,QMessageBox
from print_setting_ui import Ui_MainWindow
from PyQt5.QtGui import QFont
from PySide2.QtCore import Signal,QObject
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
import sys
import os
import warnings
import time
import time
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
QVBoxLayout, QFileDialog, QMessageBox)
from threading import Thread
from print_factory_type_setting_obj_run import print_type_setting_obj
warnings.filterwarnings('ignore')
pd.set_option('display.width', None)
class MySignals(QObject):
text_print = Signal(str)
update_table = Signal(str)
class MyMainForm(QMainWindow, Ui_MainWindow):
def __init__(self,parent=None):
super(MyMainForm, self).__init__(parent)
self.setupUi(self)
self.folder_path = ""
self.cache_path = ""
self.pushButton.clicked.connect(self.on_select_folder)
self.pushButton_2.clicked.connect(self.on_run_clicked)
self.pushButton_3.clicked.connect(self.on_open_output_clicked)
self.global_ms = MySignals()
self.global_ms.text_print.connect(self.printToGui)
def printToGui(self,text):
n_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.textBrowser.append(n_time+" "+str(text))
def on_select_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
self.folder_path = folder
#self.folder_path_label.setText(f"📂 当前选择文件夹: {folder}")
#self.run_status_label.setText("")
self.cache_path = folder + "_arrange"
os.makedirs(self.cache_path, exist_ok=True)
def on_run_clicked(self):
def threadFunc1():
if not self.folder_path:
#self.run_status_label.setText("❗请先选择一个文件夹再执行!")
return
# if hasattr(self, 'worker') and self.worker.isRunning():
# self.run_status_label.setText("⚠ 正在执行中,请稍候...")
# return
#self.run_btn.setEnabled(False)
#self.run_status_label.setText("🚀 程序正在运行,请稍候...")
print_type_setting_obj(
base_original_obj_dir=self.folder_path,
cache_type_setting_dir=self.cache_path,
show_chart=False
)
#self.run_status_label.setText("✅ 排版完成!")
thread = Thread(target=threadFunc1)
thread.start()
def open_file_cross_platform(self, path):
if not os.path.exists(path):
print("路径不存在!")
return
if sys.platform.startswith('win'):
os.startfile(path)
elif sys.platform.startswith('darwin'):
QProcess.startDetached("open", [path])
else:
QProcess.startDetached("xdg-open", [path])
def on_open_output_clicked(self):
output_path = os.path.join(self.cache_path, "print_compact_obj")
if os.path.exists(output_path):
self.open_file_cross_platform(output_path)
else:
self.run_status_label.setText(" 输出文件夹不存在!")
def douyin_spider_go(self):
"""下载抖音视频"""
def threadFunc1():
print("开始下载")
self.load_chrome_video()
thread = Thread(target=threadFunc1)
thread.start()
#thread.run()
def open_config_dir(self):
"""打开文件夹"""
def threadFunc1():
try:
start_directory = os.path.join(self.dir_base)
os.startfile(start_directory)
except RecursionError:
print("打开配置文件夹失败。")
thread = Thread(target=threadFunc1)
thread.start()
if __name__ == '__main__':
#multiprocessing.freeze_support()
app = QApplication(sys.argv)
myWin = MyMainForm()
myWin.show()
sys.exit(app.exec_())

51
print_setting_ui.py

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'dou_spider_ui.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(300, 150)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
#self.folder_path_label = QLabel("📁 请选择要排版的文件夹")
#self.folder_path_label.setWordWrap(True)
# 按钮布局
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setObjectName("pushButton_2")
self.verticalLayout.addWidget(self.pushButton_2)
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setObjectName("pushButton_3")
self.verticalLayout.addWidget(self.pushButton_3)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "自动排版工具"))
self.pushButton.setText(_translate("MainWindow", "选择文件夹"))
self.pushButton_2.setText(_translate("MainWindow", "开始自动排版"))
self.pushButton_3.setText(_translate("MainWindow", "打开排版好的文件夹"))

1120
print_show_weight_max_obj.py

File diff suppressed because it is too large Load Diff

346
print_type_setting_gui.py

@ -0,0 +1,346 @@
import sys
import os
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
QVBoxLayout, QFileDialog, QMessageBox,
QRadioButton, QHBoxLayout, QButtonGroup, QGroupBox)
from PyQt5.QtCore import QProcess, QTimer
from threading import Thread
import print_factory_type_setting_obj_run
class AutoLayoutApp(QWidget):
def __init__(self):
super().__init__()
self.small_model_temp = False
self.setWindowTitle("自动排版工具")
self.setGeometry(200, 200, 600, 380) # 增加窗口高度以容纳新控件
self.dw = print_factory_type_setting_obj_run
self.folder_path = ""
self.cache_path = ""
self.process = None
if self.small_model_temp:
self.selected_mode = "紧凑" # 默认排版模式
self.output_format = "模型" # 默认输出格式
self.selected_machine = "小机型" # 默认机型
else:
self.selected_mode = "标准" # 默认排版模式
self.output_format = "JSON" # 默认输出格式
self.selected_machine = "大机型" # 默认机型
self.is_running = False # 跟踪排版状态
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
# ===== 机型选择区域(移到最上面) =====
machine_group = QGroupBox("机型选择")
machine_layout = QHBoxLayout()
# 创建机型选择按钮组
self.machine_group = QButtonGroup(self)
# 创建两种机型选项
self.large_machine = QRadioButton("大机型(600 * 500 * 300)")
self.small_machine = QRadioButton("小机型(380 * 345 * 250)")
if self.small_model_temp:
self.large_machine.setEnabled(False)
self.small_machine.setChecked(True) # 默认选中
else:
self.large_machine.setChecked(True) # 默认选中
# 添加到按钮组
self.machine_group.addButton(self.large_machine, 1)
self.machine_group.addButton(self.small_machine, 2)
# 连接信号
self.machine_group.buttonToggled.connect(self.on_machine_changed)
# 添加到布局
machine_layout.addWidget(self.large_machine)
machine_layout.addWidget(self.small_machine)
machine_group.setLayout(machine_layout)
layout.addWidget(machine_group)
# ===== 排版模式选择区域(改为QGroupBox) =====
mode_group = QGroupBox("排版模式")
mode_layout = QHBoxLayout()
# 创建单选按钮组
self.mode_group = QButtonGroup(self)
# 创建三种排版模式选项
self.standard_mode = QRadioButton("标准模式(适合规整模型组)")
self.compact_mode = QRadioButton("紧凑模式(复杂度较高))")
self.advanced_mode = QRadioButton("高级模式(复杂度最高)")
self.advanced_mode.setVisible(False)
if self.small_model_temp:
self.standard_mode.setEnabled(False)
self.compact_mode.setChecked(True) # 默认选中
else:
self.standard_mode.setChecked(True) # 默认选中
# 添加到按钮组(确保互斥选择)
self.mode_group.addButton(self.standard_mode, 1)
self.mode_group.addButton(self.compact_mode, 2)
self.mode_group.addButton(self.advanced_mode, 3)
# 连接信号
self.mode_group.buttonToggled.connect(self.on_mode_changed)
# 添加到布局
mode_layout.addWidget(self.standard_mode)
mode_layout.addWidget(self.compact_mode)
mode_layout.addWidget(self.advanced_mode)
mode_group.setLayout(mode_layout)
layout.addWidget(mode_group)
# ==== 输出格式选择区域 ====
format_group = QGroupBox("输出格式")
format_layout = QHBoxLayout()
# 创建输出格式按钮组
self.format_group = QButtonGroup(self)
# 创建两种输出格式选项
self.json_format = QRadioButton("JSON格式")
self.model_format = QRadioButton("模型格式")
if self.small_model_temp:
self.json_format.setEnabled(False)
self.model_format.setChecked(True) # 默认选中
else:
self.json_format.setChecked(True) # 默认选中
# 添加到按钮组
self.format_group.addButton(self.json_format, 1)
self.format_group.addButton(self.model_format, 2)
# 连接信号
self.format_group.buttonToggled.connect(self.on_format_changed)
# 添加到布局
format_layout.addWidget(self.json_format)
format_layout.addWidget(self.model_format)
format_group.setLayout(format_layout)
layout.addWidget(format_group)
# ===== 原有UI元素 =====
self.folder_path_label = QLabel(" 请选择要排版的文件夹")
self.folder_path_label.setWordWrap(True)
self.run_status_label = QLabel("")
layout.addWidget(self.folder_path_label)
layout.addWidget(self.run_status_label)
self.select_folder_btn = QPushButton("选择文件夹")
self.select_folder_btn.clicked.connect(self.on_select_folder)
layout.addWidget(self.select_folder_btn)
# 按钮布局(运行按钮和预览按钮在同一行)
buttons_layout = QHBoxLayout()
self.run_btn = QPushButton("开始自动排版")
self.run_btn.clicked.connect(self.on_run_clicked)
buttons_layout.addWidget(self.run_btn)
# ===== 新增预览按钮 =====
self.preview_btn = QPushButton("预览排版结果")
self.preview_btn.clicked.connect(self.on_preview_clicked)
self.preview_btn.setEnabled(False) # 初始禁用,排版完成后才可用
buttons_layout.addWidget(self.preview_btn)
layout.addLayout(buttons_layout)
self.open_output_btn = QPushButton("打开排版好的文件夹")
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
layout.addWidget(self.open_output_btn)
self.setLayout(layout)
def on_mode_changed(self, button, checked):
"""处理排版模式选择变化"""
if checked:
# self.selected_mode = button.text().replace("模式", "")
self.selected_mode = button.text()[:2]
self.run_status_label.setText(f"已选择: {self.selected_mode} 模式")
def on_format_changed(self, button, checked):
"""处理输出格式选择变化"""
if checked:
self.output_format = button.text().replace("格式", "")
self.run_status_label.setText(f"输出格式: {self.output_format}")
def on_machine_changed(self, button, checked):
"""处理机型选择变化"""
if checked:
self.selected_machine = button.text().split("(")[0]
self.run_status_label.setText(f"已选择: {self.selected_machine}")
def on_select_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
self.folder_path = folder
self.folder_path_label.setText(f" 当前选择文件夹: {folder}")
self.run_status_label.setText("")
self.cache_path = folder + "_arrange"
# self.cache_path = os.path.join(folder, "arrange")
os.makedirs(self.cache_path, exist_ok=True)
self.preview_btn.setEnabled(False) # 选择新文件夹后禁用预览按钮
def get_base_directory(self):
"""获取脚本或可执行文件的基础目录"""
if getattr(sys, 'frozen', False):
# 打包后的可执行文件环境
base_path = os.path.dirname(sys.executable)
else:
# 正常脚本运行环境
base_path = os.path.dirname(os.path.abspath(__file__))
return base_path
def on_run_clicked(self):
# 获取脚本所在目录的父目录
script_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(script_dir)
# 获取基础目录
base_path = self.get_base_directory()
# 获取父目录
parent_dir = os.path.dirname(base_path)
bad_dir = os.path.join(parent_dir, "bad")
full_dir = os.path.join(parent_dir, "full")
# 检查bad目录
bad_dir_exists = os.path.exists(bad_dir) and os.path.isdir(bad_dir)
bad_dir_not_empty = bad_dir_exists and any(os.scandir(bad_dir))
# 检查full目录
full_dir_exists = os.path.exists(full_dir) and os.path.isdir(full_dir)
full_dir_not_empty = full_dir_exists and any(os.scandir(full_dir))
# 如果有异常数据需要处理
if bad_dir_not_empty or full_dir_not_empty:
message = "请处理以下目录中的异常数据:\n"
if bad_dir_not_empty:
message += f"- bad目录: {bad_dir}\n"
if full_dir_not_empty:
message += f"- full目录: {full_dir}\n"
QMessageBox.warning(self, "存在未处理的异常数据",
message + "\n请先处理这些目录中的数据后再进行排版!")
self.run_status_label.setText(" 存在未处理的异常数据,请先处理!")
return
def threadFunc1():
self.is_running = True # 标记排版开始
if not self.folder_path:
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
self.is_running = False
return
if self.process and self.process.state() == QProcess.Running:
self.run_status_label.setText(" 正在执行中,请稍候...")
self.is_running = False
return
self.run_btn.setEnabled(False)
self.preview_btn.setEnabled(False) # 排版中禁用预览按钮
self.run_status_label.setText(
f" 正在使用 [{self.selected_mode}] 排版, 机型: {self.selected_machine}, 输出格式: {self.output_format}, 请稍候..."
)
normalized_path = os.path.normpath(self.folder_path)
self.batch_id = os.path.basename(normalized_path)
# 调用排版函数,传递所有参数
self.dw.print_type_setting_obj(
base_original_obj_dir=self.folder_path,
cache_type_setting_dir=self.cache_path,
show_chart=False,
batch_id=self.batch_id,
selected_mode=self.selected_mode,
output_format=self.output_format,
selected_machine=self.selected_machine
)
self.run_status_label.setText(
f"✅ [{self.selected_mode}] 排版完成! 机型: {self.selected_machine}, 输出格式: {self.output_format}"
)
self.run_btn.setEnabled(True)
self.preview_btn.setEnabled(True) # 排版完成后启用预览按钮
self.is_running = False # 标记排版结束
thread = Thread(target=threadFunc1)
thread.start()
def on_process_finished(self, exitCode, exitStatus):
self.run_btn.setEnabled(True)
self.preview_btn.setEnabled(True) # 完成后启用预览按钮
if exitCode == 0:
self.run_status_label.setText("✅ 排版完成!")
else:
self.run_status_label.setText(f"❌❌ 进程异常退出 (代码: {exitCode})")
def on_process_error(self, error):
self.run_btn.setEnabled(True)
self.preview_btn.setEnabled(False) # 出错时禁用预览按钮
self.run_status_label.setText(f"❌❌ 发生错误: {error.name}")
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
def open_file_cross_platform(self, path):
if not os.path.exists(path):
self.run_status_label.setText(" 路径不存在!")
return
if sys.platform.startswith('win'):
os.startfile(path)
elif sys.platform.startswith('darwin'):
QProcess.startDetached("open", [path])
else:
QProcess.startDetached("xdg-open", [path])
def on_preview_clicked(self):
"""预览按钮点击事件处理"""
if not self.cache_path:
self.run_status_label.setText(" 请先执行排版操作!")
return
is_small_machine = self.selected_machine=="小机型"
if os.path.exists(self.folder_path):
self.run_status_label.setText("正在打开预览目录...")
self.dw.preview(
base_original_obj_dir=self.folder_path,batch_id=self.batch_id, is_small_machine=is_small_machine
)
else:
self.run_status_label.setText(" 预览目录不存在!")
def on_open_output_clicked(self):
"""打开排版结果文件夹"""
if not self.cache_path:
self.run_status_label.setText(" 请先执行排版操作!")
return
open_dir = self.cache_path
if self.output_format=="JSON":
open_dir = self.folder_path
if os.path.exists(open_dir):
self.open_file_cross_platform(open_dir)
else:
self.run_status_label.setText(" 输出文件夹不存在!")
if __name__ == "__main__":
from PyQt5.QtCore import QSharedMemory
app = QApplication(sys.argv)
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
if not shared_mem.create(1):
QMessageBox.critical(None, "错误", "程序已经在运行中!")
sys.exit(1)
window = AutoLayoutApp()
window.show()
sys.exit(app.exec_())

38
print_type_setting_gui.spec

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['print_type_setting_gui.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='print_type_setting_gui',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

126
print_type_setting_gui_multi.py

@ -0,0 +1,126 @@
import sys
import os
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
QVBoxLayout, QFileDialog, QMessageBox)
from PyQt5.QtCore import QProcess, QTimer
from threading import Thread
import print_factory_type_setting_obj_run
class AutoLayoutApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("自动排版工具")
self.setGeometry(200, 200, 600, 200)
self.dw= print_factory_type_setting_obj_run
self.folder_path = ""
self.cache_path = ""
self.process = None
self.init_ui()
def init_ui(self):
layout = QVBoxLayout()
self.folder_path_label = QLabel("<EFBFBD><EFBFBD> 请选择要排版的文件夹")
self.folder_path_label.setWordWrap(True)
self.run_status_label = QLabel("")
layout.addWidget(self.folder_path_label)
layout.addWidget(self.run_status_label)
self.select_folder_btn = QPushButton("选择文件夹")
self.select_folder_btn.clicked.connect(self.on_select_folder)
layout.addWidget(self.select_folder_btn)
self.run_btn = QPushButton("开始自动排版")
self.run_btn.clicked.connect(self.on_run_clicked)
layout.addWidget(self.run_btn)
self.open_output_btn = QPushButton("打开排版好的文件夹")
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
layout.addWidget(self.open_output_btn)
self.setLayout(layout)
def on_select_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
self.folder_path = folder
self.folder_path_label.setText(f"<EFBFBD><EFBFBD> 当前选择文件夹: {folder}")
self.run_status_label.setText("")
self.cache_path = folder + "_arrange"
os.makedirs(self.cache_path, exist_ok=True)
def on_run_clicked(self):
def threadFunc1():
if not self.folder_path:
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
return
if self.process and self.process.state() == QProcess.Running:
self.run_status_label.setText(" 正在执行中,请稍候...")
return
self.run_btn.setEnabled(False)
self.run_status_label.setText("<EFBFBD><EFBFBD> 程序正在运行,请稍候...")
return_code = self.dw.print_type_setting_obj(base_original_obj_dir=self.folder_path,cache_type_setting_dir=self.cache_path,
show_chart=False)
if return_code==0:
self.run_status_label.setText("✅ 排版完成!")
elif return_code==-1:
self.run_status_label.setText("❌选择目录为空!")
else:
self.run_status_label.setText("❌排版失败!")
self.run_btn.setEnabled(True)
thread = Thread(target=threadFunc1)
thread.start()
def on_process_finished(self, exitCode, exitStatus):
self.run_btn.setEnabled(True)
if exitCode == 0:
self.run_status_label.setText("✅ 排版完成!")
else:
self.run_status_label.setText(f"❌ 进程异常退出 (代码: {exitCode})")
def on_process_error(self, error):
self.run_btn.setEnabled(True)
self.run_status_label.setText(f"❌ 发生错误: {error.name}")
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
def open_file_cross_platform(self, path):
if not os.path.exists(path):
self.run_status_label.setText(" 路径不存在!")
return
if sys.platform.startswith('win'):
os.startfile(path)
elif sys.platform.startswith('darwin'):
QProcess.startDetached("open", [path])
else:
QProcess.startDetached("xdg-open", [path])
def on_open_output_clicked(self):
if not self.cache_path:
self.run_status_label.setText(" 请先执行排版操作!")
return
output_path = os.path.join(self.cache_path, "print_compact_obj")
if os.path.exists(output_path):
self.open_file_cross_platform(output_path)
else:
self.run_status_label.setText(" 输出文件夹不存在!")
if __name__ == "__main__":
from PyQt5.QtCore import QSharedMemory
app = QApplication(sys.argv)
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
if not shared_mem.create(1):
QMessageBox.critical(None, "错误", "程序已经在运行中!")
sys.exit(1)
window = AutoLayoutApp()
window.show()
sys.exit(app.exec_())

25
qt5_demo.py

@ -0,0 +1,25 @@
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("PyQt5 简单示例")
self.setGeometry(100, 100, 300, 200)
self.button = QPushButton("点我一下", self)
self.button.setGeometry(100, 80, 100, 30)
self.button.clicked.connect(self.show_message)
def show_message(self):
QMessageBox.information(self, "提示", "按钮被点击了!")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
"""
pyinstaller qt5_demo.py --hidden-import PySide2.QtXml
"""

44
qt5_demo.spec

@ -0,0 +1,44 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['qt5_demo.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=['PySide2.QtXml'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='qt5_demo',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='qt5_demo',
)

275
sui_01.py

@ -0,0 +1,275 @@
import open3d as o3d
import os
import numpy as np
from scipy.spatial.transform import Rotation
import sys
import argparse
# import cv2
import matplotlib.pyplot as plt
import numpy as np
from numba import njit, prange
import time
# 核心计算函数(支持Numba加速)
@njit(fastmath=True, cache=True)
def calculate_rotation_z(angle_x, angle_y, angle_z, points, cos_cache, sin_cache, angle_step):
"""计算单个旋转组合后的重心Z坐标(无显式平移)"""
# 获取预计算的三角函数值
idx_x = angle_x // angle_step
idx_y = angle_y // angle_step
idx_z = angle_z // angle_step
cos_x = cos_cache[idx_x]
sin_x = sin_cache[idx_x]
cos_y = cos_cache[idx_y]
sin_y = sin_cache[idx_y]
cos_z = cos_cache[idx_z]
sin_z = sin_cache[idx_z]
# 构造旋转矩阵(展开矩阵乘法优化)
# R = Rz @ Ry @ Rx
# 计算矩阵元素(手动展开矩阵乘法)
m00 = cos_z * cos_y
m01 = cos_z * sin_y * sin_x - sin_z * cos_x
m02 = cos_z * sin_y * cos_x + sin_z * sin_x
m10 = sin_z * cos_y
m11 = sin_z * sin_y * sin_x + cos_z * cos_x
m12 = sin_z * sin_y * cos_x - cos_z * sin_x
m20 = -sin_y
m21 = cos_y * sin_x
m22 = cos_y * cos_x
# 计算所有点的Z坐标
z_values = np.empty(points.shape[0], dtype=np.float64)
for i in prange(points.shape[0]):
x, y, z = points[i, 0], points[i, 1], points[i, 2]
# 应用旋转矩阵
rotated_z = m20 * x + m21 * y + m22 * z
z_values[i] = rotated_z
# 计算重心Z(等效于平移后的重心)
min_z = np.min(z_values)
avg_z = np.mean(z_values)
return avg_z - min_z # 等效于平移后的重心Z坐标
# 并行优化主函数
def parallel_rotation2(points, angle_step=3):
"""
参数
points : numpy.ndarray (N,3) - 三维点云
angle_step : int - 角度搜索步长度数
返回
(best_angle_x, best_angle_y, best_angle_z, min_z)
"""
points = np.ascontiguousarray(points.astype(np.float64))
# 生成所有可能角度
angles = np.arange(0, 360, angle_step)
n_angles = len(angles)
# 预计算三角函数值(大幅减少重复计算)
rads = np.radians(angles)
cos_cache = np.cos(rads).astype(np.float64)
sin_cache = np.sin(rads).astype(np.float64)
# 生成所有角度组合(内存优化版)
total_combinations = n_angles ** 3
print(f"Total combinations: {total_combinations:,}")
# 分块处理以避免内存溢出
best_z = np.inf
best_angles = (0, 0, 0)
batch_size = 10 ** 6 # 根据可用内存调整
for x_chunk in range(0, n_angles, max(1, n_angles // 4)):
angles_x = angles[x_chunk:x_chunk + max(1, n_angles // 4)]
for y_chunk in range(0, n_angles, max(1, n_angles // 4)):
angles_y = angles[y_chunk:y_chunk + max(1, n_angles // 4)]
# 生成当前分块的所有组合
xx, yy, zz = np.meshgrid(angles_x, angles_y, angles)
current_batch = np.stack([xx.ravel(), yy.ravel(), zz.ravel()], axis=1)
# 处理子批次
for i in range(0, len(current_batch), batch_size):
batch = current_batch[i:i + batch_size]
results = np.zeros(len(batch), dtype=np.float64)
_process_batch(batch, points, cos_cache, sin_cache, angle_step, results)
# 更新最佳结果
min_idx = np.argmin(results)
if results[min_idx] < best_z:
best_z = results[min_idx]
best_angles = tuple(batch[min_idx])
print(f"New best: {best_angles} -> Z={best_z:.4f}")
return (*best_angles, best_z)
@njit(parallel=True, fastmath=True)
def _process_batch(batch, points, cos_cache, sin_cache, angle_step, results):
for i in prange(len(batch)):
ax, ay, az = batch[i]
results[i] = calculate_rotation_z(
ax, ay, az, points,
cos_cache, sin_cache, angle_step
)
class ModelProcessor:
def __init__(self):
# argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
parser = argparse.ArgumentParser()
parser.add_argument(
"--id",
required=False,
)
args = parser.parse_args()
self.id = args.id
self.mesh = None
self.asset_dir = f"/home/algo/Documents/datasets/{self.id}"
def load_model(self):
"""加载并初始化3D模型"""
# model_path = f"{self.asset_dir}/baked/{self.id}.obj"
# model_path = f"{self.asset_dir}/repair_{self.id}_mesh.ply"
model_path = "/data/datasets_20t/8/88884_253283_P65951_6cm_x1.obj"
if not os.path.exists(model_path):
raise FileNotFoundError(f"Model file not found: {model_path}")
print(model_path)
mesh_native = o3d.io.read_triangle_mesh(model_path, enable_post_processing=False)
# self.mesh = o3d.io.read_triangle_mesh(model_path, enable_post_processing=False)
print("Open3D去重前顶点数:", len(mesh_native.vertices))
self.mesh = mesh_native.merge_close_vertices(eps=1e-6)
vertices2 = np.asarray(self.mesh.vertices)
print("Open3D去重后顶点数:", len(vertices2))
vertices2_sorted = sorted(
vertices2.tolist(),
key=lambda x: (x[0], x[1], x[2])
)
if not self.mesh.has_vertex_colors():
num_vertices = len(self.mesh.vertices)
self.mesh.vertex_colors = o3d.utility.Vector3dVector(
np.ones((num_vertices, 3))
)
self.uv_array = np.asarray(self.mesh.triangle_uvs)
# print(f"UV 坐标形状:{self.uv_array.shape}, {self.uv_array[0][1]}")
def calculate_rotation_and_center_of_mass(self, angle_x, angle_y, angle_z, points):
"""计算某一组旋转角度后的重心"""
# 计算绕X轴、Y轴和Z轴的旋转矩阵
R_x = np.array([
[1, 0, 0],
[0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
[0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
])
R_y = np.array([
[np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
[0, 1, 0],
[-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
])
R_z = np.array([
[np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
[np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
[0, 0, 1]
])
# 综合旋转矩阵
R = R_z @ R_y @ R_x
# 执行旋转
rotated_points = points @ R.T
# 计算最小z值
min_z = np.min(rotated_points[:, 2])
# 计算平移向量,将最小Z值平移到0
translation_vector = np.array([0, 0, -min_z])
rotated_points += translation_vector
# 计算重心
center_of_mass = np.mean(rotated_points, axis=0)
return center_of_mass[2], angle_x, angle_y, angle_z
def parallel_rotation(self, angle_step=3):
"""顺序计算最优旋转角度(单线程)"""
min_center_of_mass_y = float('inf')
best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
# 遍历所有角度组合
for angle_x in range(0, 360, angle_step):
for angle_y in range(0, 360, angle_step):
for angle_z in range(0, 360, angle_step):
center_of_mass_z, ax, ay, az = self.calculate_rotation_and_center_of_mass(
angle_x, angle_y, angle_z, self.mesh.vertices
)
if center_of_mass_z < min_center_of_mass_y:
min_center_of_mass_y = center_of_mass_z
best_angle_x, best_angle_y, best_angle_z = ax, ay, az
return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
def process(self):
"""执行完整处理流程"""
self.load_model()
try:
start = time.time()
# mesh = o3d.geometry.TriangleMesh()
# mesh.vertices = o3d.utility.Vector3dVector(np.random.rand(100, 3))
# points = np.asarray(mesh.vertices)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(self.mesh.vertices)
# 自动计算合理体素大小
raw_points = np.asarray(pcd.points)
bounds = np.ptp(raw_points, axis=0)
voxel_size = np.max(bounds) / 50 # 默认取最大边长的2%
# 执行下采样并验证
pcd_downsampled = pcd.voxel_down_sample(voxel_size)
if len(pcd_downsampled.points) < 10: # 最少保留10个点
raise RuntimeError(f"下采样失败:voxel_size={voxel_size:.3f}过大")
print(f"下采样后点数: {len(pcd_downsampled.points)} (voxel_size={voxel_size:.3f})")
# pcd.paint_uniform_color([1,0,0]) # 原始红色
# pcd_downsampled.paint_uniform_color([0,0,1]) # 采样后蓝色
# o3d.visualization.draw_geometries([pcd, pcd_downsampled])
# 继续后续处理
points = np.asarray(pcd_downsampled.points)
best_angle_x, best_angle_y, best_angle_z, min_z = parallel_rotation2(points, angle_step=5)
print("best=", best_angle_x, best_angle_y, best_angle_z, min_z)
print(time.time() - start)
except Exception as e:
print(f"Error during processing: {str(e)}")
raise
if __name__ == "__main__":
ModelProcessor().process()

19
test.py

@ -0,0 +1,19 @@
import requests
import json
printId = 84258
url = f"https://mp.api.suwa3d.com/api/printOrder/infoByPrintId?printId={printId}"
res = requests.get(url)
print(res)
datas = res.json()["data"]["layout"]
print(datas)
angle_x = datas.get("angle_x",0)
angle_y = datas.get("angle_y",0)
angle_z = datas.get("angle_z",0)
layout_z = datas.get("layout_z",0)
print("angle_x",angle_x)
print("angle_y",angle_y)
print("angle_z",angle_z)
print("layout_z",layout_z)
# TODO 解析 res

482
test_load_json.py

@ -0,0 +1,482 @@
import open3d as o3d
import numpy as np
import json
import os
from PIL import Image
import argparse
def custom_mesh_transform(vertices, transform_matrix):
"""
手动实现网格变换对每个顶点应用齐次变换矩阵
参数:
vertices: 网格顶点数组 (N, 3)
transform_matrix: 4x4 齐次变换矩阵
返回:
变换后的顶点数组 (N, 3)
"""
# 1. 顶点转齐次坐标 (N, 3) → (N, 4)
homogeneous_vertices = np.hstack((vertices, np.ones((vertices.shape[0], 1))))
# 2. 应用变换矩阵:矩阵乘法 (4x4) * (4xN) → (4xN)
transformed_homogeneous = transform_matrix @ homogeneous_vertices.T
# 3. 转回非齐次坐标 (3xN) → (N, 3)
transformed_vertices = transformed_homogeneous[:3, :].T
return transformed_vertices
def load_and_transform_models(base_path, dict_origin, blank_path, json_name):
meshes = [] # 存储所有变换后的网格
json_path = os.path.join(base_path, json_name)
"""
加载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 []
# 处理每个模型
for model in data.get('models', []):
obj_name = model.get('file_name', '')
obj_path = os.path.join(base_path, obj_name)
# 检查文件路径是否存在
if not obj_path:
print("警告: 跳过缺少文件路径的模型")
continue
# print("load path", obj_path)
# 检查文件是否存在
if not os.path.exists(obj_path):
print(f"警告: 模型文件不存在 - {obj_path}")
continue
# 加载网格
try:
# print("dict_origin obj_path=", obj_path)
key = obj_path
if key in dict_origin:
# print("key in dict_origin")
mesh = dict_origin[key]
else :
# print("key not in dict_origin")
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
if not mesh.has_vertices():
print(f"警告: 网格无有效顶点 - {obj_path}")
continue
except Exception as e:
print(f"加载模型失败: {obj_path} - {e}")
continue
transform = model.get('transform', {})
homo_matrix = transform["homo_matrix"] # 获取存储的列表
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
# 手动变换顶点
original_vertices = np.asarray(mesh.vertices)
transformed_vertices = custom_mesh_transform(original_vertices, reconstructed_matrix)
mesh.vertices = o3d.utility.Vector3dVector(transformed_vertices)
# 添加到列表
meshes.append(mesh)
print(f"已加载并变换: {os.path.basename(obj_path)}")
add_plank = True
if add_plank:
# obj_path = os.path.join(blank_dir, "blank2.obj")
obj_path = blank_path
print("add_plank",obj_path)
try:
mesh = o3d.io.read_triangle_mesh(obj_path, enable_post_processing=True)
if not mesh.has_vertices():
print(f"警告: 网格无有效顶点 - {obj_path}")
except Exception as e:
print(f"加载模型失败: {obj_path} - {e}")
rotation = [0.0, 0.0, 0.0]
mesh_center = compute_mesh_center(mesh.vertices)
R_x = mesh.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(rotation[0]))
mesh.rotate(R_x,center=mesh_center)
R_y = mesh.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(rotation[1]))
mesh.rotate(R_y,center=mesh_center)
R_z = mesh.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(rotation[2]))
mesh.rotate(R_z,center=mesh_center)
displacement = [0.0, 0.0, 0.0]
displacement = np.asarray(displacement)
mesh.translate(displacement)
meshes.append(mesh)
# print(f"已加载并变换: {obj_path}")
return meshes
def compute_mesh_center(vertices):
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 load_and_show(base_path, blank_dir, json_name="3DPrintLayout.json"):
# 加载并变换所有模型
transformed_meshes = load_and_transform_models(base_path, {}, blank_dir, 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):
"""
通过相机参数设置正交投影并确保尺寸匹配
"""
# 更可靠的方式:使用你创建窗口时已知的尺寸
# 假设你创建窗口时使用的是 desired_width 和 desired_height
param = view_control.convert_to_pinhole_camera_parameters()
# 修改内参,确保使用与创建窗口时一致的尺寸
param.intrinsic.set_intrinsics(
width=desired_width, # 必须与create_window的width一致
height=desired_height, # 必须与create_window的height一致
fx=1000.0, # 对于正交投影,此值意义不同,但仍需合理设置
fy=1000.0, # 避免使用1.0这样的极端值
cx=desired_width / 2,
cy=desired_height / 2
)
# 同时,仍需通过ViewControl启用正交投影
view_control.set_orthogonal(True)
view_control.convert_from_pinhole_camera_parameters(param)
def set_orthographic_projection(view_control, ortho_scale=10.0):
"""
尝试使用 ViewControl 接口配置正交投影效果
参数:
vis: Open3D 可视化器实例
ortho_scale: 控制正交投影的视野大小值越大场景中的物体看起来越小
"""
try:
# 1. 尝试设置一个非常小的视野(Field of View)来减少透视感
# 注意:此方法可能在某些版本中效果不明显,但它是可用的最接近正交投影的直接控制
view_control.change_field_of_view(step=-5) # 尝试将FOV调到极小[1](@ref)
# 2. 设置固定的近、远裁剪平面,避免因自动计算带来的透视变形
# 这对于保持缩放时物体大小一致很重要
view_control.set_constant_z_near(0.1) # 设置一个固定的近平面
view_control.set_constant_z_far(1000.0) # 设置一个固定的远平面[1](@ref)
# 3. 使用一个较小的缩放值(Zoom),这有助于让视角更“平行”
# 在正交投影的模拟中,通常使用较大的zoom值(>1)来拉远相机,减少透视效果。
# 但根据实际测试,您可能需要尝试一个具体的值,例如 0.5 或 0.1
view_control.set_zoom(0.46) # 这个值需要根据你的场景进行调整[6,7](@ref)
# print("已尝试通过 ViewControl 配置正交投影参数。")
return True
except Exception as e:
print(f"在配置 ViewControl 参数时出错: {e}")
return False
def set_orthographic(meshes, output_path, width=1920, height=1080,
background_color=[1, 1, 1], camera_position=None,
ortho_width=None, zoom=1.0):
vis = o3d.visualization.Visualizer()
vis.create_window(width=width, height=height, visible=False)
# 添加几何体
for mesh in meshes:
vis.add_geometry(mesh)
# 设置渲染选项
render_opt = vis.get_render_option()
render_opt.background_color = np.asarray(background_color)
render_opt.mesh_show_back_face = True
render_opt.mesh_show_wireframe = False
render_opt.point_size = 3.0
# 视角控制
view_control = vis.get_view_control()
# 计算所有网格的合并边界框,用于自适应设置投影参数
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
if len(points) > 0:
all_points.append(points)
if len(all_points) > 0:
all_points = np.vstack(all_points)
bbox_min = np.min(all_points, axis=0)
bbox_max = np.max(all_points, axis=0)
bbox_center = (bbox_min + bbox_max) / 2.0
bbox_size = np.max(bbox_max - bbox_min)
# 设置相机视角,看向场景中心
if not camera_position:
# 默认顶视图
view_control.set_front([0, 0, 1])
view_control.set_lookat(bbox_center)
view_control.set_up([0, 1, 0])
else:
view_control.set_front(camera_position['front'])
view_control.set_lookat(camera_position['lookat'])
view_control.set_up(camera_position['up'])
# 自适应设置正交投影宽度,考虑屏幕宽高比
if ortho_width is None:
aspect_ratio = width / height
ortho_width = bbox_size * 1.5
# 根据宽高比调整,确保场景适合窗口
if aspect_ratio > 1:
ortho_width *= aspect_ratio
if ortho_width <= 0:
ortho_width = 10.0
else:
# 如果没有顶点,使用默认值
if not camera_position:
view_control.set_front([0, 0, 1])
view_control.set_lookat([0, 0, 0])
view_control.set_up([0, 1, 0])
ortho_width = ortho_width or 10.0
for _ in range(2):
vis.poll_events()
vis.update_renderer()
# 设置正交投影
try:
set_orthographic_camera(view_control)
except AttributeError:
set_orthographic_projection(view_control, ortho_scale=15.0)
for _ in range(5):
vis.poll_events()
vis.update_renderer()
return vis
def render_to_texture(meshes, output_path, width=1920, height=1080,
background_color=[1, 1, 1], camera_position=None,
ortho_width=None, zoom=1.0):
vis = set_orthographic(meshes, output_path)
# 渲染并保存
vis.capture_screen_image(output_path, do_render=True)
print(f"高级渲染图片已保存到: {output_path}")
return vis
def load_show_save(base_path, dict_origin, blank_path, batch_id, is_show=False):
folder_name = batch_id
# base_path = f"{print_factory_type_dir}/{folder_name}/" # 替换为实际路径
#json_name = "3DPrintLayout.json"
json_name = f"{batch_id}.json"
output_image_path = os.path.join(base_path, f"{folder_name}.jpg")
# 加载并变换所有模型
transformed_meshes = load_and_transform_models(base_path, dict_origin, blank_path, json_name)
if not transformed_meshes:
print("没有加载到任何模型,请检查错误信息")
else:
print(f"成功加载并变换了 {len(transformed_meshes)} 个模型")
render_to_texture(transformed_meshes, output_image_path, background_color=[0.9, 0.9, 0.9])
if is_show:
# 可视化所有模型
print("显示所有模型... (按'Q'退出)")
vis = o3d.visualization.Visualizer()
vis.create_window(window_name="正交投影模型展示")
# 添加所有网格到可视化器
for mesh in transformed_meshes:
vis.add_geometry(mesh)
vis.poll_events()
vis.update_renderer()
# 获取渲染选项并设置
render_option = vis.get_render_option()
render_option.background_color = np.array([0.9, 0.9, 0.9]) # 浅灰色背景
render_option.mesh_show_back_face = True
render_option.mesh_show_wireframe = False
# 更新渲染器
vis.update_renderer()
# 运行可视化
vis.run()
vis.destroy_window()
# 主程序
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--batch_id", type=str, required=True, help="batch_id")
args = parser.parse_args()
# batch_id = args.batch_id
batch_id = "9" # 1113-MY-4
print_factory_type_dir="/root/print_factory_type"
base_path = f"{print_factory_type_dir}/{batch_id}/"
# blank_path = "{print_factory_type_dir}/blank/blank_bias/blank2.obj"
blank_path = f"{print_factory_type_dir}/blank/blank_bias/blank_small.obj"
load_show_save(base_path, {}, blank_path, batch_id, True)

52
x_y_min_test.py

@ -0,0 +1,52 @@
import os
import shutil
import time
import random
import matplotlib.pyplot as plt
import open3d as o3d
import numpy as np
# ply_read_path="/data/datasets_20t/type_setting_test_data/print_bounds_compact_data/88884_253283_P65951_6cm_x1=7.811+11.043+25.699.ply"
# # 读取点云
# pcd = o3d.io.read_point_cloud(ply_read_path)
#
# # 获取点云的点数据
# points = np.asarray(pcd.points)
#
# # 计算质心
# centroid = np.mean(points, axis=0)
#
# # 计算 Y 轴最小值
# min_y_value = np.min(points[:, 1]) # Y 轴最小值
# max_y_value = np.max(points[:, 1])
#
# # 计算 X 轴最小值
# min_x_value = np.min(points[:, 0]) # X 轴最小值
#
# print(f'min_x_value{min_x_value}')
# min_x_value -385.08287729332403
#
ply_read_path="/data/datasets_20t/type_setting_test_data/print_bounds_compact_data/456450_260316_P65976_2.66cm_x1=21.778+22.904+26.333.ply"
# 读取点云
pcd = o3d.io.read_point_cloud(ply_read_path)
# 获取点云的点数据
points = np.asarray(pcd.points)
# 计算质心
centroid = np.mean(points, axis=0)
# 计算 Y 轴最小值
min_y_value = np.min(points[:, 1]) # Y 轴最小值
max_y_value = np.max(points[:, 1])
# 计算 X 轴最小值
min_x_value = np.min(points[:, 0]) # X 轴最小值
print(f'min_x_value{min_x_value}')
# min_x_value -385.08287729332403
print(f'min_y_value{min_y_value}')
# -339

152
读写时间测试.py

@ -0,0 +1,152 @@
# import numpy as np
# def calculate_rotation_and_center_of_mass(angle_x, angle_y, angle_z, points):
# """计算某一组旋转角度后的重心"""
# # 计算绕X轴、Y轴和Z轴的旋转矩阵
# R_x = np.array([
# [1, 0, 0],
# [0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
# [0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
# ])
#
# R_y = np.array([
# [np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
# [0, 1, 0],
# [-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
# ])
#
# R_z = np.array([
# [np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
# [np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
# [0, 0, 1]
# ])
#
# # 综合旋转矩阵
# R = R_z @ R_y @ R_x
#
# # 执行旋转
# rotated_points = points @ R.T
#
# # 计算最小z值
# min_z = np.min(rotated_points[:, 2])
#
# # 计算平移向量,将最小Z值平移到0
# translation_vector = np.array([0, 0, -min_z])
# rotated_points += translation_vector
#
# # 计算重心
# center_of_mass = np.mean(rotated_points, axis=0)
#
# return center_of_mass[2], angle_x, angle_y, angle_z
#
# def parallel_rotation(points, angle_step=3):
# """顺序计算最优旋转角度(单线程)"""
# min_center_of_mass_y = float('inf')
# best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
#
# # 遍历所有角度组合
# for angle_x in range(0, 360, angle_step):
# for angle_y in range(0, 360, angle_step):
# for angle_z in range(0, 360, angle_step):
# center_of_mass_z, ax, ay, az = calculate_rotation_and_center_of_mass(
# angle_x, angle_y, angle_z, points
# )
# if center_of_mass_z < min_center_of_mass_y:
# min_center_of_mass_y = center_of_mass_z
# best_angle_x, best_angle_y, best_angle_z = ax, ay, az
#
# return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
import bpy
import time
import os
from pathlib import Path
def clear_scene():
"""清空当前场景中的所有对象"""
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
def test_bpy_io(obj_path, output_path):
"""测试 bpy 的 OBJ 读写性能"""
# 读取 OBJ
start_time = time.time()
bpy.ops.import_scene.obj(filepath=obj_path)
read_time = time.time() - start_time
# 确保场景中有对象
if not bpy.context.scene.objects:
raise ValueError("未成功导入 OBJ 文件")
# 写入 OBJ
start_time = time.time()
bpy.ops.export_scene.obj(
filepath=output_path,
use_selection=False, # 导出所有对象
use_materials=False, # 不导出材质(加快速度)
)
write_time = time.time() - start_time
# 清理场景
clear_scene()
return write_time, read_time
def test_folder_objs_with_bpy(folder_path, output_folder="output_objs_bpy"):
"""测试文件夹中所有 OBJ 文件的读写性能(使用 bpy)"""
# 确保输出文件夹存在
Path(output_folder).mkdir(exist_ok=True)
# 收集所有 OBJ 文件
obj_files = [f for f in os.listdir(folder_path) if f.lower().endswith('.obj')]
if not obj_files:
print(f"在文件夹 {folder_path} 中未找到 OBJ 文件")
return
print(f"找到 {len(obj_files)} 个 OBJ 文件,开始测试...")
results = []
for obj_file in obj_files:
input_path = os.path.join(folder_path, obj_file)
output_path = os.path.join(output_folder, f"bpy_{obj_file}")
print(f"\n测试文件: {obj_file}")
try:
write_time, read_time = test_bpy_io(input_path, output_path)
file_size = os.path.getsize(input_path) / (1024 * 1024) # MB
print(f" 文件大小: {file_size:.2f} MB")
print(f" bpy 读取时间: {read_time:.3f}s")
print(f" bpy 写入时间: {write_time:.3f}s")
results.append({
"filename": obj_file,
"size_mb": file_size,
"read_time": read_time,
"write_time": write_time,
})
except Exception as e:
print(f" 处理 {obj_file} 时出错: {e}")
# 计算平均时间
if results:
avg_read = sum(r["read_time"] for r in results) / len(results)
avg_write = sum(r["write_time"] for r in results) / len(results)
print("\n=== 汇总结果 ===")
print(f"平均读取时间: {avg_read:.3f}s")
print(f"平均写入时间: {avg_write:.3f}s")
if __name__ == "__main__":
# 设置包含OBJ文件的文件夹路径
obj_folder = "/data/datasets_20t/9_big/" # 替换为你的OBJ文件夹路径
# 运行测试
test_folder_objs_with_bpy(obj_folder)

98
读写时间测试2.py

@ -0,0 +1,98 @@
import os
import time
import open3d as o3d
import trimesh
from pathlib import Path
def test_folder_objs(folder_path, output_folder="output_objs"):
"""测试文件夹中所有OBJ文件的读写性能"""
# 确保输出文件夹存在
Path(output_folder).mkdir(exist_ok=True)
# 收集文件夹中所有OBJ文件
obj_files = [f for f in os.listdir(folder_path) if f.lower().endswith('.obj')]
if not obj_files:
print(f"在文件夹 {folder_path} 中未找到OBJ文件")
return
print(f"找到 {len(obj_files)} 个OBJ文件,开始测试...")
# 准备结果记录
results = []
for obj_file in obj_files:
file_path = os.path.join(folder_path, obj_file)
output_path = os.path.join(output_folder, obj_file)
print(f"\n测试文件: {obj_file}")
# 测试open3d
o3d_write, o3d_read = test_open3d_io(file_path, output_path.replace('.obj', '_o3d.obj'))
# 测试trimesh
tm_write, tm_read = test_trimesh_io(file_path, output_path.replace('.obj', '_tm.obj'))
# 记录结果
file_stats = {
'filename': obj_file,
'o3d_write': o3d_write,
'o3d_read': o3d_read,
'tm_write': tm_write,
'tm_read': tm_read,
'write_ratio': o3d_write / tm_write if tm_write > 0 else 0,
'read_ratio': o3d_read / tm_read if tm_read > 0 else 0
}
results.append(file_stats)
# 打印当前文件结果
print(f" open3d | 写入: {o3d_write:.3f}s | 读取: {o3d_read:.3f}s")
print(f" trimesh | 写入: {tm_write:.3f}s | 读取: {tm_read:.3f}s")
print(f" 写入速度比(trimesh/open3d): {file_stats['write_ratio']:.1f}x")
print(f" 读取速度比(trimesh/open3d): {file_stats['read_ratio']:.1f}x")
# 打印汇总结果
print("\n=== 汇总结果 ===")
avg_write_ratio = sum(r['write_ratio'] for r in results) / len(results)
avg_read_ratio = sum(r['read_ratio'] for r in results) / len(results)
print(f"平均写入速度比(trimesh/open3d): {avg_write_ratio:.1f}x")
print(f"平均读取速度比(trimesh/open3d): {avg_read_ratio:.1f}x")
def test_open3d_io(input_path, output_path):
"""测试open3d的读写性能"""
# 读取
start = time.time()
mesh = o3d.io.read_triangle_mesh(input_path)
read_time = time.time() - start
# 写入
start = time.time()
o3d.io.write_triangle_mesh(output_path, mesh)
write_time = time.time() - start
return write_time, read_time
def test_trimesh_io(input_path, output_path):
"""测试trimesh的读写性能"""
# 读取
start = time.time()
mesh = trimesh.load(input_path)
read_time = time.time() - start
# 写入
start = time.time()
mesh.export(output_path)
write_time = time.time() - start
return write_time, read_time
if __name__ == "__main__":
# 设置包含OBJ文件的文件夹路径
obj_folder = "/data/datasets_20t/9_big/" # 替换为你的OBJ文件夹路径
# 运行测试
test_folder_objs(obj_folder)
Loading…
Cancel
Save