Добавить имитаторы МЛЭ и ГБО с 3D-сценой, съёмкой рельефа и пресетами.
Вкладки позволяют готовить рельеф, двигать АНПА, накапливать поверхность по лучам и сохранять скриншоты окон; ГБО использует бортовые секторы 12–75° и чёрные зоны вне обзора. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -649,13 +649,87 @@ def export_ply(points: list[list[float]]) -> str:
|
||||
return header + body + ("\n" if points else "")
|
||||
|
||||
|
||||
def export_obj(points: list[list[float]], object_name: str = "cloud") -> str:
|
||||
def export_obj(
|
||||
points: list[list[float]],
|
||||
object_name: str = "cloud",
|
||||
classes: list[int | float] | None = None,
|
||||
) -> str:
|
||||
safe_name = "".join(ch if ch.isalnum() or ch in "_-" else "_" for ch in (object_name or "cloud")) or "cloud"
|
||||
lines = [f"# DotsToSurface point cloud ({len(points)} vertices)", f"o {safe_name}"]
|
||||
for p in points:
|
||||
lines.append(f"v {p[0]:.8f} {p[1]:.8f} {p[2]:.8f}")
|
||||
if classes is None:
|
||||
lines = [f"# DotsToSurface point cloud ({len(points)} vertices)", f"o {safe_name}"]
|
||||
for p in points:
|
||||
lines.append(f"v {p[0]:.8f} {p[1]:.8f} {p[2]:.8f}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
if len(classes) != len(points):
|
||||
raise ValueError("classes length must match points length")
|
||||
|
||||
class_names = {0: "background", 1: "object"}
|
||||
grouped: dict[int, list[list[float]]] = {}
|
||||
for point, cls in zip(points, classes):
|
||||
grouped.setdefault(int(cls), []).append(point)
|
||||
|
||||
lines = [
|
||||
f"# DotsToSurface labeled point cloud ({len(points)} vertices)",
|
||||
"# Classes: background=0, object=1",
|
||||
f"o {safe_name}",
|
||||
]
|
||||
for cls_id in sorted(grouped.keys()):
|
||||
group_name = class_names.get(cls_id, f"class_{cls_id}")
|
||||
lines.append(f"o {group_name}")
|
||||
lines.append(f"# class {cls_id}")
|
||||
for p in grouped[cls_id]:
|
||||
lines.append(f"v {p[0]:.8f} {p[1]:.8f} {p[2]:.8f}")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _class_from_obj_group(name: str) -> float | None:
|
||||
key = (name or "").strip().lower()
|
||||
if key == "background":
|
||||
return 0.0
|
||||
if key == "object":
|
||||
return 1.0
|
||||
if key.startswith("class_"):
|
||||
try:
|
||||
return float(key.split("_", 1)[1])
|
||||
except (IndexError, ValueError):
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def parse_obj_labeled_points(text: str) -> list[list[float]]:
|
||||
"""Extract [x, y, z, class] from OBJ with class groups or ``# class N`` markers."""
|
||||
labeled: list[list[float]] = []
|
||||
current_class = 0.0
|
||||
for raw in text.splitlines():
|
||||
line = raw.strip()
|
||||
if not line:
|
||||
continue
|
||||
lower = line.lower()
|
||||
if lower.startswith("# class "):
|
||||
try:
|
||||
current_class = float(line.split()[-1])
|
||||
except ValueError:
|
||||
pass
|
||||
continue
|
||||
if lower.startswith("o "):
|
||||
cls = _class_from_obj_group(line[2:])
|
||||
if cls is not None:
|
||||
current_class = cls
|
||||
continue
|
||||
if lower.startswith("v "):
|
||||
parts = line.split()
|
||||
if len(parts) < 4:
|
||||
continue
|
||||
try:
|
||||
labeled.append(
|
||||
[float(parts[1]), float(parts[2]), float(parts[3]), current_class]
|
||||
)
|
||||
except ValueError:
|
||||
continue
|
||||
return labeled
|
||||
|
||||
|
||||
def parse_obj_points(text: str) -> list[list[float]]:
|
||||
"""Extract vertex positions from Wavefront OBJ (ignores faces/materials)."""
|
||||
points: list[list[float]] = []
|
||||
|
||||
Reference in New Issue
Block a user