From 8fa9076f9104b07ec3c9f70dd25efd866f343b4e Mon Sep 17 00:00:00 2001 From: shalenikol Date: Fri, 10 May 2024 08:36:51 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9C=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BD=D0=B5=D0=B9=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B5=D1=82=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B0?= =?UTF-8?q?=D0=B2=D1=8B=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simulation/train_models/models_dope.py | 196 +++++ simulation/train_models/rbs_train.py | 29 + simulation/train_models/train_Dope.py | 542 ++++++++++++++ simulation/train_models/train_Yolo.py | 181 +++++ simulation/train_models/utils_dope.py | 967 +++++++++++++++++++++++++ 5 files changed, 1915 insertions(+) create mode 100755 simulation/train_models/models_dope.py create mode 100644 simulation/train_models/rbs_train.py create mode 100644 simulation/train_models/train_Dope.py create mode 100644 simulation/train_models/train_Yolo.py create mode 100755 simulation/train_models/utils_dope.py diff --git a/simulation/train_models/models_dope.py b/simulation/train_models/models_dope.py new file mode 100755 index 0000000..0c89004 --- /dev/null +++ b/simulation/train_models/models_dope.py @@ -0,0 +1,196 @@ +""" +NVIDIA from jtremblay@gmail.com +""" + +# Networks +import torch +import torch +import torch.nn as nn +import torch.nn.parallel +import torch.utils.data +import torchvision.models as models + + +class DopeNetwork(nn.Module): + def __init__( + self, + pretrained=False, + numBeliefMap=9, + numAffinity=16, + stop_at_stage=6, # number of stages to process (if less than total number of stages) + ): + super(DopeNetwork, self).__init__() + + self.stop_at_stage = stop_at_stage + + vgg_full = models.vgg19(pretrained=False).features + self.vgg = nn.Sequential() + for i_layer in range(24): + self.vgg.add_module(str(i_layer), vgg_full[i_layer]) + + # Add some layers + i_layer = 23 + self.vgg.add_module( + str(i_layer), nn.Conv2d(512, 256, kernel_size=3, stride=1, padding=1) + ) + self.vgg.add_module(str(i_layer + 1), nn.ReLU(inplace=True)) + self.vgg.add_module( + str(i_layer + 2), nn.Conv2d(256, 128, kernel_size=3, stride=1, padding=1) + ) + self.vgg.add_module(str(i_layer + 3), nn.ReLU(inplace=True)) + + # print('---Belief------------------------------------------------') + # _2 are the belief map stages + self.m1_2 = DopeNetwork.create_stage(128, numBeliefMap, True) + self.m2_2 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numBeliefMap, False + ) + self.m3_2 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numBeliefMap, False + ) + self.m4_2 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numBeliefMap, False + ) + self.m5_2 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numBeliefMap, False + ) + self.m6_2 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numBeliefMap, False + ) + + # print('---Affinity----------------------------------------------') + # _1 are the affinity map stages + self.m1_1 = DopeNetwork.create_stage(128, numAffinity, True) + self.m2_1 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numAffinity, False + ) + self.m3_1 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numAffinity, False + ) + self.m4_1 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numAffinity, False + ) + self.m5_1 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numAffinity, False + ) + self.m6_1 = DopeNetwork.create_stage( + 128 + numBeliefMap + numAffinity, numAffinity, False + ) + + def forward(self, x): + """Runs inference on the neural network""" + + out1 = self.vgg(x) + + out1_2 = self.m1_2(out1) + out1_1 = self.m1_1(out1) + + if self.stop_at_stage == 1: + return [out1_2], [out1_1] + + out2 = torch.cat([out1_2, out1_1, out1], 1) + out2_2 = self.m2_2(out2) + out2_1 = self.m2_1(out2) + + if self.stop_at_stage == 2: + return [out1_2, out2_2], [out1_1, out2_1] + + out3 = torch.cat([out2_2, out2_1, out1], 1) + out3_2 = self.m3_2(out3) + out3_1 = self.m3_1(out3) + + if self.stop_at_stage == 3: + return [out1_2, out2_2, out3_2], [out1_1, out2_1, out3_1] + + out4 = torch.cat([out3_2, out3_1, out1], 1) + out4_2 = self.m4_2(out4) + out4_1 = self.m4_1(out4) + + if self.stop_at_stage == 4: + return [out1_2, out2_2, out3_2, out4_2], [out1_1, out2_1, out3_1, out4_1] + + out5 = torch.cat([out4_2, out4_1, out1], 1) + out5_2 = self.m5_2(out5) + out5_1 = self.m5_1(out5) + + if self.stop_at_stage == 5: + return [out1_2, out2_2, out3_2, out4_2, out5_2], [ + out1_1, + out2_1, + out3_1, + out4_1, + out5_1, + ] + + out6 = torch.cat([out5_2, out5_1, out1], 1) + out6_2 = self.m6_2(out6) + out6_1 = self.m6_1(out6) + + return [out1_2, out2_2, out3_2, out4_2, out5_2, out6_2], [ + out1_1, + out2_1, + out3_1, + out4_1, + out5_1, + out6_1, + ] + + @staticmethod + def create_stage(in_channels, out_channels, first=False): + """Create the neural network layers for a single stage.""" + + model = nn.Sequential() + mid_channels = 128 + if first: + padding = 1 + kernel = 3 + count = 6 + final_channels = 512 + else: + padding = 3 + kernel = 7 + count = 10 + final_channels = mid_channels + + # First convolution + model.add_module( + "0", + nn.Conv2d( + in_channels, mid_channels, kernel_size=kernel, stride=1, padding=padding + ), + ) + + # Middle convolutions + i = 1 + while i < count - 1: + model.add_module(str(i), nn.ReLU(inplace=True)) + i += 1 + model.add_module( + str(i), + nn.Conv2d( + mid_channels, + mid_channels, + kernel_size=kernel, + stride=1, + padding=padding, + ), + ) + i += 1 + + # Penultimate convolution + model.add_module(str(i), nn.ReLU(inplace=True)) + i += 1 + model.add_module( + str(i), nn.Conv2d(mid_channels, final_channels, kernel_size=1, stride=1) + ) + i += 1 + + # Last convolution + model.add_module(str(i), nn.ReLU(inplace=True)) + i += 1 + model.add_module( + str(i), nn.Conv2d(final_channels, out_channels, kernel_size=1, stride=1) + ) + i += 1 + + return model diff --git a/simulation/train_models/rbs_train.py b/simulation/train_models/rbs_train.py new file mode 100644 index 0000000..2a78759 --- /dev/null +++ b/simulation/train_models/rbs_train.py @@ -0,0 +1,29 @@ +""" + rbs_train + Общая задача: web-service pipeline + Реализуемая функция: обучение нейросетевой модели по заданному BOP-датасету + + python3 $PYTHON_EDUCATION --path /Users/idontsudo/webservice/server/build/public/7065d6b6-c8a3-48c5-9679-bb8f3a690296 \ + --name test1234 --datasetName 32123213 + + 27.04.2024 @shalenikol release 0.1 +""" +import argparse +from train_Yolo import train_YoloV8 +from train_Dope import train_Dope_i + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True, help="Path for dataset") + parser.add_argument("--name", required=True, help="String with result weights name") + parser.add_argument("--datasetName", required=True, help="String with dataset name") + parser.add_argument("--outpath", default="weights", help="Output path for weights") + parser.add_argument("--type", default="ObjectDetection", help="Type of implementation") + parser.add_argument("--epoch", default=3, type=int, help="How many training epochs") + parser.add_argument('--pretrain', action="store_true", help="Use pretraining") + args = parser.parse_args() + + if args.type == "ObjectDetection": + train_YoloV8(args.path, args.name, args.datasetName, args.outpath, args.epoch, args.pretrain) + else: + train_Dope_i(args.path, args.name, args.datasetName, args.outpath, args.epoch, args.pretrain) diff --git a/simulation/train_models/train_Dope.py b/simulation/train_models/train_Dope.py new file mode 100644 index 0000000..f9908bc --- /dev/null +++ b/simulation/train_models/train_Dope.py @@ -0,0 +1,542 @@ +""" + train_Dope + Общая задача: оценка позиции объекта (Pose estimation) + Реализуемая функция: обучение нейросетевой модели DOPE по заданному BOP-датасету + + python3 $PYTHON_EDUCATION --path /Users/user/webservice/server/build/public/7065d6b6-c8a3-48c5-9679-bb8f3a690296 \ + --name test1234 --datasetName 32123213 + + 08.05.2024 @shalenikol release 0.1 +""" +import os +import json +import shutil +import numpy as np +import transforms3d as t3d + +FILE_RBS_INFO = "rbs_info.json" +FILE_CAMERA = "camera.json" +FILE_GT = "scene_gt.json" +FILE_GT_COCO = "scene_gt_coco.json" +FILE_GT_INFO = "scene_gt_info.json" + +FILE_MODEL = "epoch" +EXT_MODEL = ".pth" +EXT_RGB = "jpg" +DIR_ROOT_DS = "dataset_dope" +DIR_TRAIN_OUT = "out_weights" + +MODEL_SCALE = 1000 # исходная модель в метрах, преобразуем в мм (для DOPE) + +# Own_Numbering_Files = True # наименование image-файлов: собственная нумерация +nn_image = 0 +K_intrinsic = [] +model_info = [] +camera_data = {} +im_width = 0 + +nb_update_network = 0 +# [ +# [min(x), min(y), min(z)], +# [min(x), max(y), min(z)], +# [min(x), max(y), max(z)], +# [min(x), min(y), max(z)], +# [max(x), min(y), max(z)], +# [max(x), max(y), min(z)], +# [max(x), max(y), max(z)], +# [max(x), min(y), max(z)], +# [xc, yc, zc] # min + (max - min) / 2 +# ] + +def trans_3Dto2D_point_in_camera(xyz, K_m, R_m2c, t_m2c): + """ + xyz : 3D-координаты точки + K_m : внутренняя матрица камеры 3х3 + R_m2c : матрица поворота 3х3 + t_m2c : вектор перемещения 3х1 + return [u,v] + """ + K = np.array(K_m) + r = np.array(R_m2c) + r.shape = (3, 3) + t = np.array(t_m2c) + t.shape = (3, 1) + T = np.concatenate((r, t), axis=1) + + P_m = np.array(xyz) + P_m.resize(4) + P_m[-1] = 1.0 + P_m.shape = (4, 1) + + # Project (X, Y, Z, 1) into cameras coordinate system + P_c = T @ P_m # 4x1 + # Apply camera intrinsics to map (Xc, Yc, Zc) to p=(x, y, z) + p = K @ P_c + # Normalize by z to get (u,v,1) + uv = (p / p[2][0])[:-1] + return uv.flatten().tolist() + +def gt_parse(path: str, out_dir: str): + global nn_image + with open(os.path.join(path, FILE_GT_COCO), "r") as fh: + coco_data = json.load(fh) + with open(os.path.join(path, FILE_GT), "r") as fh: + gt_data = json.load(fh) + with open(os.path.join(path, FILE_GT_INFO), "r") as fh: + gt_info = json.load(fh) + + for img in coco_data["images"]: + rgb_file = os.path.join(path, img["file_name"]) + if os.path.isfile(rgb_file): + # if Own_Numbering_Files: + ext = os.path.splitext(rgb_file)[1] # only ext + f = f"{nn_image:06}" + out_img = os.path.join(out_dir, f + ext) + # else: + # f = os.path.split(rgb_file)[1] # filename with extension + # f = os.path.splitext(f)[0] # only filename + # out_img = out_dir + shutil.copy2(rgb_file, out_img) + out_file = os.path.join(out_dir,f+".json") + nn_image += 1 + + # full annotation of the one image + all_data = camera_data.copy() + cat_names = {obj["id"]: obj["name"] for obj in coco_data["categories"]} + id_img = img["id"] # 0, 1, 2 ... + sid_img = str(id_img) # "0", "1", "2" ... + img_info = gt_info[sid_img] + img_gt = gt_data[sid_img] + img_idx = 0 # object index on the image + objs = [] + for ann in coco_data["annotations"]: + if ann["image_id"] == id_img: + item = ann["category_id"] + obj_data = {} + obj_data["class"] = cat_names[item] + x, y, width, height = ann["bbox"] + obj_data["bounding_box"] = {"top_left":[x,y], "bottom_right":[x+width,y+height]} + + # visibility from FILE_GT_INFO + item_info = img_info[img_idx] + obj_data["visibility"] = item_info["visib_fract"] + + # location from FILE_GT + item_gt = img_gt[img_idx] + obj_id = item_gt["obj_id"] - 1 # index with 0 + cam_R_m2c = item_gt["cam_R_m2c"] + cam_t_m2c = item_gt["cam_t_m2c"] + obj_data["location"] = cam_t_m2c + q = t3d.quaternions.mat2quat(np.array(cam_R_m2c)) + obj_data["quaternion_xyzw"] = [q[1], q[2], q[3], q[0]] + + cuboid_xyz = model_info[obj_id] + obj_data["projected_cuboid"] = [ + trans_3Dto2D_point_in_camera(cub, K_intrinsic, cam_R_m2c, cam_t_m2c) + for cub in cuboid_xyz + ] + + objs.append(obj_data) + img_idx += 1 + + all_data["objects"] = objs + with open(out_file, "w") as fh: + json.dump(all_data, fh, indent=2) + +def explore(path: str, res_dir: 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: + if os.path.isfile(os.path.join(path_entry,FILE_GT_COCO)) and \ + os.path.isfile(os.path.join(path_entry,FILE_GT_INFO)) and \ + os.path.isfile(os.path.join(path_entry,FILE_GT)): + gt_parse(path_entry, res_dir) + else: + explore(path_entry, res_dir) + +def BOP2DOPE_dataset(dpath: str, out_dir: str) -> str: + """ Convert BOP-dataset to YOLO format for train """ + res_dir = os.path.join(out_dir, DIR_ROOT_DS) + if os.path.isdir(res_dir): + shutil.rmtree(res_dir) + os.mkdir(res_dir) + + explore(dpath, res_dir) + + return out_dir + +def train(dopepath:str, wname:str, epochs:int, pretrain: bool, lname: list): + import random + # try: + import configparser as configparser + # except ImportError: + # import ConfigParser as configparser + import torch + # import torch.nn.parallel + import torch.optim as optim + import torch.utils.data + import torchvision.transforms as transforms + from torch.autograd import Variable + import datetime + from tensorboardX import SummaryWriter + + from models_dope import DopeNetwork + from utils_dope import CleanVisiiDopeLoader #, VisualizeBeliefMap, save_image + + import warnings + warnings.filterwarnings("ignore") + + os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3,4,5,6,7" + + torch.autograd.set_detect_anomaly(False) + torch.autograd.profiler.profile(False) + torch.autograd.gradcheck = False + torch.backends.cudnn.benchmark = True + + start_time = datetime.datetime.now() + print("start:", start_time.strftime("%m/%d/%Y, %H:%M:%S")) + + res_model = os.path.join(dopepath, wname + EXT_MODEL) + + local_rank = 0 + opt = lambda: None + opt.use_s3 = False + opt.train_buckets = [] + opt.endpoint = None + opt.lr=0.0001 + opt.loginterval=100 + opt.sigma=0.5 # 4 + opt.nbupdates=None + # opt.save=False + # opt.option="default" + # opt.gpuids=[0] + + opt.namefile=FILE_MODEL + opt.workers=8 + opt.batchsize=16 + + opt.data = [os.path.join(dopepath, DIR_ROOT_DS)] + opt.outf = os.path.join(dopepath, DIR_TRAIN_OUT) + opt.object = lname #["fork"] + opt.exts = [EXT_RGB] + # opt.imagesize = im_width + opt.epochs = epochs + opt.pretrained = pretrain + opt.net_path = res_model if pretrain else None + opt.manualseed = random.randint(1, 10000) + + # # Validate Arguments + # if opt.use_s3 and (opt.train_buckets is None or opt.endpoint is None): + # raise ValueError( + # "--train_buckets and --endpoint must be specified if training with data from s3 bucket." + # ) + # if not opt.use_s3 and opt.data is None: + # raise ValueError("--data field must be specified.") + + os.makedirs(opt.outf, exist_ok=True) + + # if local_rank == 0: + # writer = SummaryWriter(opt.outf + "/runs/") + random.seed(opt.manualseed) + torch.cuda.set_device(local_rank) + # torch.distributed.init_process_group(backend="nccl", init_method="env://") + torch.manual_seed(opt.manualseed) + torch.cuda.manual_seed_all(opt.manualseed) + + # # Data Augmentation + # if not opt.save: + # contrast = 0.2 + # brightness = 0.2 + # noise = 0.1 + # normal_imgs = [0.59, 0.25] + # transform = transforms.Compose( + # [ + # AddRandomContrast(0.2), + # AddRandomBrightness(0.2), + # transforms.Resize(opt.imagesize), + # ] + # ) + # else: + # contrast = 0.00001 + # brightness = 0.00001 + # noise = 0.00001 + # normal_imgs = None + # transform = transforms.Compose( + # [transforms.Resize(opt.imagesize), transforms.ToTensor()] + # ) + + # Load Model + net = DopeNetwork() + output_size = 50 + # opt.sigma = 0.5 + + train_dataset = CleanVisiiDopeLoader( + opt.data, + sigma=opt.sigma, + output_size=output_size, + extensions=opt.exts, + objects=opt.object, + use_s3=opt.use_s3, + buckets=opt.train_buckets, + endpoint_url=opt.endpoint, + ) + trainingdata = torch.utils.data.DataLoader( + train_dataset, + batch_size=opt.batchsize, + shuffle=True, + num_workers=opt.workers, + pin_memory=True, + ) + if not trainingdata is None: + print(f"training data: {len(trainingdata)} batches") + + print("Loading Model...") + net = net.cuda() + # net = torch.nn.parallel.DistributedDataParallel( + # net.cuda(), device_ids=[local_rank], output_device=local_rank + # ) + if opt.pretrained: + if opt.net_path is not None: + net.load_state_dict(torch.load(opt.net_path)) + else: + print("Error: Did not specify path to pretrained weights.") + quit() + + parameters = filter(lambda p: p.requires_grad, net.parameters()) + optimizer = optim.Adam(parameters, lr=opt.lr) + + print("ready to train!") + + global nb_update_network + nb_update_network = 0 + # best_results = {"epoch": None, "passed": None, "add_mean": None, "add_std": None} + + scaler = torch.cuda.amp.GradScaler() + + def _runnetwork(epoch, train_loader): #, syn=False + global nb_update_network + # net + net.train() + + loss_avg_to_log = {} + loss_avg_to_log["loss"] = [] + loss_avg_to_log["loss_affinities"] = [] + loss_avg_to_log["loss_belief"] = [] + loss_avg_to_log["loss_class"] = [] + for batch_idx, targets in enumerate(train_loader): + optimizer.zero_grad() + + data = Variable(targets["img"].cuda()) + target_belief = Variable(targets["beliefs"].cuda()) + target_affinities = Variable(targets["affinities"].cuda()) + + output_belief, output_aff = net(data) + + loss = None + + loss_belief = torch.tensor(0).float().cuda() + loss_affinities = torch.tensor(0).float().cuda() + loss_class = torch.tensor(0).float().cuda() + + for stage in range(len(output_aff)): # output, each belief map layers. + loss_affinities += ( + (output_aff[stage] - target_affinities) + * (output_aff[stage] - target_affinities) + ).mean() + + loss_belief += ( + (output_belief[stage] - target_belief) + * (output_belief[stage] - target_belief) + ).mean() + + loss = loss_affinities + loss_belief + + # if batch_idx == 0: + # post = "train" + # if local_rank == 0: + # for i_output in range(1): + # # input images + # writer.add_image( + # f"{post}_input_{i_output}", + # targets["img_original"][i_output], + # epoch, + # dataformats="CWH", + # ) + # # belief maps gt + # imgs = VisualizeBeliefMap(target_belief[i_output]) + # img, grid = save_image( + # imgs, "some_img.png", mean=0, std=1, nrow=3, save=False + # ) + # writer.add_image( + # f"{post}_belief_ground_truth_{i_output}", + # grid, + # epoch, + # dataformats="CWH", + # ) + # # belief maps guess + # imgs = VisualizeBeliefMap(output_belief[-1][i_output]) + # img, grid = save_image( + # imgs, "some_img.png", mean=0, std=1, nrow=3, save=False + # ) + # writer.add_image( + # f"{post}_belief_guess_{i_output}", + # grid, + # epoch, + # dataformats="CWH", + # ) + + loss.backward() + + optimizer.step() + + nb_update_network += 1 + + # log the loss + loss_avg_to_log["loss"].append(loss.item()) + loss_avg_to_log["loss_class"].append(loss_class.item()) + loss_avg_to_log["loss_affinities"].append(loss_affinities.item()) + loss_avg_to_log["loss_belief"].append(loss_belief.item()) + + if batch_idx % opt.loginterval == 0: + print( + "Train Epoch: {} [{}/{} ({:.0f}%)] \tLoss: {:.15f} \tLocal Rank: {}".format( + epoch, + batch_idx * len(data), + len(train_loader.dataset), + 100.0 * batch_idx / len(train_loader), + loss.item(), + local_rank, + ) + ) + # # log the loss values + # if local_rank == 0: + # writer.add_scalar("loss/train_loss", np.mean(loss_avg_to_log["loss"]), epoch) + # writer.add_scalar("loss/train_cls", np.mean(loss_avg_to_log["loss_class"]), epoch) + # writer.add_scalar("loss/train_aff", np.mean(loss_avg_to_log["loss_affinities"]), epoch) + # writer.add_scalar("loss/train_bel", np.mean(loss_avg_to_log["loss_belief"]), epoch) + + for epoch in range(1, opt.epochs + 1): + + _runnetwork(epoch, trainingdata) + + try: + if local_rank == 0: + torch.save( + net.state_dict(), + f"{opt.outf}/{opt.namefile}_{str(epoch).zfill(3)}.pth", + ) + except Exception as e: + print(f"Encountered Exception: {e}") + + if not opt.nbupdates is None and nb_update_network > int(opt.nbupdates): + break + + # if local_rank == 0: + # save result model + torch.save(net.state_dict(), res_model) #os.path.join(dopepath, wname + EXT_MODEL)) + # else: + # torch.save( + # net.state_dict(), + # f"{opt.outf}/{opt.namefile}_{str(epoch).zfill(3)}_rank_{local_rank}.pth", + # ) + + print("end:", datetime.datetime.now().strftime("%m/%d/%Y, %H:%M:%S")) + print("Total time taken: ", str(datetime.datetime.now() - start_time).split(".")[0]) + +def train_Dope_i(path:str, wname:str, dname:str, outpath:str, epochs:int, pretrain: bool): + """ Main procedure for train DOPE model """ + global K_intrinsic, model_info, camera_data, im_width + + if not os.path.isdir(outpath): + print(f"Invalid output path '{outpath}'") + exit(-1) + out_dir = os.path.join(outpath, wname) + ds_path = os.path.join(path, dname) + + if not os.path.isdir(ds_path): + print(f"{ds_path} : no BOP directory") + return "" + + camera_json = os.path.join(ds_path, FILE_CAMERA) + if not os.path.isfile(camera_json): + print(f"{camera_json} : no intrinsic camera file") + return "" + + rbs_info = os.path.join(ds_path, FILE_RBS_INFO) + if not os.path.isfile(rbs_info): + print(f"{rbs_info} : no dataset info file") + return "" + + camera_data = {} + with open(camera_json, "r") as fh: + data = json.load(fh) + keys = ["cx","cy","fx","fy"] + intrinsic = {k: data[k] for k in keys} + im_height = data["height"] + im_width = data["width"] + camera_data["camera_data"] = dict(intrinsic=intrinsic, height=im_height, width=im_width) + K_intrinsic = [ + [data["fx"], 0.0, data["cx"]], + [0.0, data["fy"], data["cy"]], + [0.0, 0.0, 1.0] + ] + # calc cuboid + center + with open(rbs_info, "r") as fh: + info = json.load(fh) + # список имён объектов + list_name = list(map(lambda x: x["name"], info)) + # in FILE_RBS_INFO model numbering from smallest to largest + model_info = [] + for m_info in info: + cub = np.array(m_info["cuboid"]) * MODEL_SCALE + xyz_min = cub.min(axis=0) + xyz_max = cub.max(axis=0) + # [xc, yc, zc] # min + (max - min) / 2 + center = [] + for i in range(3): + center.append(xyz_min[i] + (xyz_max[i]- xyz_min[i]) / 2) + c = np.array(center, ndmin=2) + model_info.append(np.append(cub, c, axis=0)) + + if pretrain: + # продолжить обучение + if not os.path.isdir(out_dir): + print(f"No dir '{out_dir}'") + exit(-2) + dpath = out_dir + # model_path = os.path.join(dpath, wname + ".pt") + else: + # обучение сначала + if not os.path.isdir(out_dir): + os.mkdir(out_dir) + + dpath = BOP2DOPE_dataset(ds_path, out_dir) + if len(dpath) == 0: + print(f"Error in convert dataset '{ds_path}' to '{outpath}'") + exit(-4) + # model_path = os.path.join(dpath, FILE_BASEMODEL) + + # results = f"python train.py --local_rank 0 --data {dpath} --object fork" \ + # + f" -e {epochs} --batchsize 16 --exts jpg --imagesize 640 --pretrained" \ + # + " --net_path /home/shalenikol/fork_work/dope_training/output/weights_2996/net_epoch_47.pth" + # print(results) + train(dpath, wname, epochs, pretrain, list_name) + +import argparse + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True, help="Path for dataset") + parser.add_argument("--name", required=True, help="String with result weights name") + parser.add_argument("--datasetName", required=True, help="String with dataset name") + parser.add_argument("--outpath", default="weights", help="Output path for weights") + parser.add_argument("--epoch", default=3, help="How many training epochs") + parser.add_argument('--pretrain', action="store_true", help="Use pretraining") + args = parser.parse_args() + + train_Dope_i(args.path, args.name, args.datasetName, args.outpath, args.epoch, args.pretrain) diff --git a/simulation/train_models/train_Yolo.py b/simulation/train_models/train_Yolo.py new file mode 100644 index 0000000..1eaf7a0 --- /dev/null +++ b/simulation/train_models/train_Yolo.py @@ -0,0 +1,181 @@ +""" + train_Yolo + Общая задача: обнаружение объекта (Object detection) + Реализуемая функция: обучение нейросетевой модели YoloV8 по заданному BOP-датасету + + python3 $PYTHON_TRAIN --path /Users/idontsudo/webservice/server/build/public/7065d6b6-c8a3-48c5-9679-bb8f3a690296/datasets \ + --name test123 --datasetName ds213 --outpath /Users/idontsudo/webservice/server/build/public/7065d6b6-c8a3-48c5-9679-bb8f3a690296/weights + + 27.04.2024 @shalenikol release 0.1 +""" +import os +import shutil +import json +import yaml + +from ultralytics import YOLO +# from ultralytics.utils.metrics import DetMetrics + +FILE_BASEMODEL = "yolov8n.pt" +FILE_RBS_INFO = "rbs_info.json" +FILE_RBS_TRAIN = "rbs_train.yaml" +FILE_GT_COCO = "scene_gt_coco.json" +FILE_L_TRAIN = "i_train.txt" +FILE_L_VAL = "i_val.txt" +FILE_TRAIN_RES = "weights/last.pt" +DIR_ROOT_DS = "datasets" +DIR_COCO_DS = "rbs_coco" +DIR_RGB_DS = "images" +DIR_LABELS_DS = "labels" + +SZ_SERIES = 15 # number of train images per validation images + +nn_image = 0 +f1 = f2 = None + +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 gt_parse(path: str, out_dir: str): + global nn_image, f1, f2 + with open(os.path.join(path, FILE_GT_COCO), "r") as fh: + coco_data = json.load(fh) + + for img in coco_data["images"]: + rgb_file = os.path.join(path, img["file_name"]) + if os.path.isfile(rgb_file): + ext = os.path.splitext(rgb_file)[1] # only ext + f = f"{nn_image:06}" + out_img = os.path.join(out_dir, DIR_RGB_DS, f + ext) + shutil.copy2(rgb_file, out_img) + + # заполним файлы с метками bbox + img_id = img["id"] + with open(os.path.join(out_dir, DIR_LABELS_DS, f + ".txt"), "w") as fh: + for i in coco_data["annotations"]: + if i["image_id"] == img_id: + cat_id = i["category_id"] + if cat_id < 999: + bbox = i["bbox"] + im_h = i["height"] + im_w = i["width"] + rel = convert2relative(im_h,im_w,bbox) + # формат: + fh.write(f"{cat_id-1} {rel[0]} {rel[1]} {rel[2]} {rel[3]}\n") # category from 0 + + nn_image += 1 + line = os.path.join("./", DIR_RGB_DS, f + ext) + "\n" + if nn_image % SZ_SERIES == 0: + f2.write(line) + else: + f1.write(line) + +def explore(path: str, res_dir: 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: + if os.path.isfile(os.path.join(path_entry,FILE_GT_COCO)): + gt_parse(path_entry, res_dir) + else: + explore(path_entry, res_dir) + +def BOP2Yolo_dataset(dpath: str, out_dir: str, lname: list) -> str: + """ Convert BOP-dataset to YOLO format for train """ + cfg_yaml = os.path.join(out_dir, FILE_RBS_TRAIN) + p = os.path.join(out_dir, DIR_ROOT_DS, DIR_COCO_DS) + cfg_data = {"path": p, "train": FILE_L_TRAIN, "val": FILE_L_VAL} + cfg_data["names"] = {i:x for i,x in enumerate(lname)} + with open(cfg_yaml, "w") as fh: + yaml.dump(cfg_data, fh) + + res_dir = os.path.join(out_dir, DIR_ROOT_DS) + if not os.path.isdir(res_dir): + os.mkdir(res_dir) + + res_dir = os.path.join(res_dir, DIR_COCO_DS) + if not os.path.isdir(res_dir): + os.mkdir(res_dir) + + p = os.path.join(res_dir, DIR_RGB_DS) + if not os.path.isdir(p): + os.mkdir(p) + p = os.path.join(res_dir, DIR_LABELS_DS) + if not os.path.isdir(p): + os.mkdir(p) + + global f1, f2 + f1 = open(os.path.join(res_dir, FILE_L_TRAIN), "w") + f2 = open(os.path.join(res_dir, FILE_L_VAL), "w") + explore(dpath, res_dir) + f1.close() + f2.close() + + return out_dir + +def train_YoloV8(path:str, wname:str, dname:str, outpath:str, epochs:int, pretrain: bool): + """ Main procedure for train YOLOv8 model """ + if not os.path.isdir(outpath): + print(f"Invalid output path '{outpath}'") + exit(-1) + out_dir = os.path.join(outpath, wname) + + if pretrain: + # продолжить обучение + if not os.path.isdir(out_dir): + print(f"No dir '{out_dir}'") + exit(-2) + dpath = out_dir + model_path = os.path.join(dpath, wname + ".pt") + else: + # обучение сначала + if not os.path.isdir(out_dir): + os.mkdir(out_dir) + + ds_path = os.path.join(path, dname) + rbs_info = os.path.join(ds_path, FILE_RBS_INFO) + if not os.path.isfile(rbs_info): + print(f"{rbs_info} : no dataset description file") + exit(-3) + + with open(rbs_info, "r") as fh: + y = json.load(fh) + # список имён объектов + list_name = list(map(lambda x: x["name"], y)) + + dpath = BOP2Yolo_dataset(ds_path, out_dir, list_name) + if len(dpath) == 0: + print(f"Error in convert dataset '{ds_path}' to '{outpath}'") + exit(-4) + model_path = os.path.join(dpath, FILE_BASEMODEL) + + model = YOLO(model_path) + results = model.train(data=os.path.join(dpath, FILE_RBS_TRAIN), epochs=epochs, project=out_dir) + wf = os.path.join(results.save_dir, FILE_TRAIN_RES) + if not os.path.isfile(wf): + print(f"Error in train: no result file '{wf}'") + exit(-5) + + shutil.copy2(wf, os.path.join(dpath, wname + ".pt")) + shutil.rmtree(results.save_dir) + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True, help="Path for dataset") + parser.add_argument("--name", required=True, help="String with result weights name") + parser.add_argument("--datasetName", required=True, help="String with dataset name") + parser.add_argument("--outpath", default="weights", help="Output path for weights") + parser.add_argument("--epoch", default=3, type=int, help="How many training epochs") + parser.add_argument('--pretrain', action="store_true", help="Use pretraining") + args = parser.parse_args() + + train_YoloV8(args.path, args.name, args.datasetName, args.outpath, args.epoch, args.pretrain) diff --git a/simulation/train_models/utils_dope.py b/simulation/train_models/utils_dope.py new file mode 100755 index 0000000..55ab058 --- /dev/null +++ b/simulation/train_models/utils_dope.py @@ -0,0 +1,967 @@ +""" +NVIDIA from jtremblay@gmail.com +""" +import numpy as np +import torch + +import os + +import torch +import torch.nn as nn +import torch.nn.parallel + +import torch.utils.data + +import torchvision.transforms as transforms + +import torch.utils.data as data +import glob +import os +import boto3 +import io + +from PIL import Image +from PIL import ImageDraw +from PIL import ImageEnhance + +from math import acos +from math import sqrt +from math import pi + +from os.path import exists, basename +import json +from os.path import join + +import albumentations as A + + +def default_loader(path): + return Image.open(path).convert("RGB") + + +def length(v): + return sqrt(v[0] ** 2 + v[1] ** 2) + + +def dot_product(v, w): + return v[0] * w[0] + v[1] * w[1] + + +def normalize(v): + norm = np.linalg.norm(v, ord=1) + if norm == 0: + norm = np.finfo(v.dtype).eps + return v / norm + + +def determinant(v, w): + return v[0] * w[1] - v[1] * w[0] + + +def inner_angle(v, w): + cosx = dot_product(v, w) / (length(v) * length(w)) + rad = acos(cosx) # in radians + return rad * 180 / pi # returns degrees + + +def py_ang(A, B=(1, 0)): + inner = inner_angle(A, B) + det = determinant(A, B) + if ( + det < 0 + ): # this is a property of the det. If the det < 0 then B is clockwise of A + return inner + else: # if the det > 0 then A is immediately clockwise of B + return 360 - inner + + +import colorsys, math + + +def append_dot(extensions): + res = [] + + for ext in extensions: + if not ext.startswith("."): + res.append(f".{ext}") + else: + res.append(ext) + + return res + + +def loadimages(root, extensions=["png"]): + imgs = [] + extensions = append_dot(extensions) + + def add_json_files( + path, + ): + for ext in extensions: + for file in os.listdir(path): + imgpath = os.path.join(path, file) + if ( + imgpath.endswith(ext) + and exists(imgpath) + and exists(imgpath.replace(ext, ".json")) + ): + imgs.append( + ( + imgpath, + imgpath.replace(path, "").replace("/", ""), + imgpath.replace(ext, ".json"), + ) + ) + + def explore(path): + 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: + explore(path_entry) + + add_json_files(path) + + explore(root) + + return imgs + + +def loadweights(root): + if root.endswith(".pth") and os.path.isfile(root): + return [root] + else: + weights = [ + os.path.join(root, f) + for f in os.listdir(root) + if os.path.isfile(os.path.join(root, f)) and f.endswith(".pth") + ] + + weights.sort() + return weights + + +def loadimages_inference(root, extensions): + imgs, imgsname = [], [] + extensions = append_dot(extensions) + + def add_imgs( + path, + ): + for ext in extensions: + for file in os.listdir(path): + imgpath = os.path.join(path, file) + if imgpath.endswith(ext) and exists(imgpath): + imgs.append(imgpath) + imgsname.append(imgpath.replace(root, "")) + + def explore(path): + 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: + explore(path_entry) + + add_imgs(path) + + explore(root) + + return imgs, imgsname + + +class CleanVisiiDopeLoader(data.Dataset): + def __init__( + self, + path_dataset, + objects=None, + sigma=1, + output_size=400, + extensions=["png"], + debug=False, + use_s3=False, + buckets=[], + endpoint_url=None, + ): + ################### + self.path_dataset = path_dataset + self.objects_interest = objects + self.sigma = sigma + self.output_size = output_size + self.extensions = append_dot(extensions) + self.debug = debug + ################### + + self.imgs = [] + self.s3_buckets = {} + self.use_s3 = use_s3 + + if self.use_s3: + self.session = boto3.Session() + self.s3 = self.session.resource( + service_name="s3", endpoint_url=endpoint_url + ) + + for bucket_name in buckets: + try: + self.s3_buckets[bucket_name] = self.s3.Bucket(bucket_name) + except Exception as e: + print( + f"Error trying to load bucket {bucket_name} for training data:", + e, + ) + + for bucket in self.s3_buckets: + bucket_objects = [ + str(obj.key) for obj in self.s3_buckets[bucket].objects.all() + ] + + jsons = set([json for json in bucket_objects if json.endswith(".json")]) + imgs = [ + img + for img in bucket_objects + if img.endswith(tuple(self.extensions)) + ] + + for ext in self.extensions: + for img in imgs: + # Only add images that have a ground truth file + if img.endswith(ext) and img.replace(ext, ".json") in jsons: + # (img key, bucket name, json key) + self.imgs.append((img, bucket, img.replace(ext, ".json"))) + + else: + for path_look in path_dataset: + self.imgs += loadimages(path_look, extensions=self.extensions) + + # np.random.shuffle(self.imgs) + print("Number of Training Images:", len(self.imgs)) + print(self.imgs) + + if debug: + print("Debuging will be save in debug/") + if os.path.isdir("debug"): + print(f'folder {"debug"}/ exists') + else: + os.mkdir("debug") + print(f'created folder {"debug"}/') + + def __len__(self): + return len(self.imgs) + + def __getitem__(self, index): + + # load the data + if self.use_s3: + img_key, bucket, json_key = self.imgs[index] + mem_img = io.BytesIO() + + object_img = self.s3_buckets[bucket].Object(img_key) + object_img.download_fileobj(mem_img) + + img = np.array(Image.open(mem_img).convert("RGB")) + + object_json = self.s3_buckets[bucket].Object(json_key) + data_json = json.load(object_json.get()["Body"]) + + img_name = img_key[:-3] + + else: + path_img, img_name, path_json = self.imgs[index] + + # load the image + img = np.array(Image.open(path_img).convert("RGB")) + + # load the json file + with open(path_json) as f: + data_json = json.load(f) + + all_projected_cuboid_keypoints = [] + + # load the projected cuboid keypoints + for obj in data_json["objects"]: + if ( + self.objects_interest is not None + and not obj["class"] in self.objects_interest + ): + continue + # load the projected_cuboid_keypoints + # 06.02.2024 @shalenikol + # if obj["visibility_image"] > 0: + if obj["visibility"] > 0: + projected_cuboid_keypoints = obj["projected_cuboid"] + # FAT dataset only has 8 corners for 'projected_cuboid' + if len(projected_cuboid_keypoints) == 8: + projected_cuboid_keypoints.append(obj["projected_cuboid_centroid"]) + else: + projected_cuboid_keypoints = [ + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + ] + all_projected_cuboid_keypoints.append(projected_cuboid_keypoints) + + if len(all_projected_cuboid_keypoints) == 0: + all_projected_cuboid_keypoints = [ + [ + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + [-100, -100], + ] + ] + + # flatten the keypoints + flatten_projected_cuboid = [] + for obj in all_projected_cuboid_keypoints: + for p in obj: + flatten_projected_cuboid.append(p) + + ####### + if self.debug: + img_to_save = Image.fromarray(img) + draw = ImageDraw.Draw(img_to_save) + + for ip, p in enumerate(flatten_projected_cuboid): + draw.ellipse( + (int(p[0]) - 2, int(p[1]) - 2, int(p[0]) + 2, int(p[1]) + 2), + fill="green", + ) + + img_to_save.save(f"debug/{img_name.replace('.png','_original.png')}") + ####### + + # data augmentation + transform = A.Compose( + [ + A.RandomCrop(width=400, height=400), + A.Rotate(limit=180), + A.RandomBrightnessContrast( + brightness_limit=0.2, contrast_limit=0.15, p=1 + ), + A.GaussNoise(p=1), + ], + keypoint_params=A.KeypointParams(format="xy", remove_invisible=False), + ) + transformed = transform(image=img, keypoints=flatten_projected_cuboid) + img_transformed = transformed["image"] + flatten_projected_cuboid_transformed = transformed["keypoints"] + + ####### + + # transform to the final output + if not self.output_size == 400: + transform = A.Compose( + [ + A.Resize(width=self.output_size, height=self.output_size), + ], + keypoint_params=A.KeypointParams(format="xy", remove_invisible=False), + ) + transformed = transform( + image=img_transformed, keypoints=flatten_projected_cuboid_transformed + ) + img_transformed_output_size = transformed["image"] + flatten_projected_cuboid_transformed_output_size = transformed["keypoints"] + + else: + img_transformed_output_size = img_transformed + flatten_projected_cuboid_transformed_output_size = ( + flatten_projected_cuboid_transformed + ) + + ####### + if self.debug: + img_transformed_saving = Image.fromarray(img_transformed) + + draw = ImageDraw.Draw(img_transformed_saving) + + for ip, p in enumerate(flatten_projected_cuboid_transformed): + draw.ellipse( + (int(p[0]) - 2, int(p[1]) - 2, int(p[0]) + 2, int(p[1]) + 2), + fill="green", + ) + + img_transformed_saving.save( + f"debug/{img_name.replace('.png','_transformed.png')}" + ) + ####### + + # update the keypoints list + # obj x keypoint_id x (x,y) + i_all = 0 + for i_obj, obj in enumerate(all_projected_cuboid_keypoints): + for i_p, point in enumerate(obj): + all_projected_cuboid_keypoints[i_obj][ + i_p + ] = flatten_projected_cuboid_transformed_output_size[i_all] + i_all += 1 + + # generate the belief maps + beliefs = CreateBeliefMap( + size=int(self.output_size), + pointsBelief=all_projected_cuboid_keypoints, + sigma=self.sigma, + nbpoints=9, + save=False, + ) + beliefs = torch.from_numpy(np.array(beliefs)) + # generate affinity fields with centroid. + affinities = GenerateMapAffinity( + size=int(self.output_size), + nb_vertex=8, + pointsInterest=all_projected_cuboid_keypoints, + objects_centroid=np.array(all_projected_cuboid_keypoints)[:, -1].tolist(), + scale=1, + ) + + # prepare for the image tensors + normalize_tensor = transforms.Compose( + [ + transforms.ToTensor(), + transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)), + ] + ) + to_tensor = transforms.Compose( + [ + transforms.ToTensor(), + ] + ) + img_tensor = normalize_tensor(Image.fromarray(img_transformed)) + img_original = to_tensor(img_transformed) + + ######## + if self.debug: + imgs = VisualizeBeliefMap(beliefs) + img, grid = save_image( + imgs, + f"debug/{img_name.replace('.png','_beliefs.png')}", + mean=0, + std=1, + nrow=3, + save=True, + ) + imgs = VisualizeAffinityMap(affinities) + save_image( + imgs, + f"debug/{img_name.replace('.png','_affinities.png')}", + mean=0, + std=1, + nrow=3, + save=True, + ) + ######## + img_tensor[torch.isnan(img_tensor)] = 0 + affinities[torch.isnan(affinities)] = 0 + beliefs[torch.isnan(beliefs)] = 0 + + img_tensor[torch.isinf(img_tensor)] = 0 + affinities[torch.isinf(affinities)] = 0 + beliefs[torch.isinf(beliefs)] = 0 + + return { + "img": img_tensor, + "affinities": torch.clamp(affinities, -1, 1), + "beliefs": torch.clamp(beliefs, 0, 1), + "file_name": img_name, + "img_original": img_original, + } + + +def VisualizeAffinityMap( + tensor, + # tensor of (len(keypoints)*2)xwxh + threshold_norm_vector=0.4, + # how long does the vector has to be to be drawn + points=None, + # list of points to draw in white on top of the image + factor=1.0, + # by how much the image was reduced, scale factor + translation=(0, 0) + # by how much the points were moved + # return len(keypoints)x3xwxh # stack of images +): + images = torch.zeros(tensor.shape[0] // 2, 3, tensor.shape[1], tensor.shape[2]) + for i_image in range(0, tensor.shape[0], 2): # could be read as i_keypoint + + indices = ( + torch.abs(tensor[i_image, :, :]) + torch.abs(tensor[i_image + 1, :, :]) + > threshold_norm_vector + ).nonzero() + + for indice in indices: + + i, j = indice + + angle_vector = np.array([tensor[i_image, i, j], tensor[i_image + 1, i, j]]) + if length(angle_vector) > threshold_norm_vector: + angle = py_ang(angle_vector) + c = colorsys.hsv_to_rgb(angle / 360, 1, 1) + else: + c = [0, 0, 0] + for i_c in range(3): + images[i_image // 2, i_c, i, j] = c[i_c] + if not points is None: + point = points[i_image // 2] + + print( + int(point[1] * factor + translation[1]), + int(point[0] * factor + translation[0]), + ) + images[ + i_image // 2, + :, + int(point[1] * factor + translation[1]) + - 1 : int(point[1] * factor + translation[1]) + + 1, + int(point[0] * factor + translation[0]) + - 1 : int(point[0] * factor + translation[0]) + + 1, + ] = 1 + + return images + + +def VisualizeBeliefMap( + tensor, + # tensor of len(keypoints)xwxh + points=None, + # list of points to draw on top of the image + factor=1.0, + # by how much the image was reduced, scale factor + translation=(0, 0) + # by how much the points were moved + # return len(keypoints)x3xwxh # stack of images in torch tensor +): + images = torch.zeros(tensor.shape[0], 3, tensor.shape[1], tensor.shape[2]) + for i_image in range(0, tensor.shape[0]): # could be read as i_keypoint + + belief = tensor[i_image].clone() + belief -= float(torch.min(belief).item()) + belief /= float(torch.max(belief).item()) + + belief = torch.clamp(belief, 0, 1) + belief = torch.cat( + [belief.unsqueeze(0), belief.unsqueeze(0), belief.unsqueeze(0)] + ).unsqueeze(0) + + images[i_image] = belief + + return images + + +def GenerateMapAffinity( + size, nb_vertex, pointsInterest, objects_centroid, scale, save=False +): + # Apply the downscale right now, so the vectors are correct. + + img_affinity = Image.new("RGB", (int(size / scale), int(size / scale)), "black") + # create the empty tensors + totensor = transforms.Compose([transforms.ToTensor()]) + + affinities = [] + for i_points in range(nb_vertex): + affinities.append(torch.zeros(2, int(size / scale), int(size / scale))) + + for i_pointsImage in range(len(pointsInterest)): + pointsImage = pointsInterest[i_pointsImage] + center = objects_centroid[i_pointsImage] + for i_points in range(nb_vertex): + point = pointsImage[i_points] + + affinity_pair, img_affinity = getAfinityCenter( + int(size / scale), + int(size / scale), + tuple((np.array(pointsImage[i_points]) / scale).tolist()), + tuple((np.array(center) / scale).tolist()), + img_affinity=img_affinity, + radius=1, + ) + + affinities[i_points] = (affinities[i_points] + affinity_pair) / 2 + + # Normalizing + v = affinities[i_points].numpy() + + xvec = v[0] + yvec = v[1] + + norms = np.sqrt(xvec * xvec + yvec * yvec) + nonzero = norms > 0 + + xvec[nonzero] /= norms[nonzero] + yvec[nonzero] /= norms[nonzero] + + affinities[i_points] = torch.from_numpy(np.concatenate([[xvec], [yvec]])) + affinities = torch.cat(affinities, 0) + + return affinities + + +def getAfinityCenter( + width, height, point, center, radius=7, tensor=None, img_affinity=None +): + """ + Create the affinity map + """ + if tensor is None: + tensor = torch.zeros(2, height, width).float() + + # create the canvas for the afinity output + imgAffinity = Image.new("RGB", (width, height), "black") + totensor = transforms.Compose([transforms.ToTensor()]) + draw = ImageDraw.Draw(imgAffinity) + r1 = radius + p = point + draw.ellipse((p[0] - r1, p[1] - r1, p[0] + r1, p[1] + r1), (255, 255, 255)) + + del draw + + # compute the array to add the afinity + array = (np.array(imgAffinity) / 255)[:, :, 0] + + angle_vector = np.array(center) - np.array(point) + angle_vector = normalize(angle_vector) + affinity = np.concatenate([[array * angle_vector[0]], [array * angle_vector[1]]]) + + if not img_affinity is None: + # find the angle vector + if length(angle_vector) > 0: + angle = py_ang(angle_vector) + else: + angle = 0 + c = np.array(colorsys.hsv_to_rgb(angle / 360, 1, 1)) * 255 + draw = ImageDraw.Draw(img_affinity) + draw.ellipse( + (p[0] - r1, p[1] - r1, p[0] + r1, p[1] + r1), + fill=(int(c[0]), int(c[1]), int(c[2])), + ) + del draw + re = torch.from_numpy(affinity).float() + tensor + return re, img_affinity + + +def CreateBeliefMap(size, pointsBelief, nbpoints, sigma=16, save=False): + # Create the belief maps in the points + beliefsImg = [] + for numb_point in range(nbpoints): + array = np.zeros([size, size]) + out = np.zeros([size, size]) + + for point in pointsBelief: + p = [point[numb_point][1], point[numb_point][0]] + w = int(sigma * 2) + if p[0] - w >= 0 and p[0] + w < size and p[1] - w >= 0 and p[1] + w < size: + for i in range(int(p[0]) - w, int(p[0]) + w + 1): + for j in range(int(p[1]) - w, int(p[1]) + w + 1): + + # if there is already a point there. + array[i, j] = max( + np.exp( + -( + ((i - p[0]) ** 2 + (j - p[1]) ** 2) + / (2 * (sigma**2)) + ) + ), + array[i, j], + ) + + beliefsImg.append(array.copy()) + + if save: + stack = np.stack([array, array, array], axis=0).transpose(2, 1, 0) + imgBelief = Image.fromarray((stack * 255).astype("uint8")) + imgBelief.save("debug/{}.png".format(numb_point)) + return beliefsImg + + +def crop(img, i, j, h, w): + """Crop the given PIL.Image. + Args: + img (PIL.Image): Image to be cropped. + i: Upper pixel coordinate. + j: Left pixel coordinate. + h: Height of the cropped image. + w: Width of the cropped image. + Returns: + PIL.Image: Cropped image. + """ + return img.crop((j, i, j + w, i + h)) + + +class AddRandomContrast(object): + """ + Apply some random image filters from PIL + """ + + def __init__(self, sigma=0.1): + self.sigma = sigma + + def __call__(self, im): + + contrast = ImageEnhance.Contrast(im) + + im = contrast.enhance(np.random.normal(1, self.sigma)) + + return im + + +class AddRandomBrightness(object): + """ + Apply some random image filters from PIL + """ + + def __init__(self, sigma=0.1): + self.sigma = sigma + + def __call__(self, im): + + contrast = ImageEnhance.Brightness(im) + im = contrast.enhance(np.random.normal(1, self.sigma)) + return im + + +class AddNoise(object): + """Given mean: (R, G, B) and std: (R, G, B), + will normalize each channel of the torch.*Tensor, i.e. + channel = (channel - mean) / std + """ + + def __init__(self, std=0.1): + self.std = std + + def __call__(self, tensor): + # TODO: make efficient + t = torch.FloatTensor(tensor.size()).normal_(0, self.std) + + t = tensor.add(t) + t = torch.clamp(t, -1, 1) # this is expansive + return t + + +irange = range + + +def make_grid( + tensor, + nrow=8, + padding=2, + normalize=False, + range=None, + scale_each=False, + pad_value=0, +): + """Make a grid of images. + Args: + tensor (Tensor or list): 4D mini-batch Tensor of shape (B x C x H x W) + or a list of images all of the same size. + nrow (int, optional): Number of images displayed in each row of the grid. + The Final grid size is (B / nrow, nrow). Default is 8. + padding (int, optional): amount of padding. Default is 2. + normalize (bool, optional): If True, shift the image to the range (0, 1), + by subtracting the minimum and dividing by the maximum pixel value. + range (tuple, optional): tuple (min, max) where min and max are numbers, + then these numbers are used to normalize the image. By default, min and max + are computed from the tensor. + scale_each (bool, optional): If True, scale each image in the batch of + images separately rather than the (min, max) over all images. + pad_value (float, optional): Value for the padded pixels. + Example: + See this notebook `here `_ + """ + if not ( + torch.is_tensor(tensor) + or (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor)) + ): + raise TypeError( + "tensor or list of tensors expected, got {}".format(type(tensor)) + ) + + # if list of tensors, convert to a 4D mini-batch Tensor + if isinstance(tensor, list): + tensor = torch.stack(tensor, dim=0) + + if tensor.dim() == 2: # single image H x W + tensor = tensor.view(1, tensor.size(0), tensor.size(1)) + if tensor.dim() == 3: # single image + if tensor.size(0) == 1: # if single-channel, convert to 3-channel + tensor = torch.cat((tensor, tensor, tensor), 0) + tensor = tensor.view(1, tensor.size(0), tensor.size(1), tensor.size(2)) + + if tensor.dim() == 4 and tensor.size(1) == 1: # single-channel images + tensor = torch.cat((tensor, tensor, tensor), 1) + + if normalize is True: + tensor = tensor.clone() # avoid modifying tensor in-place + if range is not None: + assert isinstance( + range, tuple + ), "range has to be a tuple (min, max) if specified. min and max are numbers" + + def norm_ip(img, min, max): + img.clamp_(min=min, max=max) + img.add_(-min).div_(max - min + 1e-5) + + def norm_range(t, range): + if range is not None: + norm_ip(t, range[0], range[1]) + else: + norm_ip(t, float(t.min()), float(t.max())) + + if scale_each is True: + for t in tensor: # loop over mini-batch dimension + norm_range(t, range) + else: + norm_range(tensor, range) + + if tensor.size(0) == 1: + return tensor.squeeze() + + # make the mini-batch of images into a grid + nmaps = tensor.size(0) + xmaps = min(nrow, nmaps) + ymaps = int(math.ceil(float(nmaps) / xmaps)) + height, width = int(tensor.size(2) + padding), int(tensor.size(3) + padding) + grid = tensor.new(3, height * ymaps + padding, width * xmaps + padding).fill_( + pad_value + ) + k = 0 + for y in irange(ymaps): + for x in irange(xmaps): + if k >= nmaps: + break + grid.narrow(1, y * height + padding, height - padding).narrow( + 2, x * width + padding, width - padding + ).copy_(tensor[k]) + k = k + 1 + return grid + + +def save_image(tensor, filename, nrow=4, padding=2, mean=None, std=None, save=True): + """ + Saves a given Tensor into an image file. + If given a mini-batch tensor, will save the tensor as a grid of images. + """ + from PIL import Image + + tensor = tensor.cpu() + grid = make_grid(tensor, nrow=nrow, padding=10, pad_value=1) + if not mean is None: + # ndarr = grid.mul(std).add(mean).mul(255).byte().transpose(0,2).transpose(0,1).numpy() + ndarr = ( + grid.mul(std) + .add(mean) + .mul(255) + .byte() + .transpose(0, 2) + .transpose(0, 1) + .numpy() + ) + else: + ndarr = ( + grid.mul(0.5) + .add(0.5) + .mul(255) + .byte() + .transpose(0, 2) + .transpose(0, 1) + .numpy() + ) + im = Image.fromarray(ndarr) + if save is True: + im.save(filename) + return im, grid + + +from PIL import ImageDraw, Image, ImageFont +import json + + +class Draw(object): + """Drawing helper class to visualize the neural network output""" + + def __init__(self, im): + """ + :param im: The image to draw in. + """ + self.draw = ImageDraw.Draw(im) + self.width = im.size[0] + + def draw_line(self, point1, point2, line_color, line_width=2): + """Draws line on image""" + if point1 is not None and point2 is not None: + self.draw.line([point1, point2], fill=line_color, width=line_width) + + def draw_dot(self, point, point_color, point_radius): + """Draws dot (filled circle) on image""" + if point is not None: + xy = [ + point[0] - point_radius, + point[1] - point_radius, + point[0] + point_radius, + point[1] + point_radius, + ] + self.draw.ellipse(xy, fill=point_color, outline=point_color) + + def draw_text(self, point, text, text_color): + """Draws text on image""" + if point is not None: + self.draw.text(point, text, fill=text_color, font=ImageFont.truetype("misc/arial.ttf", self.width // 50)) + + def draw_cube(self, points, color=(0, 255, 0)): + """ + Draws cube with a thick solid line across + the front top edge and an X on the top face. + """ + # draw front + self.draw_line(points[0], points[1], color) + self.draw_line(points[1], points[2], color) + self.draw_line(points[3], points[2], color) + self.draw_line(points[3], points[0], color) + + # draw back + self.draw_line(points[4], points[5], color) + self.draw_line(points[6], points[5], color) + self.draw_line(points[6], points[7], color) + self.draw_line(points[4], points[7], color) + + # draw sides + self.draw_line(points[0], points[4], color) + self.draw_line(points[7], points[3], color) + self.draw_line(points[5], points[1], color) + self.draw_line(points[2], points[6], color) + + # draw dots + self.draw_dot(points[0], point_color=color, point_radius=4) + self.draw_dot(points[1], point_color=color, point_radius=4) + + # draw x on the top + self.draw_line(points[0], points[5], color) + self.draw_line(points[1], points[4], color) + + # Draw center + self.draw_dot(points[8], point_color=color, point_radius=6) + + for i in range(9): + self.draw_text(points[i], str(i), (255, 0, 0)) + + From 05b1963a34eb421647b0352d20e6f02c125d0ea8 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Fri, 10 May 2024 08:41:24 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20=D1=81=20=D0=B2=D1=8B=D0=B2?= =?UTF-8?q?=D0=BE=D0=B4=D0=BE=D0=BC=20=D0=B2=20=D0=BA=D0=BE=D0=BD=D1=81?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=20=D0=BE=D0=B1=D1=80=D0=B0=D1=82=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 6 +- .../freecad/robossembler/Frames.py | 45 +- ...rtExportEntities.py => export_entities.py} | 0 .../geometry_usecase.py | 63 + .../geometric_feasibility_predicate/main.py | 1 + .../re_main.py | 1313 +++++++++++++++++ .../robossembler/importFreecadUsecase.py | 81 - .../freecad/robossembler/init_gui.py | 8 +- .../freecad/robossembler/is_object_solid.py | 29 - .../{markupEntities.py => markup_entities.py} | 0 .../{poseGenerator.py => pose_generator.py} | 0 .../freecad/robossembler/project_validator.py | 184 +++ freecad_workbench/freecad/update_workbench.sh | 29 + .../test_reductor.20240510-053606.FCBak | Bin 0 -> 66630 bytes test_models/test_reductor.FCStd | Bin 47026 -> 66728 bytes 15 files changed, 1603 insertions(+), 156 deletions(-) rename freecad_workbench/freecad/robossembler/{ImportExportEntities.py => export_entities.py} (100%) create mode 100644 freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/geometry_usecase.py create mode 100644 freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py delete mode 100644 freecad_workbench/freecad/robossembler/importFreecadUsecase.py delete mode 100644 freecad_workbench/freecad/robossembler/is_object_solid.py rename freecad_workbench/freecad/robossembler/{markupEntities.py => markup_entities.py} (100%) rename freecad_workbench/freecad/robossembler/{poseGenerator.py => pose_generator.py} (100%) create mode 100644 freecad_workbench/freecad/robossembler/project_validator.py create mode 100755 freecad_workbench/freecad/update_workbench.sh create mode 100644 test_models/test_reductor.20240510-053606.FCBak diff --git a/.gitignore b/.gitignore index 33406e3..73d42f0 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,8 @@ install_plugin_cad.sh *# .#* \#*\# -out/ \ No newline at end of file +out/ + +#freecad_workbench +freecad_workbench/freecad/update_workbench.sh +*.FCBak \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/Frames.py b/freecad_workbench/freecad/robossembler/Frames.py index 6a794f9..7b3599f 100644 --- a/freecad_workbench/freecad/robossembler/Frames.py +++ b/freecad_workbench/freecad/robossembler/Frames.py @@ -4,8 +4,9 @@ from . import ICONPATH, TRANSLATIONSPATH from .Tools import spawnClassCommand, placement2axisvec, describeSubObject, getPrimitiveInfo #, RobossemblerFreeCadExportScenario from .pddl import freecad2pddl from .BoMList import run_BoM_list -from .ImportExportEntities import export_coordinate_systems +from .export_entities import export_coordinate_systems from .utils.freecad_processor import process_file +from .project_validator import validate_project from .usecases.asm4parser_usecase import Asm4StructureParseUseCase @@ -296,11 +297,6 @@ spawnClassCommand("FrameCommand", "MenuText": "Make a free frame", "ToolTip": "Make a freestanding reference frame."}) -# spawnClassCommand("ASM4StructureParsing", -# RobossemblerFreeCadExportScenario().call, -# {"Pixmap": str(os.path.join(ICONPATH, "assembly4.svg")), -# "MenuText": "Make a ASM4 parsing", -# "ToolTip": "Make a ASM4 1"}) spawnClassCommand("SelectedPartFrameCommand", makeSelectedPartFrames, @@ -319,44 +315,17 @@ spawnClassCommand("FeatureFrameCommand", "MenuText": "frame on selected primitive", "ToolTip": "Create a frame on selected primitive."}) -spawnClassCommand("PDDL_CreateTypes", - freecad2pddl.add_types, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "Types", - "ToolTip": "Add Types"}) - -spawnClassCommand("PDDL_CreateParameters", - freecad2pddl.add_parameters, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "Parameters", - "ToolTip": "Add Parameters"}) -spawnClassCommand("PDDL_CreateAction", - freecad2pddl.add_action, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "Action", - "ToolTip": "Add Action"}) -spawnClassCommand("PDDL_CreatePredicate", - freecad2pddl.add_predicate, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "Predicate", - "ToolTip": "Add Predicate"}) -spawnClassCommand("PDDL_CreateDurativeAction", - freecad2pddl.add_durative_action, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "DurativeAction", - "ToolTip": "Add Durative Action"}) -spawnClassCommand("PDDL_ExportPDDL", - freecad2pddl.export_to_file, - {"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")), - "MenuText": "ExportDomain", - "ToolTip": "Create and Export Domain.pddl to File"}) - spawnClassCommand("Export_Entities", export_coordinate_systems, {"Pixmap": str(os.path.join(ICONPATH, "BoMList.svg")), "MenuText": "ExportLCS", "ToolTip": "Export all the markups"}) +spawnClassCommand("Validate_Project", + validate_project, + {"Pixmap": str(os.path.join(ICONPATH, "")), + "MenuText": "Validate Project", + "ToolTip": "Check errors in project file"}) spawnClassCommand("Publish_Project", process_file, {"Pixmap": str(os.path.join(ICONPATH, "publish.svg")), diff --git a/freecad_workbench/freecad/robossembler/ImportExportEntities.py b/freecad_workbench/freecad/robossembler/export_entities.py similarity index 100% rename from freecad_workbench/freecad/robossembler/ImportExportEntities.py rename to freecad_workbench/freecad/robossembler/export_entities.py diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/geometry_usecase.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/geometry_usecase.py new file mode 100644 index 0000000..3807ad5 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/geometry_usecase.py @@ -0,0 +1,63 @@ +import FreeCAD as App +import FreeCAD.Console as Console + +class GeometryUseCase: + def __init__(self, document): + self.document = document + + def is_valid_geometry(self, obj): + return hasattr(obj, "Shape") and obj.Shape.Volume is not None + + def create_intersection_matrix(self): + bodies = [obj for obj in self.document.Objects if self.is_valid_geometry(obj)] + num_bodies = len(bodies) + intersection_matrix = [[False for _ in range(num_bodies)] for _ in range(num_bodies)] + + for i in range(num_bodies): + for j in range(i + 1, num_bodies): + body1 = bodies[i] + body2 = bodies[j] + + if body1.Shape.common(body2.Shape).Volume > 0: + intersection_matrix[i][j] = True + intersection_matrix[j][i] = True + + if not hasattr(body1, "AllowIntersections") or not hasattr(body2, "AllowIntersections"): + Console.PrintWarning( + f"Пересечение между '{body1.Name}' и '{body2.Name}' без разрешения.\n" + ) + + return intersection_matrix + + def create_contact_matrix(self): + bodies = [obj for obj in self.document.Objects if self.is_valid_geometry(obj)] + num_bodies = len(bodies) + contact_matrix = [[False for _ in range(num_bodies)] for _ in range(num_bodies)] + + for i in range(num_bodies): + for j in range(i + 1, num_bodies): + body1 = bodies[i] + body2 = bodies[j] + + if body1.Shape.common(body2.Shape).SurfaceArea > 0: + contact_matrix[i][j] = True + contact_matrix[j][i] = True + + return contact_matrix + + +doc = App.ActiveDocument +if doc is None: + Console.PrintError("Нет активного документа в FreeCAD.\n") +else: + use_case = GeometryUseCase(doc) + + intersection_matrix = use_case.create_intersection_matrix() + Console.PrintMessage("Матрица пересечений:\n") + for row in intersection_matrix: + Console.PrintMessage(f"{row}\n") + + contact_matrix = use_case.create_contact_matrix() + Console.PrintMessage("Матрица контактов:\n") + for row in contact_matrix: + Console.PrintMessage(f"{row}\n") diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py index c669ba4..f11e36a 100644 --- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py +++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py @@ -31,6 +31,7 @@ from usecases.open_freecad_document_use_case import ( from mocks.mock_structure import bottle_jack_mock_structure, simple_cube_mock_structure + def main(): try: EnvReaderUseCase.call().either( diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py new file mode 100644 index 0000000..19bce43 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py @@ -0,0 +1,1313 @@ +import FreeCAD as App +import uuid +import os +import json +from typing import List, Dict, Any, TypeVar, Callable, Type, cast +from itertools import repeat +from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Any, TypeVar, Type, cast + + +T = TypeVar("T") + + +class BackgroundConsoleColors: + HEADER = "\033[95m" + OKBLUE = "\033[94m" + OKCYAN = "\033[96m" + OKGREEN = "\033[92m" + WARNING = "\033[93m" + FAIL = "\033[91m" + ENDC = "\033[0m" + BOLD = "\033[1m" + UNDERLINE = "\033[4m" + + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def to_float(x: Any) -> float: + assert isinstance(x, float) + return x + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +@dataclass +class Env: + cadFilePath: str + outPath: str + solidBodyPadding: float + firstDetail: str + sequencesFixed: list[list[str]] + + @staticmethod + def from_dict(obj: Any) -> "Env": + assert isinstance(obj, dict) + cadFilePath = from_str(obj.get("cadFilePath")) + outPath = from_str(obj.get("outPath")) + solidBodyPadding = from_float(obj.get("solidBodyPadding")) + firstDetail = from_str(obj.get("firstDetail")) + sequencesFixed = [] + sequencesFixedParse = CoreList(obj.get("sequencesFixed")) + for el in sequencesFixedParse: + sequencesFixed.append(el) + + restrictionsOnFasteners = CoreList(obj.get("restrictionsOnFasteners")) + + for el in restrictionsOnFasteners: + for part in el["parts"]: + sequencesFixed.append([part, el["fastener"]]) + return Env( + cadFilePath, + outPath, + solidBodyPadding, + firstDetail, + sequencesFixed, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["cadFilePath"] = from_str(self.cadFilePath) + result["outPath"] = from_str(self.outPath) + result["solidBodyPadding"] = from_float(self.solidBodyPadding) + result["firstDetail"] = from_str(self.firstDetail) + result["sequencesFixed"] = self.sequencesFixed + return result + + +class Either(ABC): + @abstractmethod + def isLeft(self): + pass + + @abstractmethod + def isRight(self): + pass + + @abstractmethod + def getLeft(self): + pass + + @abstractmethod + def getRight(self): + pass + + @abstractmethod + def mapLeft(self, lf): + pass + + @abstractmethod + def mapRight(self, rf): + pass + + @abstractmethod + def either(self, leftF, rightF): + pass + + +class Left(Either): + def __init__(self, lvalue): + self.lvalue = lvalue + + def isLeft(self): + return True + + def isRight(self): + return False + + def getLeft(self): + return self.lvalue + + def getRight(self): + raise Error("Cannot get a right value out of Left.") + + def mapLeft(self, lf): + return Left(lf(self.lvalue)) + + def mapRight(self, rf): + return Left(self.lvalue) + + def either(self, leftF, rightF): + return leftF(self.lvalue) + + +class Right(Either): + def __init__(self, rvalue): + self.rvalue = rvalue + + def isLeft(self): + return False + + def isRight(self): + return True + + def getLeft(self): + raise Error("Cannot get a left value out of Right.") + + def getRight(self): + return self.rvalue + + def mapLeft(self, lf): + return Right(self.rvalue) + + def mapRight(self, rf): + return Right(rf(self.rvalue)) + + def either(self, leftF, rightF): + return rightF(self.rvalue) + + +class CoreDict(dict): + def isEquivalentByKeys(self, checkDict) -> bool: + print(checkDict) + for key in self: + value = checkDict.get(key) + if value is None: + return False + + return True + + def isMatchByKeys(self, checkList): + if len(self) != len(checkList): + return False + + sorted_dict_keys = sorted(self.keys()) + sorted_list_elements = sorted(checkList) + + if sorted_dict_keys != sorted_list_elements: + missing_keys = [ + key for key in sorted_list_elements if key not in sorted_dict_keys + ] + print(f"Отсутствующие ключи в словаре: {missing_keys}") + return False + + return True + + +class CoreList(List): + def onlyTrue(self) -> bool: + for el in self: + if el is True: + return True + return False + + def onlyUniqueElementAppend(self, el): + if el is None: + return + if self.is_element_in_array(el) is False: + self.append(el) + pass + + def is_element_in_array(self, element): + return element in self + + def equal(self, array: list) -> bool: + if len(self) != len(array): + return False + return self.sort() == array.sort() + + def isConstrainsString(self) -> bool: + for el in self: + if isinstance(el, str): + return True + return False + + def indexedPriorities(self, lowerPriority, highPriority) -> bool: + try: + lowerIndex = self.index(lowerPriority) + highPriorityIndex = self.index(highPriority) + return lowerIndex < highPriorityIndex + except: + return False + + def getAllString(self) -> list[str]: + return list(filter(lambda x: isinstance(x, str), self)) + + def onlyUnique(self) -> list: + result = [] + [result.append(x) for x in self if x not in self] + return result + + def spreadArray(self) -> "CoreList": + unpacked_array = CoreList([]) + for el in self: + if isinstance(el, list): + unpacked_array.extend(el) + else: + unpacked_array.append(el) + return unpacked_array + + +def isInListRange(listIn, index): + try: + listIn[index] + return False + except: + return True + + +class AllSequences: + all_sequences = None + adj_matrix = None + topologyIds = None + adj_matrix_names = None + + def __init__(self, adj_matrix, restrictions: list[str]) -> None: + self.adj_matrix = adj_matrix + self.all_possible_sequences(self.adj_matrix) + self.matrix_by_name() + if restrictions.__len__() != 0: + self.restrictionsValidate(restrictions) + pass + + def restrictionsValidate(self, restrictions: CoreList[str]): + filterMatrix = CoreList() + + for el in self.adj_matrix_names: + result = False + for restraint in restrictions: + result = CoreList(el).indexedPriorities(restraint[0], restraint[1]) + if result: + filterMatrix.onlyUniqueElementAppend(el) + self.adj_matrix_names = filterMatrix + pass + + def matrix_by_name(self): + result = self.all_sequences + inc = 0 + for matrix in self.all_sequences: + for index in range(len(matrix)): + result[inc][index] = CoreList( + filter( + lambda el: el.get("number") == matrix[index] + 1, + self.topologyIds, + ) + )[0].get("name") + inc += 1 + self.adj_matrix_names = result + pass + + def find_all_sequences(self, adj_matrix): + sequences = [] + num_vertices = len(adj_matrix) + + def dfs(vertex, sequence): + sequence.append(vertex) + + if len(sequence) == num_vertices: + sequences.append(sequence) + return + + for i in range(num_vertices): + if adj_matrix[vertex][i] == 1 and i not in sequence: + dfs(i, sequence.copy()) + + for i in range(num_vertices): + dfs(i, []) + + self.all_sequences = sequences + + def findId(self, listMatrix, id): + def filter_odd_num(in_num): + if in_num["name"] == id: + return True + else: + return False + + return list(filter(filter_odd_num, listMatrix))[0]["number"] + + def iter_paths(self, adj, min_length=6, path=None): + if not path: + for start_node in range(len(adj)): + yield from self.iter_paths(adj, min_length, [start_node]) + else: + if len(path) >= min_length: + yield path + if path[-1] in path[:-1]: + return + current_node = path[-1] + for next_node in range(len(adj[current_node])): + if adj[current_node][next_node] == 1: + yield from self.iter_paths(adj, min_length, path + [next_node]) + + def allUnique(self, x): + seen = set() + return not any(i in seen or seen.add(i) for i in x) + + def all_possible_sequences(self, matrix): + topologyIds = [] + topologyMatrixNumber = {} + inc = 0 + for k, v in matrix.items(): + inc += 1 + topologyIds.append({"name": k, "number": inc}) + inc = 0 + for k, v in matrix.items(): + inc += 1 + topologyMatrixNumber[inc] = list( + map(lambda el: self.findId(topologyIds, el), v) + ) + self.topologyIds = topologyIds + adj = [] + matrixSize = matrix.keys().__len__() + inc = 0 + for k, v in topologyMatrixNumber.items(): + adj.append(list(repeat(0, matrixSize))) + for el in v: + adj[inc][el - 1] = 1 + inc += 1 + return self.find_all_sequences(adj) + + +class VectorModel: + x: float + y: float + z: float + + def __init__(self, cadVector) -> None: + self.x = cadVector[0] + self.y = cadVector[1] + self.z = cadVector[2] + pass + + def toFreeCadVector(self): + return App.Vector(self.x, self.y, self.z) + + def toString(self): + return str("x:" + str(self.x) + "y:" + str(self.y) + "z:" + str(self.z)) + + +class FreeCadRepository: + _solids = [] + + def openDocument(self, path: str): + App.open("" + path) + + def closeIfOpenDocument(self): + # print('Проверка на документ') + try: + if App.ActiveDocument is not None: + # print(App.ActiveDocument.name + "закрыт") + # App.closeDocument(App.ActiveDocument.name) + App.ActiveDocument.clearDocument() + except Exception as e: + print(e) + + def getAllLabelsSolids(self) -> List[str]: + return list(map(lambda el: el.Label, self.getAllSolids())) + + def isAllObjectsSolids(self) -> List[str]: + result = [] + for part in App.ActiveDocument.Objects: + if self.is_object_solid(part) is False: + result.append(part.Label) + return result + + def objectSetPosition(self, solid, cadVector): + solid.Placement.Base = cadVector + pass + + def objectGetPosition(self, solid) -> VectorModel: + return VectorModel(cadVector=solid.Placement.Base) + + def isObjectIntersections(self, part) -> str: + result = [] + for solid in self.getAllSolids(): + if solid.ID != part.ID: + collisionResult: int = int(part.Shape.distToShape(solid.Shape)[0]) + if collisionResult == 0: + result.append(solid.Label) + if result.__len__() == 0: + return None + return result + + def objectHasTouches(self, part, solidBodyPadding: float) -> List[str]: + try: + positionVector = self.objectGetPosition(part) + result = CoreList() + result.append(self.isObjectIntersections(part=part)) + if solidBodyPadding != 0 and solidBodyPadding != None: + result.append( + self.axis_movement_and_intersections_observer( + positionVector=positionVector, + alongAxis="x", + solidBodyPadding=solidBodyPadding, + part=part, + ) + ) + result.append( + self.axis_movement_and_intersections_observer( + positionVector=positionVector, + alongAxis="y", + solidBodyPadding=solidBodyPadding, + part=part, + ) + ) + result.append( + self.axis_movement_and_intersections_observer( + positionVector=positionVector, + alongAxis="z", + solidBodyPadding=solidBodyPadding, + part=part, + ) + ) + spreadArr = result.spreadArray() + if spreadArr.isConstrainsString(): + return spreadArr.getAllString() + return None + except Exception as error: + print(error) + return None + + def axis_movement_and_intersections_observer( + self, + positionVector: VectorModel, + alongAxis: str, + solidBodyPadding: float, + part, + ) -> bool: + result = CoreList() + # UP + positionVector.__setattr__( + alongAxis, + positionVector.__getattribute__(alongAxis) + solidBodyPadding, + ) + self.objectSetPosition(part, positionVector.toFreeCadVector()) + # result.onlyUniqueElementAppend(self.isObjectIntersections(part=part)) + result.extend(self.isObjectIntersections(part=part)) + # RESET UP CHANGES + positionVector.__setattr__( + alongAxis, + positionVector.__getattribute__(alongAxis) - solidBodyPadding, + ) + self.objectSetPosition(part, positionVector.toFreeCadVector()) + + # DOWN + positionVector.__setattr__( + alongAxis, + positionVector.__getattribute__(alongAxis) - solidBodyPadding, + ) + self.objectSetPosition(part, positionVector.toFreeCadVector()) + # CHECK DOWN INTERSECTIONS + # result.onlyUniqueElementAppend(self.isObjectIntersections(part=part)) + result.extend(self.isObjectIntersections(part=part)) + + # RESET DOWN CHANGES + positionVector.__setattr__( + alongAxis, + positionVector.__getattribute__(alongAxis) + solidBodyPadding, + ) + self.objectSetPosition(part, positionVector.toFreeCadVector()) + if result.__len__() == 0: + return None + + return result.onlyUnique() + + def getAllSolids(self): + if self._solids.__len__() == 0: + for part in App.ActiveDocument.Objects: + if self.is_object_solid(part): + self._solids.append(part) + + return self._solids + + def is_object_solid(self, obj): + if not isinstance(obj, App.DocumentObject): + return False + if hasattr(obj, "Group"): + return False + + if not hasattr(obj, "Shape"): + return False + if not hasattr(obj.Shape, "Solids"): + return False + + if len(obj.Shape.Solids) == 0: + return False + + return True + + +T = TypeVar("T") + + +def from_list(f: Callable[[Any], T], x: Any) -> List[T]: + assert isinstance(x, list) + return [f(y) for y in x] + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + +def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]: + assert isinstance(x, dict) + return {k: f(v) for (k, v) in x.items()} + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +# Вспомогательный класс который делает генрацию JSON на основе пайтон обьектов + + +class AdjacencyMatrix: + matrixError: Dict[str, str] = {} + all_parts: List[str] + first_detail: str + matrix: Dict[str, List[str]] + + fileName = "adjacency_matrix.json" + + def __init__( + self, all_parts: List[str], first_detail: str, matrix: Dict[str, List[str]] + ) -> None: + self.all_parts = all_parts + self.first_detail = first_detail + self.matrix = matrix + self.validateMatrix() + + def matrixToFileSystem(self, path: str): + FileSystemRepository.writeFile( + json.dumps(self.to_dict(), ensure_ascii=False, indent=4), + path, + AdjacencyMatrix.fileName, + ) + pass + + def sequencesToFileSystem(self, path: str, restrictions: list[str]): + FileSystemRepository.writeFile( + json.dumps( + {"sequences": AllSequences(self.matrix, restrictions).adj_matrix_names}, + ensure_ascii=False, + indent=4, + ), + path, + "sequences.json", + ), + pass + + def matrixGetUniqueContact(self): + detailsToCheck = [] + detailsHashCheck = {} + for k, v in self.matrix.items(): + for el in v: + if el != k: + hash = to_ascii_hash(k + el) + if detailsHashCheck.get(hash) == None: + detailsHashCheck[hash] = hash + detailsToCheck.append({"child": el, "parent": k}) + return detailsToCheck + + def whatPlaceLeadingPartIndex(self): + i = 0 + for el in self.matrix: + if el == self.first_detail: + return i + i = +1 + + def validateMatrix(self): + for el in self.all_parts: + if self.matrix.get(el) == None: + self.matrixError[el] = "Not found adjacency " + el + + @staticmethod + def from_dict(obj: Any) -> "AdjacencyMatrix": + assert isinstance(obj, dict) + + all_pars = from_list(from_str, obj.get("allParts")) + first_detail = from_str(obj.get("firstDetail")) + matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix")) + + return AdjacencyMatrix(all_pars, first_detail, matrix) + + def to_dict(self) -> dict: + result: dict = {} + result["allParts"] = from_list(from_str, self.all_parts) + result["firstDetail"] = from_str(self.first_detail) + result["matrix"] = from_dict(lambda x: from_list(from_str, x), self.matrix) + if self.matrixError.values().__len__() == 0: + result["matrixError"] = None + else: + result["matrixError"] = self.matrixError + return result + + def getDictMatrix(self) -> dict: + result = {} + + for k, v in self.matrix.items(): + result[k] = {} + for el in v: + result[k][el] = el + + return result + + +def adjacency_matrix_from_dict(s: Any) -> AdjacencyMatrix: + return AdjacencyMatrix.from_dict(s) + + +def adjacency_matrix_to_dict(x: AdjacencyMatrix) -> Any: + return to_class(AdjacencyMatrix, x) + + +# Вспомогательный класс для работы с Freecad + + +class FreeCadMetaModel(object): + def __init__(self, label, vertex) -> None: + self.label = label + self.vertex = vertex + + +collision_squares_labels = [] + + +class MeshGeometryCoordinateModel(object): + # Получение геометрии мешей + def __init__( + self, + x, + y, + z, + label, + ): + self.x = x + self.y = y + self.z = z + self.label = label + self.cadLabel = "" + + def initializePrimitivesByCoordinate(self, detailSquares): + uuidDoc = str(uuid.uuid1()) + App.ActiveDocument.addObject("Part::Box", "Box") + App.ActiveDocument.ActiveObject.Label = uuidDoc + App.ActiveDocument.recompute() + part = App.ActiveDocument.getObjectsByLabel(uuidDoc)[0] + collision_squares_labels.append(uuidDoc) + part.Width = 2 + part.Height = 2 + part.Length = 2 + part.Placement = App.Placement( + App.Vector(self.x - 1, self.y - 1, self.z - 1), + App.Rotation(App.Vector(0.00, 0.00, 1.00), 0.00), + ) + if detailSquares.get(self.label) is None: + detailSquares[self.label] = [] + detailSquares[self.label].append(self) + self.cadLabel = uuidDoc + App.ActiveDocument.recompute() + + +class FileSystemRepository: + def readJSON(path: str): + return json.loads((open(path)).read()) + + def writeFile(data, filePath, fileName): + file_to_open = filePath + fileName + + f = open(file_to_open, "w", encoding="utf8") + + f.write(data) + + def readFile(path: str): + return open(path).read() + + def readFilesTypeFolder(pathFolder: str, fileType=".json"): + filesJson = list( + filter( + lambda x: x[-fileType.__len__() :] == fileType, os.listdir(pathFolder) + ) + ) + return filesJson + + +class GetAllPartsLabelsUseCase: + # Получение всех названий деталей + def call(self): + parts = [] + for part in FreeCadRepository().getAllSolids(): + parts.append(part.Label) + return parts + + +def isUnique(array, element): + for i in array: + if i == element: + return False + + return True + + +class GetCollisionAtPrimitiveUseCase(object): + # Получение колизий примитивов + def call(self, freeCadMetaModels, detailSquares) -> Dict[str, List[str]]: + matrix: Dict[str, List[str]] = {} + for model in freeCadMetaModels: + activePart = App.ActiveDocument.getObjectsByLabel(model.label)[0] + for key in detailSquares: + if model.label != key: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel + )[0] + collisionResult: int = int( + activePart.Shape.distToShape(primitivePart.Shape)[0] + ) + if collisionResult == 0: + if matrix.get(model.label) == None: + matrix[model.label] = [renderPrimitive.label] + else: + if isUnique(matrix[model.label], renderPrimitive.label): + matrix[model.label].append(renderPrimitive.label) + return matrix + + +class GetFirstDetailUseCase: + # Получение первой детали + def call(self): + return FreeCadRepository().getAllSolids()[0].Label + + +class GetPartPrimitiveCoordinatesUseCase(object): + # Получение координат примитивов + def call(self, freeCadMetaModels): + meshCoordinates: list[MeshGeometryCoordinateModel] = [] + for model in freeCadMetaModels: + vertexesDetail = model.vertex + labelDetail = model.label + for coords in vertexesDetail: + detailVertex = MeshGeometryCoordinateModel( + coords.X, + coords.Y, + coords.Z, + labelDetail, + ) + meshCoordinates.append(detailVertex) + + return meshCoordinates + + +class InitPartsParseUseCase: + # Инициализация парсинга + def call(self): + product_details = [] + for part in FreeCadRepository().getAllSolids(): + if part is not None: + model = FreeCadMetaModel(part.Label, part.Shape.Vertexes) + if model is not None: + product_details.append(model) + return product_details + + +class RenderPrimitiveUseCase(object): + # Рендеринг премитивов + def call( + self, meshModels: list[MeshGeometryCoordinateModel], detailSquares + ) -> None: + for mesh in meshModels: + mesh.initializePrimitivesByCoordinate(detailSquares) + + +class ClearWorkSpaceDocumentUseCase(object): + # Очистка рабочего простарнства + def call(self, detailSquares): + for key in detailSquares: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel + )[0] + App.ActiveDocument.removeObject(primitivePart.Name) + + +class RenderPrimitivesScenario(object): + def __init__( + self, + initPartsParseUseCase: InitPartsParseUseCase, + getPartPrimitiveCoordinatesUseCase: GetPartPrimitiveCoordinatesUseCase, + renderPrimitiveUseCase: RenderPrimitiveUseCase, + getCollisionAtPrimitives: GetCollisionAtPrimitiveUseCase, + clearWorkSpaceDocument: ClearWorkSpaceDocumentUseCase, + ) -> None: + self.initPartsParseUseCase = initPartsParseUseCase + self.getPartPrimitiveCoordinatesUseCase = getPartPrimitiveCoordinatesUseCase + self.renderPrimitiveUseCase = renderPrimitiveUseCase + self.getCollisionAtPrimitives = getCollisionAtPrimitives + self.clearWorkSpaceDocument = clearWorkSpaceDocument + + def call(self) -> None: + meshCoordinates = [] + detailSquares = {} + parts = self.initPartsParseUseCase.call() + meshCoordinates = self.getPartPrimitiveCoordinatesUseCase.call(parts) + self.renderPrimitiveUseCase.call(meshCoordinates, detailSquares) + matrix = self.getCollisionAtPrimitives.call(parts, detailSquares) + self.clearWorkSpaceDocument.call(detailSquares) + return matrix + + +class ClearWorkSpaceDocumentUseCase(object): + # Очистака рабочего пространства + def call(self, detailSquares): + for key in detailSquares: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel + )[0] + App.ActiveDocument.removeObject(primitivePart.Name) + + +class CadAdjacencyMatrix: + # Матрица основанная на соприкосновении примитива с обьектами + def primitiveMatrix(self): + # Получение матрицы + matrix = RenderPrimitivesScenario( + InitPartsParseUseCase(), + GetPartPrimitiveCoordinatesUseCase(), + RenderPrimitiveUseCase(), + GetCollisionAtPrimitiveUseCase(), + ClearWorkSpaceDocumentUseCase(), + ).call() + return AdjacencyMatrix( + all_parts=GetAllPartsLabelsUseCase().call(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) + + # Матрица основанная на соприкосновении обьектов + + def matrixBySurfaces(self): + matrix = {} + for part in FreeCadRepository().getAllSolids(): + matrix[part.Label] = [] + for nextPart in FreeCadRepository().getAllSolids(): + if part.Label != nextPart.Label: + # Вычисление соприконсоновения площади деталей + collisionResult: int = int( + part.Shape.distToShape(nextPart.Shape)[0] + ) + + if collisionResult == 0: + matrix[part.Label].append(nextPart.Label) + + return AdjacencyMatrix( + all_parts=GetAllPartsLabelsUseCase().call(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) + + +def reduce(function, iterable, initializer=None): + it = iter(iterable) + if initializer is None: + value = next(it) + else: + value = initializer + for element in it: + value = function(value, element) + return value + + +def to_ascii_hash(text): + ascii_values = [ord(character) for character in text] + return reduce(lambda x, y: x + y, ascii_values) + + +class IntersectionComputedUseCase: + def call(parts): + App.activeDocument().addObject("Part::MultiCommon", "Common") + App.activeDocument().Common.Shapes = [parts[0], parts[1]] + App.activeDocument().getObject("Common").ViewObject.ShapeColor = getattr( + parts[0].getLinkedObject(True).ViewObject, + "ShapeColor", + App.activeDocument().getObject("Common").ViewObject.ShapeColor, + ) + App.activeDocument().getObject("Common").ViewObject.DisplayMode = getattr( + parts[0].getLinkedObject(True).ViewObject, + "DisplayMode", + App.activeDocument().getObject("Common").ViewObject.DisplayMode, + ) + App.ActiveDocument.recompute() + area = App.activeDocument().getObject("Common").Shape.Area + App.ActiveDocument.removeObject("Common") + return area + + +class ErrorStringModel: + def __init__(self, error: str) -> None: + self.error = error + pass + + error: str + + def toString(self) -> str: + return json.dumps( + { + "error": self.error, + }, + ensure_ascii=False, + indent=4, + ) + + def toFileSystem(self, path: str): + return ( + FileSystemRepository.writeFile(self.toString(), path, "error.json"), + ExitFreeCadUseCase.call(), + ) + + +class IsAllObjectSolidsCheckUseCase: + def call() -> Either: + result = FreeCadRepository().isAllObjectsSolids() + if result.__len__() == 0: + return Left(None) + + return Right( + ErrorStringModel(error="Is not solid objects: " + ",".join(result)) + ) + + +class CheckObjectHasTouchesUseCase: + freeCadRepository = FreeCadRepository() + + def call(self, solidBodyPadding: float) -> Either: + try: + errorResult = [] + matrix = {} + for part in self.freeCadRepository.getAllSolids(): + matrix[part.Label] = [] + touches = FreeCadRepository().objectHasTouches( + part=part, solidBodyPadding=solidBodyPadding + ) + matrix[part.Label].extend(touches) + if errorResult.__len__() == 0: + return Left( + AdjacencyMatrix( + all_parts=self.freeCadRepository.getAllLabelsSolids(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) + ) + else: + return Right( + ErrorStringModel( + error="Solids bodies have no recounts: " + ",".join(errorResult) + ) + ) + except Exception as error: + print(error) + print("CheckObjectHasTouchesUseCase error") + return Right(ErrorStringModel(error="CheckObjectHasTouchesUseCase error")) + + +class CheckCadIntersectionObjects: + report = [] + + def call() -> bool: + FreeCadRepository().getAllSolids() + return False + + +class ExitFreeCadUseCase: + def call(): + import FreeCADGui as Gui + + App.ActiveDocument.clearDocument() + freecadQTWindow = Gui.getMainWindow() + freecadQTWindow.close() + + +# class CheckValidIntersectionUseCase: +# def call() -> ErrorStringModel: +# for part in FreeCadRepository().getAllSolids(): +# print(part) +# FreeCadRepository().obj +# pass + + +class EnvReaderUseCase: + def call() -> Either: + try: + return Left(Env.from_dict(FileSystemRepository.readJSON("env.json"))) + except: + print("env reader error") + return Right(None) + + +class OpenFreeCadDocumentUseCase: + def call(path: str) -> Either: + try: + FreeCadRepository().closeIfOpenDocument() + FreeCadRepository().openDocument(path) + return Left(None) + except: + print("OpenFreeCadDocumentUseCase error") + return Right(None) + + +class IntersectionGeometryUseCase: + def call(contacts, path): + intersection_geometry = {"status": True, "recalculations": None} + for el in contacts: + child = App.ActiveDocument.getObjectsByLabel(el.get("child"))[0] + parent = App.ActiveDocument.getObjectsByLabel(el.get("parent"))[0] + area = IntersectionComputedUseCase.call([child, parent]) + if area != 0.0: + if intersection_geometry.get("recalculations") == None: + intersection_geometry["status"] = False + intersection_geometry["recalculations"] = [] + intersection_geometry["recalculations"].append( + {"area": area, "connect": el.get("child") + " " + el.get("parent")} + ) + FileSystemRepository.writeFile( + json.dumps(intersection_geometry, ensure_ascii=False, indent=4), + path, + "intersection_geometry.json", + ) + + +def main(): + try: + EnvReaderUseCase.call().either( + leftF=lambda environment: ( + OpenFreeCadDocumentUseCase.call(environment.cadFilePath).either( + leftF=lambda _: ( + IsAllObjectSolidsCheckUseCase.call().either( + leftF=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(environment.solidBodyPadding) + .either( + leftF=lambda adjaxedMatrix: ( + adjaxedMatrix.sequencesToFileSystem( + environment.outPath, + environment.sequencesFixed, + ), + IntersectionGeometryUseCase.call( + adjaxedMatrix.matrixGetUniqueContact(), + environment.outPath, + ), + adjaxedMatrix.matrixToFileSystem( + environment.outPath, + ), + ExitFreeCadUseCase.call(), + ), + rightF=lambda error: error.toFileSystem( + environment.outPath + ), + ), + ), + rightF=lambda error: error.toFileSystem( + environment.outPath + ), + ), + ), + rightF=lambda error: print(error), + ), + ), + rightF=lambda error: print(error), + ) + + except Exception as error: + print(error) + ExitFreeCadUseCase.call() + + +# main() + + +class ReadFileSystemAndGetInstanceModelUseCase: + def call(self, model, path): + if hasattr(model, "from_dict") is False: + return Right( + "ReadFileSystemAndGetInstanceModelUseCase error:" + + model + + "is not have method" + + "from_dict()" + ) + try: + return Left(model.from_dict(FileSystemRepository.readJSON(path))) + except: + error = str(model) + " " + "from dict error " + "path: " + path + print("ReadFileSystemAndGetInstanceModelUseCase error" + error) + return Right(error) + pass + + +class FreeCadTest: + testName: str + + def testHelper(self, testResult): + if isinstance(testResult, bool) is False: + print( + BackgroundConsoleColors.WARNING, + self.testName + + " expected a value of type Boolean, returned a value of type below ", + ) + print(testResult) + return + if testResult: + print(BackgroundConsoleColors.OKGREEN, self.testName + "is complete!") + else: + print(BackgroundConsoleColors.FAIL, self.testName + " is Error!") + pass + + +class FreeCadASPGenerationTestController(FreeCadTest): + testName: str + + def __init__(self, testName: str) -> None: + self.testName = testName + pass + + def test( + self, + assertFn, + documentPath: str, + modelName: str, + model, + execComposition, + outPath=os.path.dirname(__file__) + "/out/", + ): + try: + OpenFreeCadDocumentUseCase.call(documentPath).either( + leftF=lambda _: ( + execComposition(""), + ReadFileSystemAndGetInstanceModelUseCase() + .call(model=model, path=outPath + modelName) + .either( + leftF=lambda model: ( + self.testHelper( + assertFn(model), + ) + ), + rightF=lambda error: print(error), + ), + ), + rightF=lambda error: print(error), + ) + except Exception as inst: + print("FreeCadASPGenerationTestController error") + print(inst) + pass + + +def test(): + try: + mocksFolder = os.path.dirname(__file__) + "/mocks/" + outFolder = os.path.dirname(__file__) + "/out/" + + # FreeCadASPGenerationTestController("test adjaxed matrix simple cube").test( + # assertFn=lambda model: CoreList(model.all_parts).equal(["Куб", "Куб001"]), + # execComposition=lambda _: ( + # CheckObjectHasTouchesUseCase() + # .call(0) + # .either( + # leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), + # rightF=lambda error: print(error), + # ) + # ), + # documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd", + # modelName=AdjacencyMatrix.fileName, + # model=AdjacencyMatrix, + # ) + + FreeCadASPGenerationTestController( + "test adjaxed matrix vs structure of document" + ).test( + assertFn=lambda model: CoreDict(model.matrix).isEquivalentByKeys( + { + "Бутылочный домкрат 4т_Гильза": [ + "Бутылочный домкрат 4т_Тяга", + "Бутылочный домкрат 4т_Тяга001", + "Бутылочный домкрат 4т_Шток насоса", + "Бутылочный домкрат 4т_Шток", + "Бутылочный домкрат 4т_Вентиль", + ], + "Бутылочный домкрат 4т_Тяга": [ + "Бутылочный домкрат 4т_Гильза", + "Бутылочный домкрат 4т_Тяга001", + "Бутылочный домкрат 4т_Коромысло", + ], + "Бутылочный домкрат 4т_Тяга001": [ + "Бутылочный домкрат 4т_Гильза", + "Бутылочный домкрат 4т_Тяга", + "Бутылочный домкрат 4т_Коромысло", + ], + "Бутылочный домкрат 4т_Шток насоса": [ + "Бутылочный домкрат 4т_Гильза", + "Бутылочный домкрат 4т_Коромысло", + ], + "Бутылочный домкрат 4т_Коромысло": [ + "Бутылочный домкрат 4т_Тяга", + "Бутылочный домкрат 4т_Тяга001", + "Бутылочный домкрат 4т_Шток насоса", + ], + "Бутылочный домкрат 4т_Шток": [ + "Бутылочный домкрат 4т_Гильза", + "Бутылочный домкрат 4т_Винт штока", + ], + "Бутылочный домкрат 4т_Винт штока": ["Бутылочный домкрат 4т_Шток"], + "Бутылочный домкрат 4т_Вентиль": ["Бутылочный домкрат 4т_Гильза"], + } + ), + execComposition=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(0) + .either( + leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), + rightF=lambda error: print(error), + ) + ), + documentPath=mocksFolder + "bottle_jack.FCStd", + modelName=AdjacencyMatrix.fileName, + model=AdjacencyMatrix, + ) + + FreeCadASPGenerationTestController( + "test adjacency matrix keys vs allparts" + ).test( + assertFn=lambda model: CoreDict(model.matrix).isMatchByKeys( + ["Куб", "Куб001"] + ), + execComposition=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(0) + .either( + leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), + rightF=lambda error: print(error), + ) + ), + documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd", + modelName=AdjacencyMatrix.fileName, + model=AdjacencyMatrix, + ) + + ExitFreeCadUseCase.call() + except: + print("test error") + ExitFreeCadUseCase.call() + pass + + +# test() + diff --git a/freecad_workbench/freecad/robossembler/importFreecadUsecase.py b/freecad_workbench/freecad/robossembler/importFreecadUsecase.py deleted file mode 100644 index 3ed4fae..0000000 --- a/freecad_workbench/freecad/robossembler/importFreecadUsecase.py +++ /dev/null @@ -1,81 +0,0 @@ -import FreeCAD -import Part -import ImportGui - -class GeometryValidateUseCase: - def __init__(self): - self.doc = None - - - - - def import_step_and_check(self, file_path): - - FreeCAD.closeDocument("Unnamed") - - - self.doc = FreeCAD.newDocument("Unnamed") - - - ImportGui.open(file_path, "Unnamed") - - - FreeCAD.ActiveDocument.recompute() - Gui.SendMsgToActiveView("ViewFit") - - - bodies = getBodies(self.doc) - - - intersections = self.check_intersections(bodies) - self.print_intersections(intersections) - - - zero_volume_bodies = self.check_zero_volume(bodies) - self.print_zero_volume(zero_volume_bodies) - - - self.save_checked_document(file_path) - - - def getBodies(doc): - bodies = [] - for obj in doc.Objects: - if hasattr(obj, 'TypeId') and obj.TypeId == "Part::Feature": - bodies.append(obj) - return bodies - - - - def check_intersections(self, bodies): - intersections = [] - for i in range(len(bodies)): - for j in range(i + 1, len(bodies)): - if bodies[i].intersects(bodies[j]): - intersections.append((i + 1, j + 1)) - return intersections - - def check_zero_volume(self, bodies): - zero_volume_bodies = [i + 1 for i, body in enumerate(bodies) if body.Volume == 0] - return zero_volume_bodies - - def print_intersections(self, intersections): - for i, j in intersections: - print("Тела пересекаются: Body {} и Body {}".format(i, j)) - - def print_zero_volume(self, zero_volume_bodies): - for i in zero_volume_bodies: - print("Тело {} имеет нулевой объем".format(i)) - - def save_checked_document(self, original_file_path): - checked_file_path = original_file_path.replace(".step", "_checked.FCStd") - FreeCAD.saveDocument(self.doc, checked_file_path) - print("Проверенная сборка сохранена в файл:", checked_file_path) - FreeCAD.closeDocument("Unnamed") - - - -use_case = GeometryValidateUseCase() -step_file_path = '/path/to/file' -use_case.import_step_and_check(step_file_path) - diff --git a/freecad_workbench/freecad/robossembler/init_gui.py b/freecad_workbench/freecad/robossembler/init_gui.py index 5cdbc87..ef93952 100644 --- a/freecad_workbench/freecad/robossembler/init_gui.py +++ b/freecad_workbench/freecad/robossembler/init_gui.py @@ -49,13 +49,7 @@ class Robossembler(Gui.Workbench): "Export_Entities", "ExportGazeboModels", "InsertGraspPose", - # "ASM4StructureParsing", - "PDDL_CreateTypes", - "PDDL_CreateParameters", - "PDDL_CreateAction", - "PDDL_CreatePredicate", - "PDDL_CreateDurativeAction", - + "Validate_Project", "Publish_Project" ] self.appendToolbar(f"{__class__.__name__} Frames", self.framecommands) diff --git a/freecad_workbench/freecad/robossembler/is_object_solid.py b/freecad_workbench/freecad/robossembler/is_object_solid.py deleted file mode 100644 index 1d69d3a..0000000 --- a/freecad_workbench/freecad/robossembler/is_object_solid.py +++ /dev/null @@ -1,29 +0,0 @@ -# coding: utf-8 -# Copyright (C) 2023 Ilia Kurochkin -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -''' -DESCRIPTION. -Simple FreeCAD's object test for manifold mawater-tight surface. -''' - -import FreeCAD - - -def is_object_solid(obj): - '''If obj is solid return True''' - if not isinstance(obj, FreeCAD.DocumentObject): - return False - - if not hasattr(obj, 'Shape'): - return False - - return obj.Shape.isClosed() diff --git a/freecad_workbench/freecad/robossembler/markupEntities.py b/freecad_workbench/freecad/robossembler/markup_entities.py similarity index 100% rename from freecad_workbench/freecad/robossembler/markupEntities.py rename to freecad_workbench/freecad/robossembler/markup_entities.py diff --git a/freecad_workbench/freecad/robossembler/poseGenerator.py b/freecad_workbench/freecad/robossembler/pose_generator.py similarity index 100% rename from freecad_workbench/freecad/robossembler/poseGenerator.py rename to freecad_workbench/freecad/robossembler/pose_generator.py diff --git a/freecad_workbench/freecad/robossembler/project_validator.py b/freecad_workbench/freecad/robossembler/project_validator.py new file mode 100644 index 0000000..e9913f1 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/project_validator.py @@ -0,0 +1,184 @@ +import FreeCAD as App +import unicodedata +import numpy as np +from .helper.is_solid import is_object_solid + +## Программа, содержащая все проверки активного проекта FreeCAD в соответствии с issue#149 + +doc = App.ActiveDocument +Console = App.Console + + +#========== Проверка имен =============== +class FormalValidator: + def __init__(self, document): + self.document = document + + def is_unicode_safe(self, name): + normalized_name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode() + return name == normalized_name + + def fix_invalid_names(self): + for obj in self.document.Objects: + if not self.is_unicode_safe(obj.Label): + safe_name = unicodedata.normalize('NFKD', obj.Label).encode('ASCII', 'ignore').decode() + obj.Label = safe_name + self.document.recompute() + + #запуск проверки и исправления имен файлов + def validate(self): + invalid_names = [obj.Label for obj in self.document.Objects if not self.is_unicode_safe(obj.Label)] + if invalid_names: + print("Некорректные имена:") + for name in invalid_names: + print(name) + self.fix_invalid_names() + else: + print("Все имена корректны.") + + +class ModelValidator: + def __init__(self, document): + self.document = document + self.partlist = [obj for obj in self.document.Objects if (is_object_solid(obj) and not obj.TypeId == 'App::Part') ] + self.material_objects = [obj for obj in document.Objects if obj.TypeId == "App::MaterialObjectPython"] + + def get_parts(self): + pass + + + + def validate_geometry(self): + for obj in self.document.Objects: + if hasattr(obj, "Shape") and not is_object_solid(obj): + Console.PrintError(f"Объект '{obj.Name}' не является твердым телом.\n") + + def is_intersecting(self, obj1, obj2): + if obj1.Shape.common(obj2.Shape).Volume > 0.0: + return True + + def has_allow_intersections(self, obj): + if hasattr(obj, 'Base_Allowed_Intersection') and getattr(obj, 'Base_Allowed_Intersection', True): + return True + + def check_bodies_for_intersections(self): + bodies = self.partlist + num_bodies = len(bodies) + + for i in range(num_bodies): + for j in range(i + 1, num_bodies): + body1 = bodies[i] + body2 = bodies[j] + if self.is_intersecting(body1, body2): + if self.has_allow_intersections(body1) or self.has_allow_intersections(body2): + Console.PrintWarning( + f"Тела '{body1.Label}' и '{body2.Label}' ""предусмотренно пересекаются.\n") + else: + Console.PrintError( + f"Тела '{body1.Label}' и '{body2.Label}' ""непредусмотренно пересекаются.\n") + print('V = ' + str(body1.Shape.common(body2.Shape).Volume)) + + + def get_adjacency_matrix(self, padding = 1): + # print(int(padding)) + bodies = self.partlist + num_bodies = len(bodies) + adjacency_matrix = np.zeros((num_bodies, num_bodies), dtype=bool) + + for i in range(num_bodies): + shape1 = bodies[i].Shape + for j in range(i + 1, num_bodies): + shape2 = bodies[j].Shape + + if shape1.common(shape2).Volume > 0: + continue + + distance = shape1.distToShape(shape2) + if distance[0] < padding: + adjacency_matrix[i][j] = 1 + adjacency_matrix[j][i] = 1 + + return adjacency_matrix + + def check_bodies_without_contacts(self): + adjacency_matrix = self.get_adjacency_matrix() + bodies = self.partlist + + isolated_bodies = [] + num_bodies = len(bodies) + + for i in range(num_bodies): + # Проверяем, есть ли у тела касания с другими телами + if not any(adjacency_matrix[i]): + isolated_bodies.append(bodies[i].Label) + + if isolated_bodies: + App.Console.PrintWarning("Следующие тела не касаются других:\n") + for label in isolated_bodies: + App.Console.PrintWarning(f"- {label}\n") + else: + App.Console.PrintMessage("Все тела имеют контакты.\n") + + + def find_material_objects(document): + pass + + + + def check_bodies_with_multiple_materials(self): + material_objects = self.material_objects + material_references = {} + + for material in material_objects: + if hasattr(material, "References"): + for reference in material.References: + if reference in material_references: + material_references[reference].append(material.Label) + else: + material_references[reference] = [material.Label] + + conflicts = {k: v for k, v in material_references.items() if len(v) > 1} + + if conflicts: + App.Console.PrintWarning("Обнаружены конфликты между объектами материалов:\n") + for ref, materials in conflicts.items(): + App.Console.PrintWarning(f"Деталь '{ref[0].Label}' используется в материалах: {', '.join(materials)}\n") + else: + App.Console.PrintMessage("Конфликтов в ссылках материалов не обнаружено.\n") + + def check_bodies_without_material(self): + material_objects = self.material_objects + partlist = self.partlist + referenced_bodies = [] + for material in material_objects: + if hasattr(material, "References"): + for reference in material.References: + referenced_bodies.append(reference[0].Label) + + bodies_without_material = [] + for part in partlist: + if part.Label not in referenced_bodies: + bodies_without_material.append(part.Label) + + if bodies_without_material: + App.Console.PrintWarning("Следующие тела не имеют назначенного материала:\n") + for name in bodies_without_material: + App.Console.PrintWarning(f"- {name}\n") + else: + App.Console.PrintMessage("Все тела имеют назначенные материалы.\n") + + + + def validate(self): + self.validate_geometry() + self.check_bodies_for_intersections() + self.check_bodies_without_contacts() + self.check_bodies_without_material() + self.check_bodies_with_multiple_materials() + +def validate_project(): + doc = App.ActiveDocument + FormalValidator(doc).validate() + ModelValidator(doc).validate() + + diff --git a/freecad_workbench/freecad/update_workbench.sh b/freecad_workbench/freecad/update_workbench.sh new file mode 100755 index 0000000..3ac11df --- /dev/null +++ b/freecad_workbench/freecad/update_workbench.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Укажите путь к папке, которую нужно скопировать +SOURCE_DIR="/home/markvoltov/GitProjects/framework/freecad_workbench/freecad/robossembler" + +# Укажите путь к папке, которую нужно заменить +DEST_DIR="/home/markvoltov/.local/share/FreeCAD/Mod/freecad_workbench" + +# Проверка, что исходная папка существует +if [ ! -d "$SOURCE_DIR" ]; then + echo "Исходная папка не существует: $SOURCE_DIR" + exit 1 +fi + +# Удаление содержимого папки назначения +if [ -d "$DEST_DIR" ]; then + echo "Удаление содержимого папки назначения: $DEST_DIR" + rm -rf "$DEST_DIR/*" +else + # Создание папки назначения, если её нет + echo "Создание папки назначения: $DEST_DIR" + mkdir -p "$DEST_DIR" +fi + +# Копирование содержимого исходной папки в папку назначения +echo "Копирование содержимого из $SOURCE_DIR в $DEST_DIR" +cp -r "$SOURCE_DIR/." "$DEST_DIR/" + +echo "Копирование завершено" diff --git a/test_models/test_reductor.20240510-053606.FCBak b/test_models/test_reductor.20240510-053606.FCBak new file mode 100644 index 0000000000000000000000000000000000000000..b57c1a1cd4db2119645cc6ec41ebdbe6269f40e6 GIT binary patch literal 66630 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

K9r?X}A4sRP^@qm~!I#ylW=eY2NX_GEN?pw_Tfh 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 G{Q&?I>~1>% literal 0 HcmV?d00001 diff --git a/test_models/test_reductor.FCStd b/test_models/test_reductor.FCStd index 970e6756494eb5e130ceb99a7df84acdfb11e1d8..302c1f34ed220630546a5d224f256f89da728e70 100644 GIT binary patch literal 66728 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%0DxEket7Zcap+>F3s>~W@lPvP0)Y3 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