import numpy as np import itertools def signed_perm_mats_det_plus_1(): """生成所有 3x3 的 ±1 置换矩阵,且 det=+1(24 个)""" mats = [] for perm in itertools.permutations(range(3)): # 6 种列置换 P = np.zeros((3,3), int) for r, c in enumerate(perm): P[r, c] = 1 for signs in itertools.product([1, -1], repeat=3): # 行符号 R = P * np.array(signs)[:, None] # 每行乘 ±1 if int(round(np.linalg.det(R))) == 1: mats.append(R) # 去重(以 tuple 做 key) uniq = {} for R in mats: key = tuple(R.reshape(-1)) uniq[key] = R # 同一个 key 只保留一个 return list(uniq.values()) # 24 个 R24 = signed_perm_mats_det_plus_1() # len(R24) == 24 #import ipdb; ipdb.set_trace() def rot_to_onehot24(R): Rr = np.round(R).astype(int) # 去毛刺到 {-1,0,1} # 直接精确匹配;如果有极少量数值误差,也可以用 L1/L2 最近邻 keys = [tuple(M.reshape(-1)) for M in R24] kR = tuple(Rr.reshape(-1)) if kR in keys: idx = keys.index(kR) else: # 兜底最近邻(谨慎使用) diffs = [np.sum(np.abs(M - Rr)) for M in R24] idx = int(np.argmin(diffs)) one_hot = np.zeros(24, dtype=int) one_hot[idx] = 1 return one_hot, idx def onehot24_to_rot(one_hot): idx = int(np.asarray(one_hot).argmax()) #import ipdb; ipdb.set_trace() return R24[idx] # # 从索引出发:idx -> R -> onehot -> idx_back # for i in range(24): # R = R24[i] # oh, j = rot_to_onehot24(R) # assert j == i # Rb = onehot24_to_rot(oh) # assert np.array_equal(R, Rb) # print("24类旋转:往返一致 ✅") import os import numpy as np from itertools import product, permutations def generate_24_rotations(): """生成 24 个 det=+1 的 ±1 置换矩阵(立方体的旋转群)。""" rots = [] for p in permutations(range(3)): # 轴置换 P = np.zeros((3,3), dtype=int) for i, j in enumerate(p): P[i, j] = 1 for signs in product([-1, 1], repeat=3): # 各轴符号 S = np.diag(signs) R = S @ P if round(np.linalg.det(R)) == 1: # 只要 det=+1 rots.append(R.astype(int)) # 去重并固定顺序 unique = [] for R in rots: if not any(np.array_equal(R, U) for U in unique): unique.append(R) return unique # 长度应为 24 ROTATIONS_24 = generate_24_rotations() def is_signed_perm_det1(R_int): """R_int 是否为 entries∈{-1,0,1} 的 3x3 矩阵,且每行/列唯一一个非零,det=+1。""" if R_int.shape != (3,3): return False if not np.all(np.isin(R_int, [-1, 0, 1])): return False # 每行/列恰好一个非零 if not np.all(np.sum(R_int != 0, axis=1) == 1): return False if not np.all(np.sum(R_int != 0, axis=0) == 1): return False # det=+1 return round(np.linalg.det(R_int)) == 1 def snap_to_signed_perm(R, tol=1e-3): """ 仅将非常接近 -1/0/+1 的元素吸附到 -1/0/+1;若存在明显偏离(例如 0.57、0.82),保持原值——最终判定会失败。 """ R = np.asarray(R, dtype=float) R_snap = R.copy() # 接近 1 -> 1;接近 -1 -> -1;接近 0 -> 0 R_snap[np.isclose(R, 1.0, atol=tol)] = 1.0 R_snap[np.isclose(R, -1.0, atol=tol)] = -1.0 R_snap[np.isclose(R, 0.0, atol=tol)] = 0.0 # 其余保持原值(不是 24 旋转就会在后续判定中失败) return R_snap def match_in_24(R, tol=1e-3): """ 返回 (matched, index, R_int) - matched: 是否匹配 24 旋转 - index: 若匹配,给出在 ROTATIONS_24 中的索引,否则为 None - R_int: 吸附后的整数矩阵(或原矩阵的近似整数版,便于调试) """ R_snap = snap_to_signed_perm(R, tol=tol) # 尝试转成 int(吸附后如果仍有非 -1/0/1 的值,astype 后会引发误判,先检查) if not np.all(np.isin(R_snap, [-1.0, 0.0, 1.0])): return False, None, R_snap # 明显不在 24 旋转里 R_int = R_snap.astype(int) if not is_signed_perm_det1(R_int): return False, None, R_int # 找索引 for idx, R0 in enumerate(ROTATIONS_24): if np.array_equal(R_int, R0): return True, idx, R_int return False, None, R_int def parse_rotation_from_parts(parts): """ parts 是一行 LDR 的分割结果。旋转矩阵是 parts[5:14](9 个数,按行展开)。 返回 3x3 numpy 数组。 """ vals = list(map(float, parts[5:14])) R = np.array(vals, dtype=float).reshape(3,3) return R def check_ldr_file(ldr_path, tol=1e-3, verbose=True): """ 逐行读取 LDR 文件,遇到以 '1 ' 开头且列数>=15 的零件行,抽取旋转矩阵并与 24 旋转比对。 """ total = 0 matched = 0 not_matched = 0 mismatches = [] # (lineno, R, R_int/snap) with open(ldr_path, 'r', encoding='utf-8', errors='ignore') as f: lines = f.readlines() if len(lines)>310: print(f"Skipping {ldr_path}: too many lines ({len(lines)}).") return for ln, line in enumerate(lines, start=1): line_strip = line.strip() if not line_strip.startswith('1 '): continue parts = line_strip.split() if len(parts) < 15: continue total += 1 R = parse_rotation_from_parts(parts) ok, idx, R_int = match_in_24(R, tol=tol) if ok: matched += 1 # if verbose: # print(f"[OK] line {ln}: matched rot index={idx} matrix=\n{R_int}") else: not_matched += 1 mismatches.append((ln, R, R_int)) if verbose: print(f"[NO] line {ln}: not in 24 rotations. snapped=\n{R_int}\norig=\n{R}") print("\n===== SUMMARY =====") print(f"file: {ldr_path}") print(f"total part-lines: {total}") print(f"matched in 24: {matched}") print(f"not matched: {not_matched}") if not_matched and verbose: print("\nExamples of not-matched (up to 5):") for ln, R, R_int in mismatches[:5]: print(f"- line {ln}: snapped=\n{R_int}\n orig=\n{R}\n") return { "total": total, "matched": matched, "not_matched": not_matched, "mismatches": mismatches, } if __name__ == "__main__": folder_path = '/public/home/wangshuo/gap/assembly/data' # 替换为你的文件夹路径 for root, dirs, files in os.walk(folder_path): for file in files: if file.endswith('.ldr') and file.startswith('modified'): # 只处理ldr文件 file_path = os.path.join(root, file) # 用法示例:替换为你的 LDR 文件路径 #ldr_file = "/public/home/wangshuo/gap/assembly/data/blue classic car/modified_blue classic car.ldr" check_ldr_file(file_path, tol=1e-3, verbose=True) # import numpy as np # from itertools import product, permutations # def generate_24_rotations(): # mats = [] # for p in permutations(range(3)): # P = np.zeros((3,3), dtype=int) # for i,j in enumerate(p): P[i,j] = 1 # for s in product([-1,1], repeat=3): # S = np.diag(s) # R = S @ P # if round(np.linalg.det(R)) == 1: # mats.append(R.astype(float)) # # 去重 # uniq = [] # for R in mats: # if not any(np.array_equal(R, U) for U in uniq): # uniq.append(R) # assert len(uniq) == 24 # return uniq # ROT24 = generate_24_rotations() # def rotation_distance_deg(R1, R2): # """ # 计算 R2 到 R1 的相对旋转角度(度数)。 # Δ = R2^T R1; angle = arccos((trace(Δ)-1)/2) # 数值上做夹取,避免浮点误差。 # """ # Delta = R2.T @ R1 # c = (np.trace(Delta) - 1.0) / 2.0 # c = np.clip(c, -1.0, 1.0) # return float(np.degrees(np.arccos(c))) # def nearest_in_24(R): # best_idx, best_R, best_deg = None, None, 1e9 # for i, Q in enumerate(ROT24): # deg = rotation_distance_deg(R, Q) # if deg < best_deg: # best_idx, best_R, best_deg = i, Q, deg # return best_idx, best_R, best_deg # # —— 示例:对你给出的矩阵做最近邻 —— # R1 = np.array([ # [ 0. , 0. , -1. ], # [-0.707107, -0.707107, 0. ], # [-0.707107, 0.707106, 0. ], # ], dtype=float) # idx, Q, deg = nearest_in_24(R1) # print("nearest idx:", idx) # print("nearest R:\n", Q) # print("angle error (deg):", deg)