初始化代码
This commit is contained in:
commit
daf8667065
26 files changed
+10879
No files matched your search
+346
@@ -0,0 +1,346 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import redis
|
||||||
|
import logging
|
||||||
|
from typing import cast
|
||||||
|
import requests
|
||||||
|
from download_print import download_datas_by_pre_layout
|
||||||
|
from download_print import BatchModelInfo
|
||||||
|
from download_print import is_test
|
||||||
|
from print_factory_type_setting_obj_run import print_type_setting_obj
|
||||||
|
|
||||||
|
|
||||||
|
# 如果没有 DISPLAY,自动启动 Xvfb
|
||||||
|
if "DISPLAY" not in os.environ:
|
||||||
|
import atexit
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
# 设置临时 XDG_RUNTIME_DIR
|
||||||
|
runtime_dir = f"/tmp/runtime-{os.getuid()}"
|
||||||
|
os.makedirs(runtime_dir, exist_ok=True)
|
||||||
|
os.environ["XDG_RUNTIME_DIR"] = runtime_dir
|
||||||
|
|
||||||
|
# 启动 Xvfb
|
||||||
|
xvfb_cmd = ["Xvfb", ":99", "-screen", "0", "1024x768x24", "-nolisten", "tcp"]
|
||||||
|
xvfb_proc = subprocess.Popen(xvfb_cmd)
|
||||||
|
atexit.register(lambda: xvfb_proc.terminate()) # 退出时关闭 Xvfb
|
||||||
|
|
||||||
|
# 设置 DISPLAY
|
||||||
|
os.environ["DISPLAY"] = ":99"
|
||||||
|
sleep(0.5) # 等待 Xvfb 启动
|
||||||
|
|
||||||
|
# redis_config = {
|
||||||
|
# "host": "127.0.0.1",
|
||||||
|
# "port": 6379,
|
||||||
|
# "db": 6,
|
||||||
|
# "password": "", # "kcV2000",
|
||||||
|
# "socket_timeout": 10,
|
||||||
|
# }
|
||||||
|
|
||||||
|
redis_config = {
|
||||||
|
"host": "mp.api.suwa3d.com",
|
||||||
|
"port": 6379,
|
||||||
|
"db": 6,
|
||||||
|
"password": "kcV2000", # "kcV2000",
|
||||||
|
"socket_timeout": 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
import sys
|
||||||
|
class RedisTaskQueue:
|
||||||
|
"""封装所有与 Redis 任务队列相关的操作。"""
|
||||||
|
def __init__(self, queue_key: str, redis_config: dict = redis_config):
|
||||||
|
self.queue_key = queue_key
|
||||||
|
self.client = redis.Redis(**redis_config)
|
||||||
|
|
||||||
|
def get_length(self) -> int:
|
||||||
|
"""
|
||||||
|
获取队列的长度。如果连接丢失,会尝试重新连接一次。
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:# 检测queue_key是否存在
|
||||||
|
length = 0
|
||||||
|
self.client = redis.Redis(**redis_config)
|
||||||
|
if not self.client.exists(self.queue_key):
|
||||||
|
logging.warning(f"队列不存在: {self.queue_key}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
key_type = self.client.type(self.queue_key).decode('utf-8') # type: ignore
|
||||||
|
if key_type == 'list':
|
||||||
|
length = cast(int, self.client.llen(self.queue_key))
|
||||||
|
elif key_type == 'set':
|
||||||
|
length = cast(int, self.client.scard(self.queue_key))
|
||||||
|
return length
|
||||||
|
except redis.ConnectionError as e:
|
||||||
|
logging.warning(f"与 Redis 的连接中断: {e}。正在尝试重新连接...")
|
||||||
|
return 0
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"获取队列长度失败: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_info(self) -> dict:
|
||||||
|
"""
|
||||||
|
从指定的队列中取出一条信息
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 取出的信息,如果队列为空或出错则返回空字典
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not hasattr(self, 'client') or self.client is None:
|
||||||
|
self.client = redis.Redis(**redis_config)
|
||||||
|
|
||||||
|
if not self.client.exists(self.queue_key):
|
||||||
|
logging.warning(f"队列不存在: {self.queue_key}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
key_type = self.client.type(self.queue_key).decode('utf-8')
|
||||||
|
|
||||||
|
if key_type == 'list':
|
||||||
|
# 从列表左侧弹出一个元素
|
||||||
|
data = self.client.lpop(self.queue_key)
|
||||||
|
elif key_type == 'set':
|
||||||
|
# 从集合中随机弹出一个元素
|
||||||
|
data = self.client.spop(self.queue_key)
|
||||||
|
else:
|
||||||
|
logging.warning(f"不支持的队列类型: {key_type}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# print("data=", data)
|
||||||
|
|
||||||
|
# 假设存储的是 JSON 格式的字符串
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
return json.loads(data.decode('utf-8'))
|
||||||
|
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||||
|
# 如果不是 JSON,返回原始字符串作为值
|
||||||
|
return {"data": data.decode('utf-8')}
|
||||||
|
|
||||||
|
except redis.ConnectionError as e:
|
||||||
|
logging.warning(f"Redis 连接中断: {e}")
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"获取队列信息失败: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
"""让我们可以对实例使用 len() 函数,更符合 Python 风格。"""
|
||||||
|
return self.get_length()
|
||||||
|
|
||||||
|
import time
|
||||||
|
import gc
|
||||||
|
def main():
|
||||||
|
redis_queue_name = "pb:print_order_type_setting"
|
||||||
|
# while True:
|
||||||
|
try:
|
||||||
|
redis_queue = RedisTaskQueue(redis_queue_name)
|
||||||
|
task_num = redis_queue.get_length()
|
||||||
|
|
||||||
|
# print("task_num=", task_num)
|
||||||
|
|
||||||
|
if task_num <= 0:
|
||||||
|
time.sleep(10)
|
||||||
|
sys.exit(0)
|
||||||
|
return
|
||||||
|
|
||||||
|
info = redis_queue.get_info()
|
||||||
|
|
||||||
|
print("info=", info)
|
||||||
|
|
||||||
|
process_clound_print(info)
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"处理任务时出错: {e}")
|
||||||
|
|
||||||
|
time.sleep(2.5)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def test_main():
|
||||||
|
# while True:
|
||||||
|
# data= {'machine_print_counts': 150, 'pre_batch_id': 9910032, 'print_machine_id': 14, 'print_orders': [{'counts': 1, 'layout_z': 35.62070959151145, 'model_weight': 41.862, 'order_id': 917900, 'pid': 376666, 'print_id': 126318}, {'counts': 1, 'layout_z': 31.380497732946978, 'model_weight': 35.158, 'order_id': 917810, 'pid': 376562, 'print_id': 126413}, {'counts': 1, 'layout_z': 26.136854969886777, 'model_weight': 21.403, 'order_id': 917820, 'pid': 376572, 'print_id': 126414}, {'counts': 1, 'layout_z': 37.967177899738104, 'model_weight': 30.48, 'order_id': 917922, 'pid': 376684, 'print_id': 126415}, {'counts': 1, 'layout_z': 20.368887098446734, 'model_weight': 13.097, 'order_id': 918228, 'pid': 377198, 'print_id': 126417}, {'counts': 1, 'layout_z': 21.4227336927951, 'model_weight': 15.859, 'order_id': 917576, 'pid': 376345, 'print_id': 126769}, {'counts': 1, 'layout_z': 27.539947066784066, 'model_weight': 25.185, 'order_id': 917811, 'pid': 376564, 'print_id': 126875}, {'counts': 1, 'layout_z': 31.692723822970393, 'model_weight': 32.796, 'order_id': 917821, 'pid': 376592, 'print_id': 126876}, {'counts': 1, 'layout_z': 20.20031720146163, 'model_weight': 8.664, 'order_id': 917822, 'pid': 376602, 'print_id': 126877}, {'counts': 1, 'layout_z': 22.41680629925537, 'model_weight': 15.206, 'order_id': 917827, 'pid': 376616, 'print_id': 126878}, {'counts': 1, 'layout_z': 25.443981671314514, 'model_weight': 11.965, 'order_id': 917879, 'pid': 376635, 'print_id': 126879}, {'counts': 1, 'layout_z': 31.09222672704002, 'model_weight': 20.318, 'order_id': 917902, 'pid': 376667, 'print_id': 126882}, {'counts': 1, 'layout_z': 30.3317820592175, 'model_weight': 32.964, 'order_id': 917903, 'pid': 376668, 'print_id': 126884}, {'counts': 1, 'layout_z': 28.69639626624317, 'model_weight': 27.751, 'order_id': 917904, 'pid': 376669, 'print_id': 126885}, {'counts': 1, 'layout_z': 28.043180029163203, 'model_weight': 19.866, 'order_id': 917917, 'pid': 376677, 'print_id': 126886}, {'counts': 1, 'layout_z': 28.58783468947201, 'model_weight': 25.505, 'order_id': 918196, 'pid': 377126, 'print_id': 126888}, {'counts': 1, 'layout_z': 43.55871899466554, 'model_weight': 63.512, 'order_id': 918201, 'pid': 377131, 'print_id': 126890}, {'counts': 1, 'layout_z': 32.52479624889478, 'model_weight': 22.943, 'order_id': 918203, 'pid': 377133, 'print_id': 126891}, {'counts': 1, 'layout_z': 33.21834123013511, 'model_weight': 42.163, 'order_id': 918206, 'pid': 377139, 'print_id': 126892}, {'counts': 1, 'layout_z': 27.81674335447885, 'model_weight': 15.455, 'order_id': 918207, 'pid': 377141, 'print_id': 126893}, {'counts': 1, 'layout_z': 29.78203639784899, 'model_weight': 29.696, 'order_id': 918231, 'pid': 377201, 'print_id': 126894}, {'counts': 1, 'layout_z': 23.66875736859606, 'model_weight': 13.39, 'order_id': 915637, 'pid': 373135, 'print_id': 126895}, {'counts': 1, 'layout_z': 29.00408914521088, 'model_weight': 19.553, 'order_id': 918246, 'pid': 377209, 'print_id': 126897}, {'counts': 1, 'layout_z': 24.44397251338635, 'model_weight': 15.424, 'order_id': 918325, 'pid': 376974, 'print_id': 126899}, {'counts': 1, 'layout_z': 31.755406379699707, 'model_weight': 31.131, 'order_id': 918492, 'pid': 377584, 'print_id': 126900}, {'counts': 1, 'layout_z': 38.03699946264237, 'model_weight': 47.412, 'order_id': 918493, 'pid': 377587, 'print_id': 126901}, {'counts': 1, 'layout_z': 43.52919799223749, 'model_weight': 37.842, 'order_id': 918494, 'pid': 377589, 'print_id': 126902}, {'counts': 1, 'layout_z': 25.906302369108843, 'model_weight': 18.64, 'order_id': 918495, 'pid': 377590, 'print_id': 126903}, {'counts': 1, 'layout_z': 36.02611978829816, 'model_weight': 43.814, 'order_id': 918496, 'pid': 377591, 'print_id': 126904}, {'counts': 1, 'layout_z': 26.020689766262052, 'model_weight': 18.513, 'order_id': 918497, 'pid': 377594, 'print_id': 126905}, {'counts': 1, 'layout_z': 23.988078117370605, 'model_weight': 15.753, 'order_id': 918498, 'pid': 377596, 'print_id': 126906}, {'counts': 1, 'layout_z': 27.457814063914604, 'model_weight': 23.507, 'order_id': 918500, 'pid': 377601, 'print_id': 126908}, {'counts': 1, 'layout_z': 29.306090320672993, 'model_weight': 25.043, 'order_id': 918501, 'pid': 377605, 'print_id': 126909}, {'counts': 1, 'layout_z': 29.070231478924583, 'model_weight': 25.598, 'order_id': 918504, 'pid': 377606, 'print_id': 126910}, {'counts': 1, 'layout_z': 24.314504768436095, 'model_weight': 18.68, 'order_id': 918555, 'pid': 377684, 'print_id': 126911}, {'counts': 1, 'layout_z': 37.52979171030315, 'model_weight': 53.541, 'order_id': 918558, 'pid': 377689, 'print_id': 126913}, {'counts': 1, 'layout_z': 26.530791960490333, 'model_weight': 15.082, 'order_id': 918560, 'pid': 377702, 'print_id': 126915}, {'counts': 1, 'layout_z': 23.604568481445312, 'model_weight': 21.614, 'order_id': 918583, 'pid': 377748, 'print_id': 126917}, {'counts': 1, 'layout_z': 27.865868478825355, 'model_weight': 22.6, 'order_id': 918636, 'pid': 377819, 'print_id': 126919}, {'counLine truncated
|
||||||
|
data= {"machine_print_counts":50,"pre_batch_id":991112,"print_machine_id":22,"print_orders":[{"counts":1,"layout_z":3.543965775898459,"model_weight":12.833,"order_id":857420,"pid":268473,"print_id":85240},{"counts":1,"layout_z":7.798861189952786,"model_weight":13.784,"order_id":875986,"pid":305425,"print_id":88136}]}
|
||||||
|
|
||||||
|
process_clound_print(data)
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
import os
|
||||||
|
def clear_directory_recursive(dir_path):
|
||||||
|
"""
|
||||||
|
递归清空目录:保留原目录,递归删除其所有内容和子内容。
|
||||||
|
"""
|
||||||
|
if not os.path.isdir(dir_path):
|
||||||
|
print(f"路径 {dir_path} 不是一个有效目录。")
|
||||||
|
return
|
||||||
|
|
||||||
|
for item_name in os.listdir(dir_path):
|
||||||
|
item_path = os.path.join(dir_path, item_name)
|
||||||
|
|
||||||
|
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||||
|
# 如果是文件或符号链接,直接删除
|
||||||
|
try:
|
||||||
|
os.unlink(item_path)
|
||||||
|
print(f"已删除: {item_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"删除失败 {item_path}: {e}")
|
||||||
|
elif os.path.isdir(item_path):
|
||||||
|
# 如果是子目录,递归调用函数清空它,然后删除这个空目录
|
||||||
|
try:
|
||||||
|
clear_directory_recursive(item_path) # 递归清空子目录
|
||||||
|
os.rmdir(item_path) # 删除现已为空的子目录
|
||||||
|
print(f"已删除子目录: {item_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"删除子目录失败 {item_path}: {e}")
|
||||||
|
|
||||||
|
print(f"目录 {dir_path} 下的内容已清空。")
|
||||||
|
|
||||||
|
def process_clound_print(data):
|
||||||
|
print_ids = []
|
||||||
|
list_print_model_info = []
|
||||||
|
|
||||||
|
selected_machine = "大机型"
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
# parsed = json.loads(data.decode('utf-8'))
|
||||||
|
parsed = data
|
||||||
|
|
||||||
|
pre_batch_id = parsed["pre_batch_id"]
|
||||||
|
print(f"pre_batch_id={pre_batch_id}")
|
||||||
|
|
||||||
|
machine_print_counts = parsed["machine_print_counts"]
|
||||||
|
|
||||||
|
machine_type = "big_machine"
|
||||||
|
if not is_test:
|
||||||
|
machine_type = parsed["machine_type"]
|
||||||
|
|
||||||
|
if machine_type == "small_machine" :
|
||||||
|
selected_machine = "小机型"
|
||||||
|
else :
|
||||||
|
selected_machine = "大机型"
|
||||||
|
|
||||||
|
print_orders = parsed.get("print_orders", [])
|
||||||
|
|
||||||
|
if not print_orders:
|
||||||
|
print("没有找到订单数据")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for index, order in enumerate(print_orders, 1):
|
||||||
|
order_info = {
|
||||||
|
'index': index,
|
||||||
|
'counts': order.get('counts'),
|
||||||
|
'pid': order.get('pid'),
|
||||||
|
'print_id': order.get('print_id'),
|
||||||
|
'order_id': order.get('order_id'),
|
||||||
|
'layout_z': order.get('layout_z')
|
||||||
|
}
|
||||||
|
|
||||||
|
results.append(order_info)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# 打印订单信息
|
||||||
|
print(f"订单 #{index}:")
|
||||||
|
print(f" counts: {order_info['counts']}")
|
||||||
|
print(f" PID: {order_info['pid']}")
|
||||||
|
print(f" print_id: {order_info['print_id']}")
|
||||||
|
print(f" 布局Z: {order_info['layout_z']}")
|
||||||
|
"""
|
||||||
|
|
||||||
|
print_ids.append(order_info['print_id'])
|
||||||
|
|
||||||
|
# list_print_model_info.append(batch_model_info)
|
||||||
|
|
||||||
|
# print("-" * 40)
|
||||||
|
|
||||||
|
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||||
|
# 如果不是 JSON,返回原始字符串作为值
|
||||||
|
print("error!")
|
||||||
|
|
||||||
|
print(f"print_ids={print_ids}")
|
||||||
|
|
||||||
|
# print_ids2 = [115988, 118411]
|
||||||
|
|
||||||
|
# api_addr_pre = "https://mp.api.suwa3d.com"
|
||||||
|
# test = True
|
||||||
|
# if test:
|
||||||
|
# api_addr_pre = "http://127.0.0.1:8199"
|
||||||
|
# url = f"{api_addr_pre}/api/printOrder/getInfoByPrintIds?print_ids={print_ids}"
|
||||||
|
|
||||||
|
url = f"https://mp.api.suwa3d.com/api/printOrder/getInfoByPrintIds?print_ids={print_ids}"
|
||||||
|
res = requests.get(url)
|
||||||
|
|
||||||
|
data = res.json()["data"]
|
||||||
|
|
||||||
|
for index, order in enumerate(data, 1):
|
||||||
|
order_info = {
|
||||||
|
'order_id': order.get('order_id'),
|
||||||
|
'pid': order.get('pid'),
|
||||||
|
'print_order_id': order.get('print_order_id'),
|
||||||
|
'real_size': order.get('real_size'),
|
||||||
|
'path': order.get('path'),
|
||||||
|
'quantity': order.get('quantity')
|
||||||
|
}
|
||||||
|
|
||||||
|
results.append(order_info)
|
||||||
|
|
||||||
|
model_size = f"{order_info['real_size']}_x{order_info['quantity']}"
|
||||||
|
# 打印订单信息
|
||||||
|
# print(f"订单 #{order_info['order_id']}:")
|
||||||
|
# print(f" PID: {order_info['pid']}")
|
||||||
|
# print(f" print_order_id: {order_info['print_order_id']}")
|
||||||
|
# print(f" model_size: {model_size}")
|
||||||
|
# print(f" quantity: {order_info['quantity']}")
|
||||||
|
# print(f" path: {order_info['path']}")
|
||||||
|
|
||||||
|
batch_model_info = BatchModelInfo(
|
||||||
|
order_id=order_info["order_id"],
|
||||||
|
pid=order_info["pid"],
|
||||||
|
print_order_id=order_info["print_order_id"],
|
||||||
|
model_size=model_size,
|
||||||
|
path=order_info["path"],
|
||||||
|
count= order_info["quantity"] if order_info["quantity"] > 1 else 1
|
||||||
|
)
|
||||||
|
list_print_model_info.append(batch_model_info)
|
||||||
|
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
print_factory_type_dir="/root/print_factory_type"
|
||||||
|
workdir = f"{print_factory_type_dir}/data/{pre_batch_id}"
|
||||||
|
oss_config = f"{print_factory_type_dir}/print_factory_type_setting_big/download_print/run.yaml"
|
||||||
|
|
||||||
|
clear_directory_recursive(f"{print_factory_type_dir}/data/")
|
||||||
|
clear_directory_recursive(f"{print_factory_type_dir}/full/")
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
if not download_datas_by_pre_layout(list_print_model_info, workdir, oss_config):
|
||||||
|
print(f"下载失败,排版终止 批次={pre_batch_id}")
|
||||||
|
return
|
||||||
|
print(f"下载耗时:{time.time()-start_time}")
|
||||||
|
# 下载结束
|
||||||
|
|
||||||
|
# 排版开始
|
||||||
|
src_dir = pre_batch_id
|
||||||
|
selected_mode="紧凑" # 标准 紧凑
|
||||||
|
output_format="JSON" # 模型 JSON
|
||||||
|
cache_type_setting_dir=f"{print_factory_type_dir}/data/{src_dir}/arrange"
|
||||||
|
base_original_obj_dir = f"{print_factory_type_dir}/data/{src_dir}"
|
||||||
|
print_type_setting_obj(base_original_obj_dir=base_original_obj_dir,cache_type_setting_dir=cache_type_setting_dir,
|
||||||
|
batch_id=pre_batch_id,show_chart=False,selected_mode=selected_mode,output_format=output_format,selected_machine=selected_machine)
|
||||||
|
#排版结束
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
if is_test:
|
||||||
|
test_main()
|
||||||
|
else:
|
||||||
|
print(f"is_test={is_test}, run main")
|
||||||
|
main()
|
||||||
|
|
||||||
+1569
File diff suppressed because it is too large.
Load diff
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
run:
|
||||||
|
down:
|
||||||
|
AccessKeyId: 'LTAI5tJDLxK6wBdHE9Nu443G'
|
||||||
|
AccessKeySecret: 'sBN7IK4ozSE9nNtmD3dmDSuiS24SZq'
|
||||||
|
Endpoint: 'oss-cn-shanghai.aliyuncs.com'
|
||||||
|
Bucket: 'suwa3d-securedata'
|
||||||
@@ -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 @@
|
|||||||
|
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 @@
|
|||||||
|
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 @@
|
|||||||
|
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 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
while true; do
|
||||||
|
python clound_print.py | tee -a output.log
|
||||||
|
echo "脚本执行完成,等待10秒后重新运行..."
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
@@ -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 @@
|
|||||||
|
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 @@
|
|||||||
|
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,127 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import pandas as pd
|
||||||
|
from threading import Thread
|
||||||
|
from PyQt5.QtWidgets import QApplication, QMainWindow,QMessageBox
|
||||||
|
from print_setting_ui import Ui_MainWindow
|
||||||
|
from PyQt5.QtGui import QFont
|
||||||
|
from PySide2.QtCore import Signal,QObject
|
||||||
|
from PyQt5.QtCore import QThread, pyqtSignal, QProcess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
import time
|
||||||
|
import time
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||||
|
QVBoxLayout, QFileDialog, QMessageBox)
|
||||||
|
from threading import Thread
|
||||||
|
from print_factory_type_setting_obj_run import print_type_setting_obj
|
||||||
|
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
pd.set_option('display.width', None)
|
||||||
|
|
||||||
|
|
||||||
|
class MySignals(QObject):
|
||||||
|
text_print = Signal(str)
|
||||||
|
update_table = Signal(str)
|
||||||
|
|
||||||
|
|
||||||
|
class MyMainForm(QMainWindow, Ui_MainWindow):
|
||||||
|
def __init__(self,parent=None):
|
||||||
|
super(MyMainForm, self).__init__(parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.folder_path = ""
|
||||||
|
self.cache_path = ""
|
||||||
|
self.pushButton.clicked.connect(self.on_select_folder)
|
||||||
|
self.pushButton_2.clicked.connect(self.on_run_clicked)
|
||||||
|
self.pushButton_3.clicked.connect(self.on_open_output_clicked)
|
||||||
|
self.global_ms = MySignals()
|
||||||
|
self.global_ms.text_print.connect(self.printToGui)
|
||||||
|
|
||||||
|
def printToGui(self,text):
|
||||||
|
n_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
||||||
|
self.textBrowser.append(n_time+" "+str(text))
|
||||||
|
|
||||||
|
def on_select_folder(self):
|
||||||
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||||
|
if folder:
|
||||||
|
self.folder_path = folder
|
||||||
|
#self.folder_path_label.setText(f"📂 当前选择文件夹: {folder}")
|
||||||
|
#self.run_status_label.setText("")
|
||||||
|
self.cache_path = folder + "_arrange"
|
||||||
|
os.makedirs(self.cache_path, exist_ok=True)
|
||||||
|
|
||||||
|
def on_run_clicked(self):
|
||||||
|
|
||||||
|
def threadFunc1():
|
||||||
|
if not self.folder_path:
|
||||||
|
#self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# if hasattr(self, 'worker') and self.worker.isRunning():
|
||||||
|
# self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||||
|
# return
|
||||||
|
#self.run_btn.setEnabled(False)
|
||||||
|
#self.run_status_label.setText("🚀 程序正在运行,请稍候...")
|
||||||
|
|
||||||
|
print_type_setting_obj(
|
||||||
|
base_original_obj_dir=self.folder_path,
|
||||||
|
cache_type_setting_dir=self.cache_path,
|
||||||
|
show_chart=False
|
||||||
|
)
|
||||||
|
|
||||||
|
#self.run_status_label.setText("✅ 排版完成!")
|
||||||
|
|
||||||
|
thread = Thread(target=threadFunc1)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def open_file_cross_platform(self, path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
print("路径不存在!")
|
||||||
|
return
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
os.startfile(path)
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
QProcess.startDetached("open", [path])
|
||||||
|
else:
|
||||||
|
QProcess.startDetached("xdg-open", [path])
|
||||||
|
|
||||||
|
def on_open_output_clicked(self):
|
||||||
|
output_path = os.path.join(self.cache_path, "print_compact_obj")
|
||||||
|
if os.path.exists(output_path):
|
||||||
|
self.open_file_cross_platform(output_path)
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||||
|
|
||||||
|
|
||||||
|
def douyin_spider_go(self):
|
||||||
|
"""下载抖音视频"""
|
||||||
|
def threadFunc1():
|
||||||
|
print("开始下载")
|
||||||
|
self.load_chrome_video()
|
||||||
|
thread = Thread(target=threadFunc1)
|
||||||
|
thread.start()
|
||||||
|
#thread.run()
|
||||||
|
|
||||||
|
def open_config_dir(self):
|
||||||
|
"""打开文件夹"""
|
||||||
|
def threadFunc1():
|
||||||
|
try:
|
||||||
|
start_directory = os.path.join(self.dir_base)
|
||||||
|
os.startfile(start_directory)
|
||||||
|
except RecursionError:
|
||||||
|
print("打开配置文件夹失败。")
|
||||||
|
thread = Thread(target=threadFunc1)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
#multiprocessing.freeze_support()
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
myWin = MyMainForm()
|
||||||
|
myWin.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
@@ -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", "打开排版好的文件夹"))
|
||||||
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,346 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||||
|
QVBoxLayout, QFileDialog, QMessageBox,
|
||||||
|
QRadioButton, QHBoxLayout, QButtonGroup, QGroupBox)
|
||||||
|
from PyQt5.QtCore import QProcess, QTimer
|
||||||
|
from threading import Thread
|
||||||
|
import print_factory_type_setting_obj_run
|
||||||
|
|
||||||
|
|
||||||
|
class AutoLayoutApp(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.small_model_temp = False
|
||||||
|
self.setWindowTitle("自动排版工具")
|
||||||
|
self.setGeometry(200, 200, 600, 380) # 增加窗口高度以容纳新控件
|
||||||
|
self.dw = print_factory_type_setting_obj_run
|
||||||
|
self.folder_path = ""
|
||||||
|
self.cache_path = ""
|
||||||
|
self.process = None
|
||||||
|
if self.small_model_temp:
|
||||||
|
self.selected_mode = "紧凑" # 默认排版模式
|
||||||
|
self.output_format = "模型" # 默认输出格式
|
||||||
|
self.selected_machine = "小机型" # 默认机型
|
||||||
|
else:
|
||||||
|
self.selected_mode = "标准" # 默认排版模式
|
||||||
|
self.output_format = "JSON" # 默认输出格式
|
||||||
|
self.selected_machine = "大机型" # 默认机型
|
||||||
|
self.is_running = False # 跟踪排版状态
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# ===== 机型选择区域(移到最上面) =====
|
||||||
|
machine_group = QGroupBox("机型选择")
|
||||||
|
machine_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
# 创建机型选择按钮组
|
||||||
|
self.machine_group = QButtonGroup(self)
|
||||||
|
|
||||||
|
# 创建两种机型选项
|
||||||
|
self.large_machine = QRadioButton("大机型(600 * 500 * 300)")
|
||||||
|
self.small_machine = QRadioButton("小机型(380 * 345 * 250)")
|
||||||
|
|
||||||
|
if self.small_model_temp:
|
||||||
|
self.large_machine.setEnabled(False)
|
||||||
|
self.small_machine.setChecked(True) # 默认选中
|
||||||
|
else:
|
||||||
|
self.large_machine.setChecked(True) # 默认选中
|
||||||
|
|
||||||
|
# 添加到按钮组
|
||||||
|
self.machine_group.addButton(self.large_machine, 1)
|
||||||
|
self.machine_group.addButton(self.small_machine, 2)
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
self.machine_group.buttonToggled.connect(self.on_machine_changed)
|
||||||
|
|
||||||
|
# 添加到布局
|
||||||
|
machine_layout.addWidget(self.large_machine)
|
||||||
|
machine_layout.addWidget(self.small_machine)
|
||||||
|
machine_group.setLayout(machine_layout)
|
||||||
|
layout.addWidget(machine_group)
|
||||||
|
|
||||||
|
# ===== 排版模式选择区域(改为QGroupBox) =====
|
||||||
|
mode_group = QGroupBox("排版模式")
|
||||||
|
mode_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
# 创建单选按钮组
|
||||||
|
self.mode_group = QButtonGroup(self)
|
||||||
|
|
||||||
|
# 创建三种排版模式选项
|
||||||
|
self.standard_mode = QRadioButton("标准模式(适合规整模型组)")
|
||||||
|
self.compact_mode = QRadioButton("紧凑模式(复杂度较高))")
|
||||||
|
self.advanced_mode = QRadioButton("高级模式(复杂度最高)")
|
||||||
|
self.advanced_mode.setVisible(False)
|
||||||
|
|
||||||
|
if self.small_model_temp:
|
||||||
|
self.standard_mode.setEnabled(False)
|
||||||
|
self.compact_mode.setChecked(True) # 默认选中
|
||||||
|
else:
|
||||||
|
self.standard_mode.setChecked(True) # 默认选中
|
||||||
|
|
||||||
|
# 添加到按钮组(确保互斥选择)
|
||||||
|
self.mode_group.addButton(self.standard_mode, 1)
|
||||||
|
self.mode_group.addButton(self.compact_mode, 2)
|
||||||
|
self.mode_group.addButton(self.advanced_mode, 3)
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
self.mode_group.buttonToggled.connect(self.on_mode_changed)
|
||||||
|
|
||||||
|
# 添加到布局
|
||||||
|
mode_layout.addWidget(self.standard_mode)
|
||||||
|
mode_layout.addWidget(self.compact_mode)
|
||||||
|
mode_layout.addWidget(self.advanced_mode)
|
||||||
|
mode_group.setLayout(mode_layout)
|
||||||
|
layout.addWidget(mode_group)
|
||||||
|
|
||||||
|
# ==== 输出格式选择区域 ====
|
||||||
|
format_group = QGroupBox("输出格式")
|
||||||
|
format_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
# 创建输出格式按钮组
|
||||||
|
self.format_group = QButtonGroup(self)
|
||||||
|
|
||||||
|
# 创建两种输出格式选项
|
||||||
|
self.json_format = QRadioButton("JSON格式")
|
||||||
|
self.model_format = QRadioButton("模型格式")
|
||||||
|
|
||||||
|
if self.small_model_temp:
|
||||||
|
self.json_format.setEnabled(False)
|
||||||
|
self.model_format.setChecked(True) # 默认选中
|
||||||
|
else:
|
||||||
|
self.json_format.setChecked(True) # 默认选中
|
||||||
|
|
||||||
|
# 添加到按钮组
|
||||||
|
self.format_group.addButton(self.json_format, 1)
|
||||||
|
self.format_group.addButton(self.model_format, 2)
|
||||||
|
|
||||||
|
# 连接信号
|
||||||
|
self.format_group.buttonToggled.connect(self.on_format_changed)
|
||||||
|
|
||||||
|
# 添加到布局
|
||||||
|
format_layout.addWidget(self.json_format)
|
||||||
|
format_layout.addWidget(self.model_format)
|
||||||
|
format_group.setLayout(format_layout)
|
||||||
|
layout.addWidget(format_group)
|
||||||
|
|
||||||
|
# ===== 原有UI元素 =====
|
||||||
|
self.folder_path_label = QLabel(" 请选择要排版的文件夹")
|
||||||
|
self.folder_path_label.setWordWrap(True)
|
||||||
|
|
||||||
|
self.run_status_label = QLabel("")
|
||||||
|
|
||||||
|
layout.addWidget(self.folder_path_label)
|
||||||
|
layout.addWidget(self.run_status_label)
|
||||||
|
|
||||||
|
self.select_folder_btn = QPushButton("选择文件夹")
|
||||||
|
self.select_folder_btn.clicked.connect(self.on_select_folder)
|
||||||
|
layout.addWidget(self.select_folder_btn)
|
||||||
|
|
||||||
|
# 按钮布局(运行按钮和预览按钮在同一行)
|
||||||
|
buttons_layout = QHBoxLayout()
|
||||||
|
|
||||||
|
self.run_btn = QPushButton("开始自动排版")
|
||||||
|
self.run_btn.clicked.connect(self.on_run_clicked)
|
||||||
|
buttons_layout.addWidget(self.run_btn)
|
||||||
|
|
||||||
|
# ===== 新增预览按钮 =====
|
||||||
|
self.preview_btn = QPushButton("预览排版结果")
|
||||||
|
self.preview_btn.clicked.connect(self.on_preview_clicked)
|
||||||
|
self.preview_btn.setEnabled(False) # 初始禁用,排版完成后才可用
|
||||||
|
buttons_layout.addWidget(self.preview_btn)
|
||||||
|
|
||||||
|
layout.addLayout(buttons_layout)
|
||||||
|
|
||||||
|
self.open_output_btn = QPushButton("打开排版好的文件夹")
|
||||||
|
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
|
||||||
|
layout.addWidget(self.open_output_btn)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def on_mode_changed(self, button, checked):
|
||||||
|
"""处理排版模式选择变化"""
|
||||||
|
if checked:
|
||||||
|
# self.selected_mode = button.text().replace("模式", "")
|
||||||
|
self.selected_mode = button.text()[:2]
|
||||||
|
self.run_status_label.setText(f"已选择: {self.selected_mode} 模式")
|
||||||
|
|
||||||
|
def on_format_changed(self, button, checked):
|
||||||
|
"""处理输出格式选择变化"""
|
||||||
|
if checked:
|
||||||
|
self.output_format = button.text().replace("格式", "")
|
||||||
|
self.run_status_label.setText(f"输出格式: {self.output_format}")
|
||||||
|
|
||||||
|
def on_machine_changed(self, button, checked):
|
||||||
|
"""处理机型选择变化"""
|
||||||
|
if checked:
|
||||||
|
self.selected_machine = button.text().split("(")[0]
|
||||||
|
self.run_status_label.setText(f"已选择: {self.selected_machine}")
|
||||||
|
|
||||||
|
def on_select_folder(self):
|
||||||
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||||
|
if folder:
|
||||||
|
self.folder_path = folder
|
||||||
|
self.folder_path_label.setText(f" 当前选择文件夹: {folder}")
|
||||||
|
self.run_status_label.setText("")
|
||||||
|
self.cache_path = folder + "_arrange"
|
||||||
|
# self.cache_path = os.path.join(folder, "arrange")
|
||||||
|
|
||||||
|
os.makedirs(self.cache_path, exist_ok=True)
|
||||||
|
self.preview_btn.setEnabled(False) # 选择新文件夹后禁用预览按钮
|
||||||
|
|
||||||
|
def get_base_directory(self):
|
||||||
|
"""获取脚本或可执行文件的基础目录"""
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
# 打包后的可执行文件环境
|
||||||
|
base_path = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 正常脚本运行环境
|
||||||
|
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
return base_path
|
||||||
|
|
||||||
|
def on_run_clicked(self):
|
||||||
|
# 获取脚本所在目录的父目录
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_dir = os.path.dirname(script_dir)
|
||||||
|
|
||||||
|
# 获取基础目录
|
||||||
|
base_path = self.get_base_directory()
|
||||||
|
# 获取父目录
|
||||||
|
parent_dir = os.path.dirname(base_path)
|
||||||
|
bad_dir = os.path.join(parent_dir, "bad")
|
||||||
|
full_dir = os.path.join(parent_dir, "full")
|
||||||
|
|
||||||
|
# 检查bad目录
|
||||||
|
bad_dir_exists = os.path.exists(bad_dir) and os.path.isdir(bad_dir)
|
||||||
|
bad_dir_not_empty = bad_dir_exists and any(os.scandir(bad_dir))
|
||||||
|
|
||||||
|
# 检查full目录
|
||||||
|
full_dir_exists = os.path.exists(full_dir) and os.path.isdir(full_dir)
|
||||||
|
full_dir_not_empty = full_dir_exists and any(os.scandir(full_dir))
|
||||||
|
|
||||||
|
# 如果有异常数据需要处理
|
||||||
|
if bad_dir_not_empty or full_dir_not_empty:
|
||||||
|
message = "请处理以下目录中的异常数据:\n"
|
||||||
|
if bad_dir_not_empty:
|
||||||
|
message += f"- bad目录: {bad_dir}\n"
|
||||||
|
if full_dir_not_empty:
|
||||||
|
message += f"- full目录: {full_dir}\n"
|
||||||
|
|
||||||
|
QMessageBox.warning(self, "存在未处理的异常数据",
|
||||||
|
message + "\n请先处理这些目录中的数据后再进行排版!")
|
||||||
|
self.run_status_label.setText("⚠️ 存在未处理的异常数据,请先处理!")
|
||||||
|
return
|
||||||
|
|
||||||
|
def threadFunc1():
|
||||||
|
self.is_running = True # 标记排版开始
|
||||||
|
if not self.folder_path:
|
||||||
|
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||||
|
self.is_running = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.process and self.process.state() == QProcess.Running:
|
||||||
|
self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||||
|
self.is_running = False
|
||||||
|
return
|
||||||
|
|
||||||
|
self.run_btn.setEnabled(False)
|
||||||
|
self.preview_btn.setEnabled(False) # 排版中禁用预览按钮
|
||||||
|
self.run_status_label.setText(
|
||||||
|
f" 正在使用 [{self.selected_mode}] 排版, 机型: {self.selected_machine}, 输出格式: {self.output_format}, 请稍候..."
|
||||||
|
)
|
||||||
|
|
||||||
|
normalized_path = os.path.normpath(self.folder_path)
|
||||||
|
self.batch_id = os.path.basename(normalized_path)
|
||||||
|
|
||||||
|
# 调用排版函数,传递所有参数
|
||||||
|
self.dw.print_type_setting_obj(
|
||||||
|
base_original_obj_dir=self.folder_path,
|
||||||
|
cache_type_setting_dir=self.cache_path,
|
||||||
|
show_chart=False,
|
||||||
|
batch_id=self.batch_id,
|
||||||
|
selected_mode=self.selected_mode,
|
||||||
|
output_format=self.output_format,
|
||||||
|
selected_machine=self.selected_machine
|
||||||
|
)
|
||||||
|
|
||||||
|
self.run_status_label.setText(
|
||||||
|
f"✅ [{self.selected_mode}] 排版完成! 机型: {self.selected_machine}, 输出格式: {self.output_format}"
|
||||||
|
)
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
self.preview_btn.setEnabled(True) # 排版完成后启用预览按钮
|
||||||
|
self.is_running = False # 标记排版结束
|
||||||
|
|
||||||
|
thread = Thread(target=threadFunc1)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def on_process_finished(self, exitCode, exitStatus):
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
self.preview_btn.setEnabled(True) # 完成后启用预览按钮
|
||||||
|
if exitCode == 0:
|
||||||
|
self.run_status_label.setText("✅ 排版完成!")
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText(f"❌❌ 进程异常退出 (代码: {exitCode})")
|
||||||
|
|
||||||
|
def on_process_error(self, error):
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
self.preview_btn.setEnabled(False) # 出错时禁用预览按钮
|
||||||
|
self.run_status_label.setText(f"❌❌ 发生错误: {error.name}")
|
||||||
|
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
|
||||||
|
|
||||||
|
def open_file_cross_platform(self, path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
self.run_status_label.setText("⚠️ 路径不存在!")
|
||||||
|
return
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
os.startfile(path)
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
QProcess.startDetached("open", [path])
|
||||||
|
else:
|
||||||
|
QProcess.startDetached("xdg-open", [path])
|
||||||
|
|
||||||
|
def on_preview_clicked(self):
|
||||||
|
"""预览按钮点击事件处理"""
|
||||||
|
if not self.cache_path:
|
||||||
|
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||||
|
return
|
||||||
|
|
||||||
|
is_small_machine = self.selected_machine=="小机型"
|
||||||
|
|
||||||
|
if os.path.exists(self.folder_path):
|
||||||
|
self.run_status_label.setText("正在打开预览目录...")
|
||||||
|
self.dw.preview(
|
||||||
|
base_original_obj_dir=self.folder_path,batch_id=self.batch_id, is_small_machine=is_small_machine
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText("⚠️ 预览目录不存在!")
|
||||||
|
|
||||||
|
def on_open_output_clicked(self):
|
||||||
|
"""打开排版结果文件夹"""
|
||||||
|
if not self.cache_path:
|
||||||
|
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||||
|
return
|
||||||
|
|
||||||
|
open_dir = self.cache_path
|
||||||
|
if self.output_format=="JSON":
|
||||||
|
open_dir = self.folder_path
|
||||||
|
|
||||||
|
if os.path.exists(open_dir):
|
||||||
|
self.open_file_cross_platform(open_dir)
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from PyQt5.QtCore import QSharedMemory
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
|
||||||
|
if not shared_mem.create(1):
|
||||||
|
QMessageBox.critical(None, "错误", "程序已经在运行中!")
|
||||||
|
sys.exit(1)
|
||||||
|
window = AutoLayoutApp()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
@@ -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,126 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
|
||||||
|
QVBoxLayout, QFileDialog, QMessageBox)
|
||||||
|
from PyQt5.QtCore import QProcess, QTimer
|
||||||
|
from threading import Thread
|
||||||
|
import print_factory_type_setting_obj_run
|
||||||
|
|
||||||
|
|
||||||
|
class AutoLayoutApp(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("自动排版工具")
|
||||||
|
self.setGeometry(200, 200, 600, 200)
|
||||||
|
self.dw= print_factory_type_setting_obj_run
|
||||||
|
self.folder_path = ""
|
||||||
|
self.cache_path = ""
|
||||||
|
self.process = None
|
||||||
|
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
|
self.folder_path_label = QLabel("�� 请选择要排版的文件夹")
|
||||||
|
self.folder_path_label.setWordWrap(True)
|
||||||
|
|
||||||
|
self.run_status_label = QLabel("")
|
||||||
|
|
||||||
|
layout.addWidget(self.folder_path_label)
|
||||||
|
layout.addWidget(self.run_status_label)
|
||||||
|
|
||||||
|
self.select_folder_btn = QPushButton("选择文件夹")
|
||||||
|
self.select_folder_btn.clicked.connect(self.on_select_folder)
|
||||||
|
layout.addWidget(self.select_folder_btn)
|
||||||
|
|
||||||
|
self.run_btn = QPushButton("开始自动排版")
|
||||||
|
self.run_btn.clicked.connect(self.on_run_clicked)
|
||||||
|
layout.addWidget(self.run_btn)
|
||||||
|
|
||||||
|
self.open_output_btn = QPushButton("打开排版好的文件夹")
|
||||||
|
self.open_output_btn.clicked.connect(self.on_open_output_clicked)
|
||||||
|
layout.addWidget(self.open_output_btn)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def on_select_folder(self):
|
||||||
|
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
|
||||||
|
if folder:
|
||||||
|
self.folder_path = folder
|
||||||
|
self.folder_path_label.setText(f"�� 当前选择文件夹: {folder}")
|
||||||
|
self.run_status_label.setText("")
|
||||||
|
self.cache_path = folder + "_arrange"
|
||||||
|
os.makedirs(self.cache_path, exist_ok=True)
|
||||||
|
|
||||||
|
def on_run_clicked(self):
|
||||||
|
def threadFunc1():
|
||||||
|
if not self.folder_path:
|
||||||
|
self.run_status_label.setText("❗请先选择一个文件夹再执行!")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.process and self.process.state() == QProcess.Running:
|
||||||
|
self.run_status_label.setText("⚠️ 正在执行中,请稍候...")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.run_btn.setEnabled(False)
|
||||||
|
self.run_status_label.setText("�� 程序正在运行,请稍候...")
|
||||||
|
|
||||||
|
return_code = self.dw.print_type_setting_obj(base_original_obj_dir=self.folder_path,cache_type_setting_dir=self.cache_path,
|
||||||
|
show_chart=False)
|
||||||
|
if return_code==0:
|
||||||
|
self.run_status_label.setText("✅ 排版完成!")
|
||||||
|
elif return_code==-1:
|
||||||
|
self.run_status_label.setText("❌选择目录为空!")
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText("❌排版失败!")
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
|
||||||
|
thread = Thread(target=threadFunc1)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def on_process_finished(self, exitCode, exitStatus):
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
if exitCode == 0:
|
||||||
|
self.run_status_label.setText("✅ 排版完成!")
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText(f"❌ 进程异常退出 (代码: {exitCode})")
|
||||||
|
|
||||||
|
def on_process_error(self, error):
|
||||||
|
self.run_btn.setEnabled(True)
|
||||||
|
self.run_status_label.setText(f"❌ 发生错误: {error.name}")
|
||||||
|
QMessageBox.critical(self, "错误", f"进程执行出错: {error.name}")
|
||||||
|
|
||||||
|
def open_file_cross_platform(self, path):
|
||||||
|
if not os.path.exists(path):
|
||||||
|
self.run_status_label.setText("⚠️ 路径不存在!")
|
||||||
|
return
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
os.startfile(path)
|
||||||
|
elif sys.platform.startswith('darwin'):
|
||||||
|
QProcess.startDetached("open", [path])
|
||||||
|
else:
|
||||||
|
QProcess.startDetached("xdg-open", [path])
|
||||||
|
|
||||||
|
def on_open_output_clicked(self):
|
||||||
|
if not self.cache_path:
|
||||||
|
self.run_status_label.setText("⚠️ 请先执行排版操作!")
|
||||||
|
return
|
||||||
|
|
||||||
|
output_path = os.path.join(self.cache_path, "print_compact_obj")
|
||||||
|
if os.path.exists(output_path):
|
||||||
|
self.open_file_cross_platform(output_path)
|
||||||
|
else:
|
||||||
|
self.run_status_label.setText("⚠️ 输出文件夹不存在!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from PyQt5.QtCore import QSharedMemory
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
shared_mem = QSharedMemory("AutoLayoutTool_unique_key")
|
||||||
|
if not shared_mem.create(1):
|
||||||
|
QMessageBox.critical(None, "错误", "程序已经在运行中!")
|
||||||
|
sys.exit(1)
|
||||||
|
window = AutoLayoutApp()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
+25
@@ -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 @@
|
|||||||
|
# -*- 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 @@
|
|||||||
|
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 @@
|
|||||||
|
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 @@
|
|||||||
|
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 @@
|
|||||||
|
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 @@
|
|||||||
|
# import numpy as np
|
||||||
|
# def calculate_rotation_and_center_of_mass(angle_x, angle_y, angle_z, points):
|
||||||
|
# """计算某一组旋转角度后的重心"""
|
||||||
|
# # 计算绕X轴、Y轴和Z轴的旋转矩阵
|
||||||
|
# R_x = np.array([
|
||||||
|
# [1, 0, 0],
|
||||||
|
# [0, np.cos(np.radians(angle_x)), -np.sin(np.radians(angle_x))],
|
||||||
|
# [0, np.sin(np.radians(angle_x)), np.cos(np.radians(angle_x))]
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# R_y = np.array([
|
||||||
|
# [np.cos(np.radians(angle_y)), 0, np.sin(np.radians(angle_y))],
|
||||||
|
# [0, 1, 0],
|
||||||
|
# [-np.sin(np.radians(angle_y)), 0, np.cos(np.radians(angle_y))]
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# R_z = np.array([
|
||||||
|
# [np.cos(np.radians(angle_z)), -np.sin(np.radians(angle_z)), 0],
|
||||||
|
# [np.sin(np.radians(angle_z)), np.cos(np.radians(angle_z)), 0],
|
||||||
|
# [0, 0, 1]
|
||||||
|
# ])
|
||||||
|
#
|
||||||
|
# # 综合旋转矩阵
|
||||||
|
# R = R_z @ R_y @ R_x
|
||||||
|
#
|
||||||
|
# # 执行旋转
|
||||||
|
# rotated_points = points @ R.T
|
||||||
|
#
|
||||||
|
# # 计算最小z值
|
||||||
|
# min_z = np.min(rotated_points[:, 2])
|
||||||
|
#
|
||||||
|
# # 计算平移向量,将最小Z值平移到0
|
||||||
|
# translation_vector = np.array([0, 0, -min_z])
|
||||||
|
# rotated_points += translation_vector
|
||||||
|
#
|
||||||
|
# # 计算重心
|
||||||
|
# center_of_mass = np.mean(rotated_points, axis=0)
|
||||||
|
#
|
||||||
|
# return center_of_mass[2], angle_x, angle_y, angle_z
|
||||||
|
#
|
||||||
|
# def parallel_rotation(points, angle_step=3):
|
||||||
|
# """顺序计算最优旋转角度(单线程)"""
|
||||||
|
# min_center_of_mass_y = float('inf')
|
||||||
|
# best_angle_x, best_angle_y, best_angle_z = 0, 0, 0
|
||||||
|
#
|
||||||
|
# # 遍历所有角度组合
|
||||||
|
# for angle_x in range(0, 360, angle_step):
|
||||||
|
# for angle_y in range(0, 360, angle_step):
|
||||||
|
# for angle_z in range(0, 360, angle_step):
|
||||||
|
# center_of_mass_z, ax, ay, az = calculate_rotation_and_center_of_mass(
|
||||||
|
# angle_x, angle_y, angle_z, points
|
||||||
|
# )
|
||||||
|
# if center_of_mass_z < min_center_of_mass_y:
|
||||||
|
# min_center_of_mass_y = center_of_mass_z
|
||||||
|
# best_angle_x, best_angle_y, best_angle_z = ax, ay, az
|
||||||
|
#
|
||||||
|
# return best_angle_x, best_angle_y, best_angle_z, min_center_of_mass_y
|
||||||
|
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def clear_scene():
|
||||||
|
"""清空当前场景中的所有对象"""
|
||||||
|
bpy.ops.object.select_all(action='SELECT')
|
||||||
|
bpy.ops.object.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def test_bpy_io(obj_path, output_path):
|
||||||
|
"""测试 bpy 的 OBJ 读写性能"""
|
||||||
|
# 读取 OBJ
|
||||||
|
start_time = time.time()
|
||||||
|
bpy.ops.import_scene.obj(filepath=obj_path)
|
||||||
|
read_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 确保场景中有对象
|
||||||
|
if not bpy.context.scene.objects:
|
||||||
|
raise ValueError("未成功导入 OBJ 文件")
|
||||||
|
|
||||||
|
# 写入 OBJ
|
||||||
|
start_time = time.time()
|
||||||
|
bpy.ops.export_scene.obj(
|
||||||
|
filepath=output_path,
|
||||||
|
use_selection=False, # 导出所有对象
|
||||||
|
use_materials=False, # 不导出材质(加快速度)
|
||||||
|
)
|
||||||
|
write_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 清理场景
|
||||||
|
clear_scene()
|
||||||
|
|
||||||
|
return write_time, read_time
|
||||||
|
|
||||||
|
|
||||||
|
def test_folder_objs_with_bpy(folder_path, output_folder="output_objs_bpy"):
|
||||||
|
"""测试文件夹中所有 OBJ 文件的读写性能(使用 bpy)"""
|
||||||
|
# 确保输出文件夹存在
|
||||||
|
Path(output_folder).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# 收集所有 OBJ 文件
|
||||||
|
obj_files = [f for f in os.listdir(folder_path) if f.lower().endswith('.obj')]
|
||||||
|
|
||||||
|
if not obj_files:
|
||||||
|
print(f"在文件夹 {folder_path} 中未找到 OBJ 文件")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"找到 {len(obj_files)} 个 OBJ 文件,开始测试...")
|
||||||
|
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for obj_file in obj_files:
|
||||||
|
input_path = os.path.join(folder_path, obj_file)
|
||||||
|
output_path = os.path.join(output_folder, f"bpy_{obj_file}")
|
||||||
|
|
||||||
|
print(f"\n测试文件: {obj_file}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
write_time, read_time = test_bpy_io(input_path, output_path)
|
||||||
|
file_size = os.path.getsize(input_path) / (1024 * 1024) # MB
|
||||||
|
|
||||||
|
print(f" 文件大小: {file_size:.2f} MB")
|
||||||
|
print(f" bpy 读取时间: {read_time:.3f}s")
|
||||||
|
print(f" bpy 写入时间: {write_time:.3f}s")
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"filename": obj_file,
|
||||||
|
"size_mb": file_size,
|
||||||
|
"read_time": read_time,
|
||||||
|
"write_time": write_time,
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 处理 {obj_file} 时出错: {e}")
|
||||||
|
|
||||||
|
# 计算平均时间
|
||||||
|
if results:
|
||||||
|
avg_read = sum(r["read_time"] for r in results) / len(results)
|
||||||
|
avg_write = sum(r["write_time"] for r in results) / len(results)
|
||||||
|
print("\n=== 汇总结果 ===")
|
||||||
|
print(f"平均读取时间: {avg_read:.3f}s")
|
||||||
|
print(f"平均写入时间: {avg_write:.3f}s")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 设置包含OBJ文件的文件夹路径
|
||||||
|
obj_folder = "/data/datasets_20t/9_big/" # 替换为你的OBJ文件夹路径
|
||||||
|
|
||||||
|
# 运行测试
|
||||||
|
test_folder_objs_with_bpy(obj_folder)
|
||||||
+98
@@ -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)
|
||||||
Reference in new issue
Block a user