commit
daf8667065
26 changed files with 10879 additions and 0 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
run: |
||||
down: |
||||
AccessKeyId: 'LTAI5tJDLxK6wBdHE9Nu443G' |
||||
AccessKeySecret: 'sBN7IK4ozSE9nNtmD3dmDSuiS24SZq' |
||||
Endpoint: 'oss-cn-shanghai.aliyuncs.com' |
||||
Bucket: 'suwa3d-securedata' |
||||
@ -0,0 +1,354 @@
@@ -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) |
||||
|
||||
@ -0,0 +1,847 @@
@@ -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) |
||||
@ -0,0 +1,352 @@
@@ -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) |
||||
|
||||
@ -0,0 +1,179 @@
@@ -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']}, |
||||
""" |
||||
|
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash |
||||
while true; do |
||||
python clound_print.py | tee -a output.log |
||||
echo "脚本执行完成,等待10秒后重新运行..." |
||||
sleep 10 |
||||
done |
||||
@ -0,0 +1,291 @@
@@ -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) |
||||
@ -0,0 +1,193 @@
@@ -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) |
||||
@ -0,0 +1,162 @@
@@ -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) |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@
@@ -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", "打开排版好的文件夹")) |
||||
|
||||
@ -0,0 +1,38 @@
@@ -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, |
||||
) |
||||
@ -0,0 +1,25 @@
@@ -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 |
||||
""" |
||||
@ -0,0 +1,44 @@
@@ -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', |
||||
) |
||||
@ -0,0 +1,275 @@
@@ -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() |
||||
@ -0,0 +1,19 @@
@@ -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 |
||||
@ -0,0 +1,482 @@
@@ -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) |
||||
|
||||
@ -0,0 +1,52 @@
@@ -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 |
||||
@ -0,0 +1,152 @@
@@ -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) |
||||
@ -0,0 +1,98 @@
@@ -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…
Reference in new issue