diff --git a/README.md b/README.md
index 421cd3c..13118e8 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,21 @@
-# Веб-сервис для отладки Robossembler Framework
+# Веб-сервис Robossembler
-Необходимость разработки сервиса хранения и просмотра пакетов обусловлена тем, что для корректной работы фреймворка «Робосборщик» необходима согласованная работа разнообразных программный модулей – результаты работы одних модулей должны передаваться через стандартизированные интерфейсы другим модулям. Как правило, результатами работы программных модулей являются исполняемые файлы программ, файлы 3D-моделей в форматах STL, FBX, Collada/DAE, OBJ, PLY и т.п., конфигурационные файлы в форматах yaml, json, ini, txt, веса нейронных сетей, описания роботов/сцен в форматах URDF, SDF, MJCF и т.д.. При этом необходимо соблюсти условие соответствия данных файлов/документов друг другу, иметь возможность формировать и отслеживать цепочки вычислений (конвейер, pipeline), которые их порождают.
+Сервис для сопровождении процесса/жизненного цикла разработки программ сборки изделий роботами и интеграции программных модулей [Фреймворка Робосборщик](https://gitlab.com/robossembler/framework).
-Данный веб-сервис выполняет следующие функции:
+## Мотивация
-- Создание процессов (process) – команд, запускающих определённые вычисления
-- Создание триггеров (trigger) – событий, запускающихся по завершении процесса
-- Создание конвейеров вычислений (pipeline) – цепочек из процессов
-- Создание проектов (project) – набора конвейеров для выполнения прикладных задач
-- Хранение и просмотр артефактов, порождаемых процессами, а также отслеживание их жизненного цикла
-- Запуск процессов/конвейеров и отслеживание их состояния
+Для корректной работы фреймворка необходима согласованная работа разнообразных программный модулей – результаты работы одних модулей должны передаваться через стандартизированные интерфейсы другим модулям. Результатами работы программных модулей являются исполняемые файлы программ, файлы 3D-моделей в форматах STL, FBX, Collada/DAE, OBJ, PLY и т.п., конфигурационные файлы в форматах yaml, json, ini, txt, веса нейронных сетей, описания роботов/сцен в форматах URDF, SDF, MJCF и т.д.
+
+## Состав модулей сервиса
+
+Каждая фаза жизненного цикла имеет своё представление в виде страницы в веб-сервисе:
+
+1. Создание проекта сборки, загрузка CAD-проекта изделия - "Проекты", вкладки "Детали", "Сборки"
+2. Подготовка и генерация датасета для навыков машинного зрения - Вкладка "Датасеты"
+3. Конфигурация сцены - Scene Builder - Вкладка "Сцена"
+4. Создание дерева поведения из навыков - Вкладка "Поведение"
+5. Просмотр результатов симуляции - Вкладка "Симуляция"
+6. Оценка производительности навыков Вкладка "Анализ"
Веб-сервис написан на языке TypeScript для среды исполнения NodeJS. Для хранения артефактов используется база данных MongoDB. Исходный код проекта разработан в соответствии с концепцией «Чистой архитектуры», описанной Робертом Мартином в одноимённой книге. Данный подход позволяет систематизировать код, отделить бизнес-логику от остальной части приложения.
@@ -19,11 +25,35 @@
- Node.js
- MongoDB
+- BlenderProc (для генерации датасетов)
-## Сборка UI
+## Клонирование проекта
-- `cd ui && npm i && npm run build && npm run deploy`
+```bash
+git clone https://gitlab.com/robossembler/webservice
+```
-# Запуск сервиса
+## Настройка переменных окружения
-- `cd server && npm run dev`
+Для работы Генератора Датасетов нужно задать следующие переменные в окружении `bash`
+
+```bash
+export PYTHON_BLENDER="путь_к_директории_с_файлами_из_rcg_pipeline"
+export PYTHON_BLENDER_PROC="путь_к_генератору_датасетов_renderBOPdataset.py"
+```
+
+## Запуск сервера
+
+Из директории `server` в корне репозитория
+
+```bash
+npm run dev
+```
+
+## Сборка и запуск UI
+
+Из директории `ui` в корне репозитория
+
+```bash
+npm i && npm run build && npm run deploy
+```
diff --git a/server/src/core/controllers/routes.ts b/server/src/core/controllers/routes.ts
index fb869b5..1710aae 100644
--- a/server/src/core/controllers/routes.ts
+++ b/server/src/core/controllers/routes.ts
@@ -1,7 +1,6 @@
import { BehaviorTreesPresentation } from "../../features/behavior_trees/behavior_trees";
import { DatasetsPresentation } from "../../features/datasets/datasets_presentation";
import { ProjectsPresentation } from "../../features/projects/projects_presentation";
-// import { ProjectsPresentation } from "../../features/_projects/projects_presentation";
import { extensions } from "../extensions/extensions";
import { Routes } from "../interfaces/router";
diff --git a/ui/src/core/ui/button/button.tsx b/ui/src/core/ui/button/button.tsx
index bf52d57..3db5865 100644
--- a/ui/src/core/ui/button/button.tsx
+++ b/ui/src/core/ui/button/button.tsx
@@ -6,22 +6,25 @@ export interface IButtonProps {
filled?: boolean;
text?: string;
onClick?: any;
- style?:React.CSSProperties
+ style?: React.CSSProperties;
}
export function CoreButton(props: IButtonProps) {
return (
props.onClick?.call()}
- style={Object.assign({
- backgroundColor: props.filled ? "rgba(103, 80, 164, 1)" : "",
- paddingRight: 20,
- paddingLeft: 20,
- paddingTop: 10,
- paddingBottom: 10,
- borderRadius: 24,
- border: props.block ? "1px solid rgba(29, 27, 32, 0.12)" : props.filled ? "" : "1px solid black",
- },props.style)}
+ style={Object.assign(
+ {
+ backgroundColor: props.filled ? "rgba(103, 80, 164, 1)" : "",
+ paddingRight: 20,
+ paddingLeft: 20,
+ paddingTop: 10,
+ paddingBottom: 10,
+ borderRadius: 24,
+ border: props.block ? "1px solid rgba(29, 27, 32, 0.12)" : props.filled ? "" : "1px solid black",
+ },
+ props.style
+ )}
>
);
}
-
-
-
\ No newline at end of file
diff --git a/ui/src/features/behavior_tree_builder/model/editor_view.ts b/ui/src/features/behavior_tree_builder/model/editor_view.ts
index 360722b..213b0b5 100644
--- a/ui/src/features/behavior_tree_builder/model/editor_view.ts
+++ b/ui/src/features/behavior_tree_builder/model/editor_view.ts
@@ -61,7 +61,6 @@ export class BtBuilderModel {
}
public static getBtPriorities(ids: string[]) {}
-
public static findSequence(
nodeId: string,
editor: NodeEditor,
diff --git a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx
index 4cee1da..46e06c4 100644
--- a/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx
+++ b/ui/src/features/behavior_tree_builder/presentation/behavior_tree_builder_screen.tsx
@@ -1,5 +1,4 @@
import * as React from "react";
-
import { useRete } from "rete-react-plugin";
import { createEditor } from "./ui/editor/editor";
import { SkillTree } from "./ui/skill_tree/skill_tree";
diff --git a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx
index 0d704cf..65df94b 100644
--- a/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx
+++ b/ui/src/features/behavior_tree_builder/presentation/ui/editor/editor.tsx
@@ -34,7 +34,6 @@ export async function createEditor(container: HTMLElement) {
});
observer.on(() => {
- console.log(200)
behaviorTreeBuilderStore.bt(editor, areaContainer);
});
diff --git a/web_p/blender.py b/web_p/blender.py
new file mode 100644
index 0000000..923555d
--- /dev/null
+++ b/web_p/blender.py
@@ -0,0 +1,20 @@
+import shutil
+import argparse
+import os.path
+
+parser = argparse.ArgumentParser()
+parser.add_argument("--path")
+args = parser.parse_args()
+
+def copy_and_move_folder(src, dst):
+ try:
+ if os.path.exists(dst):
+ shutil.rmtree(dst)
+ shutil.copytree(src, dst)
+ print(f"Folder {src} successfully copied")
+ except shutil.Error as e:
+ print(f"Error: {e}")
+
+source_folder = os.path.dirname(os.path.abspath(__file__)) + "/blender" #"/home/shalenikol/web_p/blender/"
+
+copy_and_move_folder(source_folder, args.path)
\ No newline at end of file
diff --git a/web_p/blender/assets/assets.json b/web_p/blender/assets/assets.json
new file mode 100644
index 0000000..f661cbb
--- /dev/null
+++ b/web_p/blender/assets/assets.json
@@ -0,0 +1,6 @@
+{
+ "assets": [
+ { "name": "bear_holder", "mesh": "./mesh/fork.stl", "image": "./images/bear_holder_220425.png" },
+ { "name": "bear_holder1", "mesh": "./mesh/fork.stl", "image": "./images/bear_holder_220425.png" }
+ ]
+}
diff --git a/web_p/blender/assets/images/bear_holder_220425.png b/web_p/blender/assets/images/bear_holder_220425.png
new file mode 100644
index 0000000..a366b36
Binary files /dev/null and b/web_p/blender/assets/images/bear_holder_220425.png differ
diff --git a/web_p/blender/assets/mesh/floor.fbx b/web_p/blender/assets/mesh/floor.fbx
new file mode 100644
index 0000000..0c904fd
Binary files /dev/null and b/web_p/blender/assets/mesh/floor.fbx differ
diff --git a/web_p/blender/assets/mesh/fork.fbx b/web_p/blender/assets/mesh/fork.fbx
new file mode 100644
index 0000000..891e3a2
Binary files /dev/null and b/web_p/blender/assets/mesh/fork.fbx differ
diff --git a/web_p/blender/assets/mesh/fork.obj b/web_p/blender/assets/mesh/fork.obj
new file mode 100644
index 0000000..e69de29
diff --git a/web_p/blender/assets/mesh/fork.stl b/web_p/blender/assets/mesh/fork.stl
new file mode 100644
index 0000000..e69de29
diff --git a/web_p/impassable object.FCStd b/web_p/impassable object.FCStd
new file mode 100644
index 0000000..d2a87a5
Binary files /dev/null and b/web_p/impassable object.FCStd differ
diff --git a/web_p/renderBOPdataset.py b/web_p/renderBOPdataset.py
new file mode 100755
index 0000000..f3d8b15
--- /dev/null
+++ b/web_p/renderBOPdataset.py
@@ -0,0 +1,370 @@
+import blenderproc as bproc
+"""
+ renderBOPdataset
+ Общая задача: common pipeline
+ Реализуемая функция: создание датасета в формате BOP с заданными параметрами рандомизации
+ Используется модуль blenderproc
+
+ 19.04.2024 @shalenikol release 0.1
+"""
+import numpy as np
+import argparse
+import random
+import os
+import shutil
+import json
+
+VHACD_PATH = "blenderproc_resources/vhacd"
+# DIR_BOP = "bop_data"
+DIR_MODELS = "models"
+FILE_LOG_SCENE = "res.txt"
+FILE_RBS_INFO = "rbs_info.json"
+FILE_GT_COCO = "scene_gt_coco.json"
+
+Not_Categories_Name = True # наименование категории в COCO-аннотации отсутствует
+
+def _get_path_model(name_model: str) -> str:
+ # TODO on name_model find path for mesh (model.fbx)
+ # local_path/assets/mesh/
+ return os.path.join(rnd_par.output_dir, "assets/mesh/"+name_model+".fbx")
+ # , d: dict
+ # return d["model"]
+
+def _get_path_object(name_obj: str) -> str:
+ # TODO on name_obj find path for scene object (object.fbx)
+ return os.path.join(rnd_par.output_dir, "assets/mesh/"+name_obj+".fbx")
+ # , d: dict
+ # return d["path"]
+
+def convert2relative(height, width, bbox):
+ """
+ YOLO format use relative coordinates for annotation
+ """
+ x, y, w, h = bbox
+ x += w/2
+ y += h/2
+ return x/width, y/height, w/width, h/height
+
+def render() -> int:
+ for obj in all_meshs:
+ # Make the object actively participate in the physics simulation
+ obj.enable_rigidbody(active=True, collision_shape="COMPOUND")
+ # Also use convex decomposition as collision shapes
+ obj.build_convex_decomposition_collision_shape(VHACD_PATH)
+
+ objs = all_meshs + rnd_par.scene.objs
+
+ log_txt = os.path.join(rnd_par.output_dir, FILE_LOG_SCENE)
+ with open(log_txt, "w") as fh:
+ for i,o in enumerate(objs):
+ loc = o.get_location()
+ euler = o.get_rotation_euler()
+ fh.write(f"{i} : {o.get_name()} {loc} {euler} category_id = {o.get_cp('category_id')}\n")
+
+ # define a light and set its location and energy level
+ ls = []
+ for l in rnd_par.scene.light_data:
+ light = bproc.types.Light(name=f"l{l['id']}")
+ light.set_type(l["type"])
+ light.set_location(l["loc_xyz"]) #[5, -5, 5])
+ light.set_rotation_euler(l["rot_euler"]) #[-0.063, 0.6177, -0.1985])
+ ls += [light]
+
+ # define the camera intrinsics
+ bproc.camera.set_intrinsics_from_blender_params(1,
+ rnd_par.image_size_wh[0],
+ rnd_par.image_size_wh[1],
+ lens_unit="FOV")
+
+ # add segmentation masks (per class and per instance)
+ bproc.renderer.enable_segmentation_output(map_by=["category_id", "instance", "name"])
+
+ # activate depth rendering
+ bproc.renderer.enable_depth_output(activate_antialiasing=False)
+
+ res_dir = os.path.join(rnd_par.output_dir, rnd_par.ds_name)
+ if os.path.isdir(res_dir):
+ shutil.rmtree(res_dir)
+ # Цикл рендеринга
+ # Do multiple times: Position the shapenet objects using the physics simulator and render X images with random camera poses
+ for r in range(rnd_par.n_series):
+ # один случайный объект в кадре / все заданные объекты
+ random_obj = random.choice(range(rnd_par.scene.n_obj))
+ meshs = []
+ for i,o in enumerate(all_meshs): #objs
+ if rnd_par.single_object and i != random_obj:
+ continue
+ meshs += [o]
+ rnd_mat = rnd_par.scene.obj_data[i]["material_randomization"]
+ mats = o.get_materials() #[0]
+ for mat in mats:
+ val = rnd_mat["specular"]
+ mat.set_principled_shader_value("Specular", random.uniform(val[0], val[1]))
+ val = rnd_mat["roughness"]
+ mat.set_principled_shader_value("Roughness", random.uniform(val[0], val[1]))
+ val = rnd_mat["base_color"]
+ mat.set_principled_shader_value("Base Color", np.random.uniform(val[0], val[1]))
+ val = rnd_mat["metallic"]
+ mat.set_principled_shader_value("Metallic", random.uniform(val[0], val[1]))
+
+ # Randomly set the color and energy
+ for i,l in enumerate(ls):
+ current = rnd_par.scene.light_data[i]
+ l.set_color(np.random.uniform(current["color_range_low"], current["color_range_high"]))
+ energy = current["energy_range"]
+ l.set_energy(random.uniform(energy[0], energy[1]))
+
+ # Clear all key frames from the previous run
+ bproc.utility.reset_keyframes()
+
+ # Define a function that samples 6-DoF poses
+ def sample_pose(obj: bproc.types.MeshObject):
+ obj.set_location(np.random.uniform(rnd_par.loc_range_low, rnd_par.loc_range_high)) #[-1, -1, 0], [1, 1, 2]))
+ obj.set_rotation_euler(bproc.sampler.uniformSO3())
+
+ # Sample the poses of all shapenet objects above the ground without any collisions in-between
+ bproc.object.sample_poses(meshs,
+ objects_to_check_collisions = meshs + rnd_par.scene.collision_objects,
+ sample_pose_func = sample_pose)
+
+ # Run the simulation and fix the poses of the shapenet objects at the end
+ bproc.object.simulate_physics_and_fix_final_poses(min_simulation_time=4, max_simulation_time=20, check_object_interval=1)
+
+ # Find point of interest, all cam poses should look towards it
+ poi = bproc.object.compute_poi(meshs)
+
+ coord_max = [0.1, 0.1, 0.1]
+ coord_min = [0., 0., 0.]
+
+ with open(log_txt, "a") as fh:
+ fh.write("*****************\n")
+ fh.write(f"{r}) poi = {poi}\n")
+ i = 0
+ for o in meshs:
+ i += 1
+ loc = o.get_location()
+ euler = o.get_rotation_euler()
+ fh.write(f" {i} : {o.get_name()} {loc} {euler}\n")
+ for j in range(3):
+ if loc[j] < coord_min[j]:
+ coord_min[j] = loc[j]
+ if loc[j] > coord_max[j]:
+ coord_max[j] = loc[j]
+
+ # Sample up to X camera poses
+ #an = np.random.uniform(0.78, 1.2) #1. #0.35
+ for i in range(rnd_par.n_cam_pose):
+ # Sample location
+ location = bproc.sampler.shell(center=rnd_par.center_shell,
+ radius_min=rnd_par.radius_range[0],
+ radius_max=rnd_par.radius_range[1],
+ elevation_min=rnd_par.elevation_range[0],
+ elevation_max=rnd_par.elevation_range[1])
+ # координата, по которой будем сэмплировать положение камеры
+ j = random.randint(0, 2)
+ # разовый сдвиг по случайной координате
+ d = (coord_max[j] - coord_min[j]) / rnd_par.n_sample_on_pose
+ if location[j] < 0:
+ d = -d
+ for _ in range(rnd_par.n_sample_on_pose):
+ # Compute rotation based on vector going from location towards poi
+ rotation_matrix = bproc.camera.rotation_from_forward_vec(poi - location, inplane_rot=np.random.uniform(-0.7854, 0.7854))
+ # Add homog cam pose based on location an rotation
+ cam2world_matrix = bproc.math.build_transformation_mat(location, rotation_matrix)
+ bproc.camera.add_camera_pose(cam2world_matrix)
+ location[j] -= d
+ # render the whole pipeline
+ data = bproc.renderer.render()
+ # Write data to bop format
+ bproc.writer.write_bop(res_dir,
+ target_objects = all_meshs, # Optional[List[MeshObject]] = None
+ depths = data["depth"],
+ depth_scale = 1.0,
+ colors = data["colors"],
+ color_file_format=rnd_par.image_format,
+ append_to_existing_output = (r>0),
+ save_world2cam = False) # world coords are arbitrary in most real BOP datasets
+ # dataset="robo_ds",
+
+ models_dir = os.path.join(res_dir, DIR_MODELS)
+ os.mkdir(models_dir)
+
+ data = []
+ for i,objn in enumerate(rnd_par.models.names):
+ rec = {}
+ rec["id"] = i+1
+ rec["name"] = objn
+ rec["model"] = os.path.join(DIR_MODELS, os.path.split(rnd_par.models.filenames[i])[1]) # путь относительный
+ t = [obj.get_bound_box(local_coords=True).tolist() for obj in all_meshs if obj.get_name() == objn]
+ rec["cuboid"] = t[0]
+ data.append(rec)
+ # ff = os.path.join(args.obj_path, rnd_par.models.filenames[i]) # путь к исходному файлу
+ # shutil.copy2(ff, models_dir)
+ shutil.copy2(rnd_par.models.filenames[i], models_dir)
+ f = (os.path.splitext(rnd_par.models.filenames[i]))[0] + ".mtl" # файл материала
+ if os.path.isfile(f):
+ shutil.copy2(f, models_dir)
+
+ with open(os.path.join(res_dir, FILE_RBS_INFO), "w") as fh:
+ json.dump(data, fh, indent=2)
+
+ """
+ !!! categories -> name берётся из category_id !!!
+ см.ниже
+ blenderproc.python.writer : BopWriterUtility.py
+ class _BopWriterUtility
+ def calc_gt_coco
+ ...
+ CATEGORIES = [{'id': obj.get_cp('category_id'), 'name': str(obj.get_cp('category_id')), 'supercategory':
+ dataset_name} for obj in dataset_objects]
+
+ поэтому заменим наименование категории в аннотации
+ """
+ def change_categories_name(dir: str):
+ coco_file = os.path.join(dir,FILE_GT_COCO)
+ with open(coco_file, "r") as fh:
+ data = json.load(fh)
+ cats = data["categories"]
+
+ for i,cat in enumerate(cats):
+ cat["name"] = rnd_par.models.names[i] #obj_names[i]
+
+ with open(coco_file, "w") as fh:
+ json.dump(data, fh, indent=0)
+
+ def explore(path: str):
+ if not os.path.isdir(path):
+ return
+ folders = [
+ os.path.join(path, o)
+ for o in os.listdir(path)
+ if os.path.isdir(os.path.join(path, o))
+ ]
+ for path_entry in folders:
+ print(path_entry)
+ if os.path.isfile(os.path.join(path_entry,FILE_GT_COCO)):
+ change_categories_name(path_entry)
+ else:
+ explore(path_entry)
+
+ if Not_Categories_Name:
+ explore(res_dir)
+ return 0 # success
+
+def _get_models(par, data) -> int:
+ global all_meshs
+
+ par.models = lambda: None
+ par.models.n_item = len(data)
+ if par.models.n_item == 0:
+ return 0 # no models
+
+ # загрузим объекты
+ par.models.names = [] #list(map(lambda x: x["name"], data)) # obj_names
+ par.models.filenames = [] #list(map(lambda x: x["model"], data)) #obj_filenames
+ i = 1
+ for f in data:
+ nam = f
+ par.models.names.append(nam)
+ ff = _get_path_model(nam)
+ # ff = f["model"] # путь к файлу объекта
+ par.models.filenames.append(ff)
+ if not os.path.isfile(ff):
+ print(f"Error: no such file '{ff}'")
+ return -1
+ obj = bproc.loader.load_obj(ff)
+ all_meshs += obj
+ obj[0].set_cp("category_id", i) #f["id"]) # начиная с 1
+ i += 1
+ return par.models.n_item
+
+def _get_scene(par, data) -> int:
+ # load scene
+ par.scene = lambda: None
+ objs = data["objects"]
+ par.scene.n_obj = len(objs)
+ if par.scene.n_obj == 0:
+ return 0 # empty scene
+ lights = data["lights"]
+ par.scene.n_light = len(lights)
+ if par.scene.n_light == 0:
+ return 0 # no lighting
+
+ par.scene.objs = []
+ par.scene.collision_objects = []
+ for f in objs:
+ ff = _get_path_object(f["name"]) # f["path"]
+ if not os.path.isfile(ff):
+ print(f"Error: no such file '{ff}'")
+ return -1
+ obj = bproc.loader.load_obj(ff)
+ obj[0].set_cp("category_id", 999)
+ coll = f["collision_shape"]
+ if len(coll) > 0:
+ obj[0].enable_rigidbody(False, collision_shape=coll)
+ par.scene.collision_objects += obj
+ par.scene.objs += obj #bproc.loader.load_blend(args.scene, data_blocks=["objects"])
+
+ if not par.scene.collision_objects:
+ print("Collision objects not found in the scene")
+ return 0
+ par.scene.obj_data = objs
+ par.scene.light_data = lights
+ return par.scene.n_obj
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--cfg", required=True, help="Json-string with dataset parameters")
+ args = parser.parse_args()
+
+ if args.cfg[-5:] == ".json":
+ if not os.path.isfile(args.cfg):
+ print(f"Error: no such file '{args.cfg}'")
+ exit(-1)
+ with open(args.cfg, "r") as f:
+ j_data = f.read()
+ else:
+ j_data = args.cfg
+ try:
+ cfg = json.loads(j_data)
+ except json.JSONDecodeError as e:
+ print(f"JSon error: {e}")
+ exit(-2)
+
+ ds_cfg = cfg["formBuilder"]["output"] # dataset config
+ generation = ds_cfg["generation"]
+ cam_pos = ds_cfg["camera_position"]
+ models_randomization = ds_cfg["models_randomization"]
+
+ rnd_par = lambda: None
+ rnd_par.single_object = True
+ rnd_par.ds_name = cfg["name"]
+ rnd_par.output_dir = cfg["local_path"]
+ rnd_par.dataset_objs = cfg["dataSetObjects"]
+ rnd_par.n_cam_pose = generation["n_cam_pose"]
+ rnd_par.n_sample_on_pose = generation["n_sample_on_pose"]
+ rnd_par.n_series = generation["n_series"]
+ rnd_par.image_format = generation["image_format"]
+ rnd_par.image_size_wh = generation["image_size_wh"]
+ rnd_par.center_shell = cam_pos["center_shell"]
+ rnd_par.radius_range = cam_pos["radius_range"]
+ rnd_par.elevation_range = cam_pos["elevation_range"]
+ rnd_par.loc_range_low = models_randomization["loc_range_low"]
+ rnd_par.loc_range_high = models_randomization["loc_range_high"]
+
+ if not os.path.isdir(rnd_par.output_dir):
+ # os.mkdir(rnd_par.output_dir)
+ print(f"Error: invalid path '{rnd_par.output_dir}'")
+ exit(-3)
+
+ bproc.init()
+
+ all_meshs = []
+ ret = _get_models(rnd_par, rnd_par.dataset_objs)
+ if ret <= 0:
+ print("Error: no models in config")
+ exit(-4)
+ if _get_scene(rnd_par, ds_cfg["scene"]) == 0:
+ print("Error: empty scene in config")
+ exit(-5)
+ exit(render())
\ No newline at end of file