// Scripting by Oleg Okhotnikov, contact - svoboda200786@gmail.com
let legend_back;
let active_obj;
let move_flag = false;
let drawing_object = false;
let deltaX = 0;
let deltaY = 0;
let borders;
let point;
let min_val = 0.95;
let shape_change_flag = false;
let katet_min_length = 10;
let stick_array = [];
var text_editing; // показывает, есть ли активный текстовый элемент внутри составного объекта,
// иначе клик по полю снимает активность текстового элемента
// "Составные" объекты состоят из нескольких примитивов и в отличие от групп позволяют
// перемещать подобъекты по отдельности, при этом сохраняя целостность фигуры
// Создаем составные объекты, линия (line), размерность (size), выноска (cap), розетка (socket) и т.д.:
let btn_stick = document.querySelector("#stick");
let arrow_middle_pointX = 16;
function add_line(e, obj, ispart=false, x1=old_arrow_X, y1=old_arrow_Y, x2=canvas.getPointer(e.e).x, y2=canvas.getPointer(e.e).y, flag=true, name="line", stroke = fieldcolor, line_type="line"){      
    var line,
    arrow,
    arrow2;
    if(!ispart){ // не подобъект, как например, стрелки у розетки. У каждого составного объекта
    // есть родительский подобъект, обычно линия,
    // (object.parent), который записывается в массив всех фигур (shape)    
        index++;
        
        if(obj != undefined){ 
            for(let i in obj){                
                obj[i].groupName = /[a-zA-Z\_]+/.exec(obj[i].groupName) + index; // перезаписываем groupName, добавляя новый индекс
            }
        }        
    }

    if(obj != undefined){ // значит мы записываем в объект line линию, которую вернул канвас в start.js
        line = obj.line; // первый элемент составного объекта
        line.index = index;
    }
    else{ // создаем новый объект line
        line = drawArrowLine([x1, y1, x2, y2], stroke, index, name, calcLineLength({x1: x1, y1: y1, x2: x2, y2: y2}), line_type);
    }

    if(!ispart){ // если стрелка не часть составного объекта вроде socket, то
        shape[index] = line; // делаем элемент line ключевым - при его (line) перемещении,
        // все части составного объекта arrow двигаются за ним (то есть за line)          
    } 

    var centerX = (line.x1 + line.x2) / 2,
        centerY = (line.y1 + line.y2) / 2;
        deltaX = line.left - centerX,
        deltaY = line.top - centerY;
    if(obj != undefined){     
        arrow = obj.arrow;
        arrow.index = index;
    }
    else{
        switch(name){
            case "line":
                arrow = drawArrowCircle(line, deltaX, deltaY, flag, index, name, "arrow_start");
                break;
            case "arrow":
            case "size":
                arrow = drawArrowPoly(x2, y2, x1, y1, deltaX, deltaY, line, stroke, index, name, "arrow_start");
                break;
            case "socket":
                arrow = drawArrowPoly(x1, y1, x2, y2, deltaX, deltaY, line, stroke, index, name, "arrow_start");
                break;                                
        } 
    }
    if(!ispart){
        if(obj != undefined){
            arrow2 = obj.arrow2;
            arrow2.index = index;
        }
        else{
            switch(name){
                case "line":
                case "arrow":
                    arrow2 = drawArrowCircle(line, deltaX, deltaY, flag, index, name, "arrow_end");
                    break;
                case "size":
                    arrow2 = drawArrowPoly(x1, y1, x2, y2, deltaX, deltaY, line, stroke, index, name, "arrow_end", -1);
                    break;                
            }     
        }
    }
    arrow.line = line.line = line;    
    line.arrow = arrow.arrow = arrow;
    // свойство parent обозначает родительский подобъект или, иначе говоря, объект,
    // к которому привязаны остальные подобъекты составного объекта   
    line.parent = arrow.parent = line;
    line.parent.objects = ['line', 'arrow', 'arrow2'];
    
    if(!ispart){ // у линий внутри составных объектов нет стрелок
        arrow2.line = arrow.line;
        arrow2.arrow = arrow.arrow;
        line.arrow2 = arrow.arrow2 = arrow2.arrow2 = arrow2;
        arrow2.parent = arrow.parent;
        arrow2.perPixelTargetFind = false;  

        if(name == "line"){
            arrow.stroke_colorable = false;         
        }
        arrow2.stroke_colorable = false;       
    }   

    arrow.perPixelTargetFind = false; 

    if(!window.add_geometry_flag && name != "socket"){
        // stick(e, line.arrow2);        
    }

    if(obj != undefined){
        line.parent.added = true;
    }

    if(obj){
        var groupType = /[a-zA-Z\_]+/.exec(obj.line.groupName);

        let key = obj.line.groupName;
        if(!stick_array[key]){
            stick_array[key] = {};
            if(obj.arrow2 && groupType != "geometry"){
                // let param = text2Angle(obj.arrow.get('left'), obj.arrow.get('top'), obj.arrow2.get('left'), obj.arrow2.get('top'));            
                // stick_array[key].x0 = param.x;
                // stick_array[key].y0 = param.y;            
                // stick_array[key].x1 = obj.arrow.get('left');
                // stick_array[key].y1 = obj.arrow.get('top');
                // stick_array[key].x2 = obj.arrow2.get('left');
                // stick_array[key].y2 = obj.arrow2.get('top'); 
                // console.log(groupType);
                moveEnd({pointer: {x: obj.line.parent.arrow.left, y: obj.line.parent.arrow.top}}, obj.line.parent.arrow);                     
                // moveEnd({pointer: {x: obj.line.parent.arrow2.left, y: obj.line.parent.arrow2.top}}, obj.line.parent.arrow2);                           
            
            }
            else{
                if(groupType == "geometry"){
                    setTimeout(function(){ // свойство parent поменятеся, поэтоу вызываем таймаут
                        obj.parent = shape[obj.line.index];

                        let param = text2Angle(obj.line.get('x1'), obj.line.get('y1'), obj.line.get('x2'), obj.line.get('y2'));
                        x0 = stick_array[key].x0 = param.x;
                        y0 = stick_array[key].y0 = param.y;

                        x1 = stick_array[key].x1 = obj.line.get('x1');
                        y1 = stick_array[key].y1 = obj.line.get('y1');
                        x2 = stick_array[key].x2 = obj.line.get('x2');
                        y2 = stick_array[key].y2 = obj.line.get('y2');
                        x3 = stick_array[key].x3 = obj.parent.line_part1.get('x1');
                        y3 = stick_array[key].y3 = obj.parent.line_part1.get('y1');
                        x4 = stick_array[key].x4 = obj.parent.line_part2.get('x1');
                        y4 = stick_array[key].y4 = obj.parent.line_part2.get('y1');
                        param = text2Angle(obj.parent.line_part1.get('x1'), obj.parent.line_part1.get('y1'), obj.parent.line.get('x1'), obj.parent.line.get('y1'));
                        x5 = stick_array[key].x5 = param.x;
                        y5 = stick_array[key].y5 = param.y;
                        param = text2Angle(obj.parent.line_part2.get('x1'), obj.parent.line_part2.get('y1'), obj.parent.line.get('x2'), obj.parent.line.get('y2'));
                        x6 = stick_array[key].x6 = param.x;
                        y6 = stick_array[key].y6 = param.y;

                        // var t = canvas.viewportTransform;

                        // canvas.contextTop.fillRect(t[0]*x0+t[4], t[3]*y0+t[5], 10, 10)
                        // canvas.contextTop.fillRect(t[0]*x1+t[4], t[3]*y1+t[5], 10, 10)              
                        // canvas.contextTop.fillRect(t[0]*x2+t[4], t[3]*y2+t[5], 10, 10)  
                        // canvas.contextTop.fillRect(t[0]*x3+t[4], t[3]*y3+t[5], 10, 10)              
                        // canvas.contextTop.fillRect(t[0]*x4+t[4], t[3]*y4+t[5], 10, 10)
                        // canvas.contextTop.fillRect(t[0]*x5+t[4], t[3]*y5+t[5], 10, 10)              
                        // canvas.contextTop.fillRect(t[0]*x6+t[4], t[3]*y6+t[5], 10, 10)   

                    }, 10);                 
                }
                else{
                    setTimeout(function(){
                        moveEnd({pointer: {x: obj.line.parent.arrow_part1.left, y: obj.line.parent.arrow_part1.top}}, obj.line.parent.arrow_part1);                     
                        moveEnd({pointer: {x: obj.line.parent.arrow_part2.left, y: obj.line.parent.arrow_part2.top}}, obj.line.parent.arrow_part2);                   
                    }, 10);                 
                }
            }              
        }        
        
  
    }

    line.on('mousedown', function (e) {
        if(this.name != "line_part1" && this.name != "line_part2" || this.groupType == "geometry"){
            if(this.groupType == "size"){
                this.line.x1 = this.arrow.left;
                this.line.y1 = this.arrow.top;
                this.line.x2 = this.arrow2.left;
                this.line.y2 = this.arrow2.top;                       
            }      
            this.first_center = {
                x: line.x1 + (line.x2 - line.x1) / 2,
                y: line.y1 + (line.y2 - line.y1) / 2
            };
            this.first_click = {
                x: canvas.getPointer(e.e).x,
                y: canvas.getPointer(e.e).y
            };
            this.delta_between_arrow_and_click = {
                x: this.parent.arrow.get("left") - canvas.getPointer(e.e).x,
                y: this.parent.arrow.get("top") - canvas.getPointer(e.e).y
            }; 
        }   
    })
    arrow.on('mousedown', function (e) {
        shape_change_flag = true;
        hideLens();
        if(this.parent.line_part1 != undefined){
            this.parent.line_part1.first_click = {
                x: canvas.getPointer(e.e).x,
                y: canvas.getPointer(e.e).y
            };
        }
    })   
    if(arrow2 != undefined){ 
        shape_change_flag = true;             
        arrow2.on('mousedown', function (e) {        
            hideLens();           
            shape_change_flag = true;
            if(this.parent.line_part2 != undefined){
                this.parent.line_part2.first_click = {
                    x: canvas.getPointer(e.e).x,
                    y: canvas.getPointer(e.e).y
                };
            }
        })              
    }
  
    if(obj === undefined){
        if(!ispart){
            canvas.add(line, arrow, arrow2);
            canvas.setActiveObject(arrow);           
        } 
    }
    canvas.bringToFront(arrow).renderAll();
    line.on('mouseover', function () {
        canvas.bringToFront(arrow).renderAll();
    })    
    // canvasEvents();
    setTimeout(function(){
        arrow.setCoords();
        if(arrow2){
            arrow2.setCoords();            
        }       
    }, 10);   
    return {line: line, arrow: arrow, arrow2: arrow2};        
}

function canvasEvents(){
    canvas.on('mouse:down', function (e) {
        let obj = canvas.getActiveObject();

        if(obj && obj.parent && obj.parent.groupType == "geometry"){
            
            obj.parent.line_part1.x1 = obj.parent.arrow_part1.get('left');
            obj.parent.line_part1.y1 = obj.parent.arrow_part1.get('top');
            obj.parent.line_part1.x2 = obj.parent.arrow.get('left');
            obj.parent.line_part1.y2 = obj.parent.arrow.get('top');

            obj.parent.line_part2.x1 = obj.parent.arrow_part2.get('left');
            obj.parent.line_part2.y1 = obj.parent.arrow_part2.get('top');
            obj.parent.line_part2.x2 = obj.parent.arrow2.get('left');
            obj.parent.line_part2.y2 = obj.parent.arrow2.get('top');

            obj.parent.line.x1 = obj.parent.arrow.get('left');
            obj.parent.line.y1 = obj.parent.arrow.get('top');
            obj.parent.line.x2 = obj.parent.arrow2.get('left');
            obj.parent.line.y2 = obj.parent.arrow2.get('top');

            obj.oldX = obj.parent.line_part1.x2
            obj.oldY = obj.parent.line_part1.y2
        }
    }) 
    
    canvas.on('mouse:move', function (e) {
        let obj = canvas.getActiveObject();

        if(obj && obj.parent){
            if(obj != undefined && obj.type != "activeSelection" && obj.parent.name != "rect"){
                if(obj.pointType == "arrow_start" || obj.pointType == "arrow_end"){
                    obj.parent.line.length = calcLineLength({x1: obj.parent.arrow.left, y1: obj.parent.arrow.top, x2: obj.parent.arrow2.left, y2: obj.parent.arrow2.top});
                    if (obj.name == "arrow_part1" || obj.name == "arrow_part2"){
                        obj.parent.line.length = calcLineLength({x1: obj.parent.arrow_part1.left, y1: obj.parent.arrow_part1.top, x2: obj.parent.arrow_part2.left, y2: obj.parent.arrow_part2.top});
                    }             
                }
                if (obj.name == "line_part1" || obj.name == "line_part2"){
                    obj.parent.line.length = calcLineLength({x1: obj.parent.line_part1.left, y1: obj.parent.line_part1.top, x2: obj.parent.line_part2.left, y2: obj.parent.line_part2.top});              
                }

                if(obj.parent.added && obj.parent.arrow){
                    if(obj.parent.groupType != "cap"){
                        line_min_length = obj.parent.arrow.width * obj.parent.arrow.scaleX;
                    }
                    else{
                        line_min_length = 10;
                    }                
                }

                if(obj.parent.line != undefined){                
                    if(obj.parent.line.length >= line_min_length || obj.parent.groupType == "geometry"){                
                        if(obj.pointType == "arrow_start" && shape_change_flag){
                            moveEnd(e, obj);          
                        }
                        if(obj.pointType == "arrow_end" && shape_change_flag){
                            moveEnd(e, obj);            
                        }
                        if(obj.type == "line" && !obj.ispart && (geometry_obj || mouse_down_flag) ){
                            moveLine(e, obj); 
                            window.geometry_selected = false;                                                                    
                        }                                 
                    }
                    else{
                        if(obj.pointType == "arrow_start"){
                            if (obj.name == "arrow_part1"){
                                obj.set({
                                    left: obj.parent.line_part1.x1,
                                    top: obj.parent.line_part1.y1,                        
                                })                          
                            }
                            else if (obj.name == "arrow_part2"){
                                obj.set({
                                    left: obj.parent.line_part2.x1,
                                    top: obj.parent.line_part2.y1,                        
                                })                         
                            }
                            else{
                                obj.set({
                                    left: obj.parent.line.x1,
                                    top: obj.parent.line.y1,                        
                                })                         
                            }                    
                        }
                        if(obj.pointType == "arrow_end"){
                            obj.set({
                                left: obj.parent.line.x2,
                                top: obj.parent.line.y2,                        
                            })                
                        }
                        if(obj.type == "line"){
                            if(obj.name == "line_part1"){
                                obj.set({
                                    x1: obj.parent.arrow_part1.left,
                                    y1: obj.parent.arrow_part1.top,
                                    x2: obj.parent.line.x1,
                                    y2: obj.parent.line.y1                                                                            
                                })                     
                            }
                            if(obj.name == "line_part2"){
                                obj.set({
                                    x1: obj.parent.arrow_part2.left,
                                    y1: obj.parent.arrow_part2.top,                             
                                    x2: obj.parent.line.x2,
                                    y2: obj.parent.line.y2         
                                })                         
                            }        
                        }  
                        // canvas.renderAll();                
                    }                 
                }         
            
            } 
            else if(obj != undefined && obj.parent != undefined && obj.parent.name == "rect" && shape_change_flag){
                if (obj.name == "arrow_part1"){
                    obj.parent.line_part1.length = calcLineLength({x1: obj.parent.arrow_part1.left, y1: obj.parent.arrow_part1.top, x2: obj.parent.line_part1.x2, y2: obj.parent.line_part1.y2});
                } 
                if (obj.name == "arrow_part2"){
                    obj.parent.line_part2.length = calcLineLength({x1: obj.parent.arrow_part2.left, y1: obj.parent.arrow_part2.top, x2: obj.parent.line_part2.x2, y2: obj.parent.line_part2.y2});
                }             
                if(obj.pointType == "arrow_start"){
                    moveEnd(e, obj);             
                }
                // if(obj.pointType == "arrow_end"){
                //     moveEnd(e, obj);             
                // }
            }
        }  
    }) 
}

function drawArrowLine(array, stroke, index, name, length, line_type){
    let line = new fabric.Line(array, {
            stroke: stroke,
            selectable: true,            
            hasBorders: false,
            hasControls: false,
            fill: 'rgba(0,0,0,0)',
            originX: 'center',
            originY: 'center',
            lockScalingX: true,
            lockScalingY: true,
            perPixelTargetFind: true,
            length: length,
            name: 'line',
            groupName: name+index,
            index: index
        });
        if(line_type == "geometry_line_part1" || line_type == "geometry_line_part2"){
            setTimeout(function(){
                line.strokeWidth = selectStrokeWidth("geometry", line_type);  
            }, 10);  
        }
        else{
            line.strokeWidth = selectStrokeWidth(name, line_type);            
        }
    return line;
}
function drawArrowPoly(x1, y1, x2, y2, deltaX, deltaY, line, stroke, index, name, pointType, direction = 1){
    if(direction == 1){
        var arrow = new fabric.Path('M 0,2.5 13,-6.31861e-7 9.19811,2.5 13,5 Z');
    }
    else{
        var arrow = new fabric.Path('M 13,2.5 0,-6.31861e-7 3.80189,2.5 0,5 Z');        
    }
    arrow.set({
            left: x1 + deltaX,
            top: y1 + deltaY,
            angle: calcArrowAngle(x1, y1, x2, y2),
            originX: 'left', // стрелка изначально имеет горизонтальную ориентацию, поэтому острие слева посередине
            originY: 'center',
            hasBorders: false,
            hasControls: false,
            lockScalingX: true,
            lockScalingY: true,
            centeredRotation: true,
            centeredScaling: true,            
            centeredScaling: true,
            fill: fieldcolor,
            strokeUniform: true,
            stroke: stroke,
            strokeWidth: 0,
            pointType: pointType,           
            name: 'arrow',
            groupName: name + index,
            index: index
        });
    scaleArrow(arrow);
    if(pointType == "arrow_end"){
        arrow.set({
            name: 'arrow2',
            originX: 'right',
            angle: calcArrowAngle(x2, y2, x1, y1),
        }); 
    }
    return arrow;  
}
function drawArrowCircle(line, deltaX, deltaY, flag, index, name, pointType){
    let arrow = new fabric.Circle({
        left: line.get('x1'),
        top: line.get('y1'),
        radius: 5,
        stroke: 'rgba(0, 0, 0, 0)',
        strokeWidth: 1,
        selectable: flag,
        originX: 'center',
        originY: 'center',
        hasBorders: false,
        hasControls: false,
        lockScalingX: true,
        lockScalingY: true,
        lockRotation: true,
        pointType: pointType,
        fill: 'rgba(0, 0, 0, 0)',
        perPixelTargetFind: true,
        name: 'arrow',
        groupName: name + index,
        index: index
    })
    if(pointType == "arrow_end"){
        arrow.name = "arrow2";
    }
    return arrow;
}
function drawArrowText(text, left, top, angle, originX, originY, index, name, groupName, flag){
    let text2 = new fabric.IText(text, {
            fontSize: text_dimension[zoom_size],
            // fontWeight: 100 + 100 * stroke_width,
            originX: originX,
            originY: originY,
            fontFamily: "Golos Text",
            fill: "black",
            backgroundColor: 'rgba(0,0,0,0)',
            hasBorders: false,
            hasControls: false,
            lockScalingX: true,
            lockScalingY: true,
            lockRotation: true,
            lockMovementX: flag,
            lockMovementY: flag,
            left: left,
            top: top,
            angle: angle,
            fill: fieldcolor,
            textAlign: 'center',
            name: name,
            groupName: groupName+index,
            index: index
        })
    return text2;
}
function drawSocketRect(x, y, index, name){
    var rect = new fabric.Path("M 0 0 L 0 18.484375 L 18.160156 18.484375 L 18.160156 0 L 0 0 z M 9.0800781 2.4316406 A 6.8098435 6.8098435 0 0 1 15.888672 9.2421875 A 6.8098435 6.8098435 0 0 1 9.0800781 16.052734 A 6.8098435 6.8098435 0 0 1 2.2695312 9.2421875 A 6.8098435 6.8098435 0 0 1 9.0800781 2.4316406 z ");        
    rect.set({
            left: x,
            top: y,
            originX: 'center',
            originY: 'center',
            hasBorders: false,
            hasControls: false,
            lockScalingX: true,
            lockScalingY: true,
            centeredRotation: true,
            centeredScaling: true,            
            centeredScaling: true,
            fill: fieldcolor,
            strokeUniform: true,
            strokeWidth: 0,          
            name: "rect_back",
            groupName: name + index,
            index: index
        });
    return rect;  
}
function add_arrow(e, obj, ispart=false, x1=old_arrow_X, y1=old_arrow_Y, x2=canvas.getPointer(e.e).x, y2=canvas.getPointer(e.e).y, flag=true, name="arrow", stroke = fieldcolor){
    return add_line(e, obj, ispart, x1, y1, x2, y2, flag, name, stroke);      
}

//////////////////////////// Size //////////////////////////////

function add_size(e, obj, ispart=false, x1=old_arrow_X, y1=old_arrow_Y, x2=canvas.getPointer(e.e).x, y2=canvas.getPointer(e.e).y, flag=true, name="size", stroke = fieldcolor){ 
    let temp = add_line(e, obj, ispart, x1, y1, x2, y2, flag, name, stroke); 
    let line = temp.line;
    let arrow = temp.arrow;
    let arrow2 = temp.arrow2; 

    if(obj != undefined){
        text2 = obj.text2;
        text2.index = index;
    }
    else{
        let left = text2Angle(line.x1, line.y1, line.x2, line.y2).x - 20;
        let top = text2Angle(line.x1, line.y1, line.x2, line.y2).y;
        let angle = text2Angle(line.x1, line.y1, line.x2, line.y2).ang;
        text2 = drawArrowText("000", left, top, angle, 'left', 'bottom', index, "text2", name, true);
    }   
    line.text2 = arrow.text2 = text2.text2 =  text2;
    line.text2.selectable = false;
    // свойство parent обозначает родительский подобъект или, иначе говоря, объект,
    // к которому привязаны остальные подобъекты составного объекта   
    line.parent = arrow.parent = text2.parent = line;
    if(!ispart){
        arrow2.text2 = arrow.text2;
        arrow2.parent = arrow.parent;
    }
    if(obj != undefined){
        setTimeout(function(){
            changeTextWidth(text2);
        }, 100);
        text2.on('changed', function () {
            changeTextWidth(text2);
        });
    }
    text2.on('changed', function () {
        if(this.width <= 2){
            this.width = 33;
        }
        if(this.name == "text2_part1"){
            var parent = this.parent.line_part1;
        }
        if(this.name == "text2_part2"){
            var parent = this.parent.line_part2;
        }
        if(this.name == "text2"){
            var parent = this.parent.line;
        }               

        // центрирование текста          
        textRotate(parent);
        this.setCoords();
    });
    text2.on('editing:entered', function () {
        text_editing = true;
    })    
    text2.on('editing:exited', function () {
        if(!editor_command){
            element_cursor(true);          
        }
        mouse_down_flag = false;
        this.initialized = true;
        if(this.name == "text2_part1"){
            if(!this.parent.line_part2.text2.initialized){
                this.parent.line_part2.text2.enterEditing();
                this.parent.line_part2.text2.hiddenTextarea.focus();
                this.parent.line_part2.text2.selectLine(1);
                let text_obj = this.parent.line_part2.text2;
                text_editing = true;
                setTimeout(function(){canvas.setActiveObject(text_obj);}, 10);                
            }
            else{
                text_editing = false;
            }
        }
        if(this.name == "text2_part2"){
            if(!this.parent.line_part1.text2.initialized){
                this.parent.line_part1.text2.enterEditing();
                this.parent.line_part1.text2.hiddenTextarea.focus();
                this.parent.line_part1.text2.selectLine(1);
                let text_obj = this.parent.line_part1.text2;
                text_editing = true;
                setTimeout(function(){canvas.setActiveObject(text_obj);}, 10);
            }
            else{
                text_editing = false;
            }
        }
        if(this.name == "text2"){
            text_editing = false;            
        }        
    });
    text2.on('mousedown:before', function (e) {
        this.first_click = {
            x: canvas.getPointer(e.e).x,
            y: canvas.getPointer(e.e).y
        };
        mouse_down_flag = true;
    }); 
    text2.on('mouseup', function (e) {
        mouse_down_flag = false;
        canvas.setActiveObject(this);
        if(!this.was_moved){
            this.enterEditing();            
        }
        this.was_moved = false;
        canvas.defaultCursor = null;
        canvas.defaultCursor_old = null;
    });         
    text2.on('mousemove', function (e) {
        if(mouse_down_flag){
            this.exitEditing();
            this.was_moved = true;
        }
        if(!this.isEditing){
            if(this.parent.arrow != undefined){
                var turn_angle = this.parent.arrow.angle;
            }
            else if(this == this.parent.text2){
                var turn_angle = this.parent.arrow_part2.angle;            
            }
            else{
                var turn_angle = this.parent.arrow_part1.angle; 
            }
            if((turn_angle >= 90 && turn_angle <= 135) || (turn_angle >= -90 &&  turn_angle <= -45))
                {  
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).x > this.first_click.x){
                    this.set('originY', 'top');   
                }
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).x < this.first_click.x){
                    this.set('originY', 'bottom');
                } 
            }
            else if((turn_angle >= 45 && turn_angle <= 90) || turn_angle == 270 || (turn_angle >= -135 && turn_angle <= -90))
                {                    
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).x > this.first_click.x){
                    this.set('originY', 'bottom');   
                }
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).x < this.first_click.x){
                    this.set('originY', 'top');
                } 
            }              
            else if(Math.ceil(turn_angle) == 0){
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).y > this.first_click.y){
                    this.set('originY', 'bottom');   
                }
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).y < this.first_click.y){
                    this.set('originY', 'top');
                } 
            }
            else{
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).y > this.first_click.y){
                    this.set('originY', 'top');   
                }
                if(mouse_down_flag && this.first_click != undefined && canvas.getPointer(e.e).y < this.first_click.y){
                    this.set('originY', 'bottom');
                }              
            }     
            canvas.renderAll();
        }
    })    
    if(obj === undefined && !ispart){
        canvas.add(text2);
        canvas.setActiveObject(arrow);             
    }
    line.parent.objects.push("text2");
    line.setCoords();
    arrow.setCoords();
    if(!ispart){
        arrow2.setCoords();
       return {line: line, arrow: arrow, arrow2: arrow2, text2: text2};  
    }
    else{
        return {line: line, arrow: arrow, text2: text2};         
    }                  
}

//////////////////////////// Geometry //////////////////////////////

function add_geometry(e, obj, ispart=false, x1=old_arrow_X, y1=old_arrow_Y, x2=canvas.getPointer(e.e).x, y2=canvas.getPointer(e.e).y, flag=true, name="size", stroke = fieldcolor){
    window.add_geometry_flag = true;
    let line_main, line_part1, line_part2;

    if(obj != undefined){ // значит мы создаем составной объект при загрузке документа из сохраненного файла              
        obj1 = {
            line: obj.line,
            arrow: obj.arrow,
            arrow2: obj.arrow2,            
            text2: obj.text2
        };
        line_main = editor_obj.add_size(e, obj1, false);
        obj2 = {
            line: obj.line_part1,
            arrow: obj.arrow_part1
        };
        line_part1 = editor_obj.add_line(e, obj2, true);
      
        obj3 = {
            line: obj.line_part2,
            arrow: obj.arrow_part2
        };
        line_part2 = editor_obj.add_line(e, obj3, true); 

        // Меняем координаты на глобальные
        line_main.line.x1 = line_main.line.left + line_main.line.x1;
        line_main.line.y1 = line_main.line.top + line_main.line.y1;
        line_main.line.x2 = line_main.line.left + line_main.line.x2;
        line_main.line.y2 = line_main.line.top+ line_main.line.y2;

        line_part1.line.x1 = line_part1.line.left + line_part1.line.x1;
        line_part1.line.y1 = line_part1.line.top + line_part1.line.y1;
        line_part1.line.x2 = line_part1.line.left + line_part1.line.x2;
        line_part1.line.y2 = line_part1.line.top+ line_part1.line.y2;

        line_part2.line.x1 = line_part2.line.left + line_part2.line.x1;
        line_part2.line.y1 = line_part2.line.top + line_part2.line.y1;
        line_part2.line.x2 = line_part2.line.left + line_part2.line.x2;
        line_part2.line.y2 = line_part2.line.top+ line_part2.line.y2;
               
        line_main.line.setCoords();
        line_part1.line.setCoords();
        line_part2.line.setCoords();                        
    }
    else{        
        line_main = editor_obj.add_size(e, undefined, false, x1, y1, x2, y2, false, "size");                       
        line_part1 = editor_obj.add_line(e, obj, true, line_main.arrow.left, line_main.arrow.top, line_main.arrow.left, line_main.arrow.top, flag, "line", fieldcolor, "geometry_line_part1");
        line_part2 = editor_obj.add_line(e, obj, true, line_main.arrow2.left, line_main.arrow2.top, line_main.arrow2.left, line_main.arrow2.top, flag, "line", fieldcolor, "geometry_line_part2");
    } 

    // var rect = editor_obj.add_rect(null, true, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, 18, 18, "rgba(0, 0, 0, 0)", fieldcolor, "socket", true);                         

    line_main.line.objects = ["arrow_part1",  "line_part1", "arrow_part2", "line_part2", "line", "arrow", "arrow2", "text2"];       
    
    line_part1.ispart = true;
    line_part2.ispart = true;
     
    line_main.line.line_part1 = line_part1.line;
    line_main.line.line_part2 = line_part2.line;
    line_main.line.arrow_part1 = line_part1.arrow;
    line_main.line.arrow_part2 = line_part2.arrow;
    line_main.line.arrow_part1.scale(0); // в текущей реализации верхушки боковых линий объекта не используются
    line_main.line.arrow_part2.scale(0); // в текущей реализации верхушки боковых линий объекта не используются    
   
    // свойство parent обозначает родительский подобъект или, иначе говоря, объект,
    // к которому привязаны остальные подобъекты составного объекта 
    line_main.line.parent = line_main.arrow.parent = line_main.text2.parent = line_main.line.line_part1.parent = line_main.line.line_part2.parent = line_main.line.arrow_part1.parent =
    line_main.line.line_part1.parent = line_main.line.arrow_part2.parent = line_main.line.line_part2.parent = line_main.line;
    
    line_main.line.parent.groupType = "geometry";
    
    if(obj != undefined){
        line_main.line.parent.added = true;
    }
          
    line_main.line.line_part1.name = "line_part1";        
    line_main.line.arrow_part1.name = "arrow_part1";
    line_main.line.line_part2.name = "line_part2";
    line_main.line.arrow_part2.name = "arrow_part2";

    for (i of line_main.line.parent.objects){
        line_main.line.parent[i].groupName = "geometry" + index; 
    }

    shape[index] = line_main.line.line;
    if(obj == undefined){       
        canvas.add(line_part1.line, line_part2.line);
    }
    // line_main.rect = rect;
    // window.rect2 = rect;
    // canvas.add(line_main.rect);
    // console.log(line_main.rect);

    line_main.line.line_part1.lockMovementX = true;
    line_main.line.line_part1.lockMovementY = true;        
    line_main.line.line_part2.lockMovementX = true;
    line_main.line.line_part2.lockMovementY = true;

    line_part1.line.strokeWidth = 1;
    line_part2.line.strokeWidth = 1;  

    line_main.line.arrow_part1.on('mousedown', function (e) {
        this.parent.line_part1.first_click = {
            x: canvas.getPointer(e.e).x,
            y: canvas.getPointer(e.e).y
        };
    })
    line_main.line.arrow_part2.on('mousedown', function (e) {
        this.parent.line_part2.first_click = {
            x: canvas.getPointer(e.e).x,
            y: canvas.getPointer(e.e).y
        };
    })          
    window.add_geometry_flag = false;
}

//////////////////////////// Caption //////////////////////////

function add_cap(e, obj, x1=old_arrow_X, y1=old_arrow_Y, x2=canvas.getPointer(e.e).x, y2=canvas.getPointer(e.e).y, stroke = fieldcolor, name="cap"){  
    index++;
    if(obj != undefined){ 
        for(let i in obj){                
            obj[i].groupName = /[a-zA-Z\_]+/.exec(obj[i].groupName) + index; // перезаписываем groupName, добавляя новый индекс
        }
    }
    var line,
    arrow,
    arrow2;

    if(obj != undefined){
        line = obj.line;
        line.index = index;       
    }
    else{
        line = drawArrowLine([x2, y2, x1, y1], stroke, index, name, calcLineLength({x1: x1, y1: y1, x2: x2, y2: y2})); 
    }   
    line.objects = ['line', 'arrow', 'arrow2'];
    shape[index] = line;
    var centerX = (line.x1 + line.x2) / 2,
        centerY = (line.y1 + line.y2) / 2;
    deltaX = line.left - centerX,
    deltaY = line.top - centerY;

    if(obj != undefined){
        arrow = obj.arrow;
        arrow.index = index;
    }
    else{
        arrow = drawArrowText("000", line.x2, line.y2, 0, 'right', 'bottom', index, "arrow", name, false);
    }

    if(obj != undefined){
        setTimeout(function(){
            changeTextWidth(arrow);
        }, 100);
        arrow.on('changed', function () {
            changeTextWidth(arrow);
        });
    }

    arrow.pointType = 'arrow_start';
    arrow.line = line;
    // arrow.line.strokeLineCap = 'round';

    if(obj != undefined){
        arrow2 = obj.arrow2;
        arrow2.index = index;
    }
    else{
        var diameter = line.strokeWidth * 3 + 1;  

        arrow2 = new fabric.Circle({
            left: line.get('x2') + deltaX,
            top: line.get('y2') + deltaY,
            radius: diameter / 2,
            stroke: stroke,
            strokeWidth: line.strokeWidth,
            originX: 'center',
            originY: 'center',
            hasBorders: true,
            hasControls: false,
            lockScalingX: true,
            lockScalingY: true,
            lockRotation: true,
            pointType: 'arrow_end',
            fill: 'rgba(255, 255, 255, 0)',
            perPixelTargetFind: true,
            name: "arrow2",
            groupName: name + index,
            index: index
        });
    }
    arrow2.line = line;
    arrow2.hasBorders = false;
    arrow2.colorable = false;

    line.line = arrow.line = arrow2.line = line;   
    line.arrow = arrow.arrow = arrow2.arrow = arrow;    
    line.arrow2 = arrow.arrow2 = arrow2.arrow2 = arrow2;
    line.text2 = arrow.text2 = arrow2.text2 = arrow;
    // свойство parent обозначает родительский подобъект или, иначе говоря, объект,
    // к которому привязаны остальные подобъекты составного объекта   
    line.parent = arrow2.parent = arrow.parent = line;

    stick(e, arrow2);

    if(obj != undefined){
        line.parent.added = true;
        moveEnd({pointer: {x: obj.line.parent.arrow.left, y: obj.line.parent.arrow.top}}, obj.line.parent.arrow);        
    }

    if(obj == undefined){
        canvas.add(line, arrow, arrow2);
        canvas.setActiveObject(arrow); 
    }
    shape_change_flag = true;
    arrow.on('mousedown', function (e) {
        shape_change_flag = true;
        hideLens()
    })
    arrow2.on('mousedown', function () {
        shape_change_flag = true;
        hideLens()              
    });    
    line.on('moving', function (e) {
        moveLine(e, line);
    });      
}
//////////////////////////// Socket ////////////////////////////
function add_socket(e, obj){
    // circle, small_circle1, small_circle2,
    let rect, rect_back, line_part1, line_part2;
    
    index++;
    if(obj != undefined){ 
        for(let i in obj){                
            obj[i].groupName = /[a-zA-Z\_]+/.exec(obj[i].groupName) + index; // перезаписываем groupName, добавляя новый индекс
        }
    }
    if(obj != undefined){ // значит мы создаем составной объект при загрузке документа из сохраненного файла              
        obj1 = {
            line: obj.line_part1,
            arrow: obj.arrow_part1,
            text2: obj.text2_part1
        };
        line_part1 = editor_obj.add_size(e, obj1, true);
        obj2 = {
            line: obj.line_part2,
            arrow: obj.arrow_part2,
            text2: obj.text2_part2
        };
        line_part2 = editor_obj.add_size(e, obj2, true);
        rect_back = obj.rect_back;        
        // circle = obj.circle;
        // small_circle1 = obj.small_circle1;
        // small_circle2 = obj.small_circle2;
        rect = obj.rect;               
    }
    else{
        line_part1 = editor_obj.add_size(null, undefined, true, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y+100, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, false, "socket");                       
        line_part2 = editor_obj.add_size(null, undefined, true, canvas.getPointer(e.e).x+100, canvas.getPointer(e.e).y, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, false, "socket");  
        // rect_back = editor_obj.add_rect(null, false, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, 18, 18, fieldcolor, fieldcolor, "socket", true); 
        rect_back = drawSocketRect(canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, index, "socket");         
        // circle = editor_obj.add_circle(null, true, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, 8, "rgba(255, 255, 255)", fieldcolor, "socket", true);    
        // small_circle1 = editor_obj.add_circle(null, true, canvas.getPointer(e.e).x - 4, canvas.getPointer(e.e).y, 1, fieldcolor, fieldcolor, "socket", true);         
        // small_circle2 = editor_obj.add_circle(null, true, canvas.getPointer(e.e).x + 4, canvas.getPointer(e.e).y, 1, fieldcolor, fieldcolor, "socket", true);                                     
        rect = editor_obj.add_rect(null, true, canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, 18, 18, "rgba(0, 0, 0, 0)", fieldcolor, "socket", true);                          
    }

    // circle.strokeWidth = 1;
    // small_circle1.strokeWidth = 1;
    // small_circle2.strokeWidth = 1;   
    rect.strokeWidth = 1;
    rect.index = index;
    rect.groupName = "socket" + index;
    rect.groupType = "socket";

    rect.lockScalingX = true;
    rect.lockScalingY = true;
    rect.hasControls = false; 
     // "circle", "small_circle1", "small_circle2",      
    rect.objects = ["rect_back", "arrow_part1",  "text2_part1",  "line_part1", "arrow_part2", "text2_part2", "line_part2", "rect"];        

    rect.perPixelTargetFind = false;
    rect_back.ispart = true;
    // circle.ispart = true;
    // small_circle1.ispart = true;
    // small_circle2.ispart = true;      
    line_part1.line.ispart = true;
    line_part2.line.ispart = true;
    line_part1.arrow.ispart = true;
    line_part2.arrow.ispart = true;
    line_part1.text2.ispart = true;
    line_part2.text2.ispart = true; 

    rect_back.colorable = true;
    // small_circle1.colorable = true;
    // small_circle2.colorable = true;
    rect.colorable = false;    
  
    rect.rect = rect; 
    rect.rect_back = rect_back; 
    rect.line_part1 = line_part1;
    rect.line_part2 = line_part2;
    rect.arrow_part1 = line_part1.arrow;
    rect.text2_part1 = line_part1.text2;
    rect.line_part1 = line_part1.line;
    rect.arrow_part2 = line_part2.arrow;
    rect.text2_part2 = line_part2.text2;
    rect.text2 = rect.text2_part2;
    rect.line_part2 = line_part2.line;
    // rect.circle = circle;
    // rect.small_circle1 = small_circle1;
    // rect.small_circle2 = small_circle2; 

    rect.arrow_part1.canvas = canvas; // для правильного расчета координат в getViewportTransform fabric.js
    rect.arrow_part2.canvas = canvas; // для правильного расчета координат в getViewportTransform fabric.js    

    // свойство parent обозначает родительский подобъект или, иначе говоря, объект,
    // к которому привязаны остальные подобъекты составного объекта 

     // = rect.circle.parent = rect.small_circle1.parent = rect.small_circle2.parent
    rect.rect.parent = rect.rect_back.parent = rect.line_part1.parent = rect.line_part2.parent = rect.arrow_part1.parent =
    rect.line_part1.parent = rect.text2_part1.parent = rect.arrow_part2.parent = rect.text2_part2.parent = rect.line_part2.parent = rect;
    if(obj != undefined){
        rect.parent.added = true;
    }
    rect.rect.name = "rect";
    // rect.rect_back.name = "rect_back";
    // rect.circle.name = "circle";    
    // rect.small_circle1.name = "small_circle1";
    // rect.small_circle2.name = "small_circle2";            
    rect.line_part1.name = "line_part1";        
    rect.arrow_part1.name = "arrow_part1";
    rect.text2_part1.name = "text2_part1";
    rect.line_part2.name = "line_part2";
    rect.arrow_part2.name = "arrow_part2";
    rect.text2_part2.name = "text2_part2"; 

    for(var i of rect.objects){
        rect[i].hasBorders = false;
        rect[i].hasControls = false;        
    }

    rect.line_part1.selectable = false; 
    rect.line_part2.selectable = false; 
    rect.text2_part1.selectable = false;
    rect.text2_part2.selectable = false;        
    
    shape[index] = rect.rect;

    if(obj == undefined){
        canvas.setActiveObject(shape[index]);
        moveEnd(e, line_part1.arrow);       
        moveEnd(e, line_part2.arrow);       
        canvas.add(line_part1.text2, line_part1.line, line_part2.text2, line_part2.line, rect.rect_back, rect);
    }

    // canvas.bringToFront(circle).renderAll();
    // canvas.bringToFront(small_circle1).renderAll();
    // canvas.bringToFront(small_circle2).renderAll();
    canvas.bringToFront(rect).renderAll();

    if(obj == undefined){    
        scaleSocket(shape[index]);
    }

    rect.on('moving', function () {
        let deltaX = rect.left - rect.oldX;
        let deltaY = rect.top - rect.oldY;           
        rect.oldX = rect.left;
        rect.oldY = rect.top;
        rect_back.left += deltaX;
        rect_back.top += deltaY;
        // circle.left += deltaX;
        // circle.top += deltaY;        
        // small_circle1.left += deltaX;
        // small_circle2.left += deltaX;        
        // small_circle1.top += deltaY;
        // small_circle2.top += deltaY; 
        line_part1.line.left += deltaX;
        line_part1.line.top += deltaY;
        line_part2.line.left += deltaX;
        line_part2.line.top += deltaY;          
        moveLine(e, line_part1.line);
        moveLine(e, line_part2.line);
    }) 
    rect.on('mousedown', function () {
        rect.oldX = rect.left;
        rect.oldY = rect.top;         
    })
    rect.line_part1.on('mouseover', function () {
        this.hoverCursor = "no-drop";       
    })
    rect.line_part2.on('mouseover', function () {
        this.hoverCursor = "no-drop";       
    })        
    // эти три обработчика нужны для копирования, потому что круги копируются поверх квадрата 
    // circle.on('mousedown:before', function () {
        // canvas.bringToFront(this.parent.rect).renderAll();        
    // })
    // small_circle1.on('mousedown:before', function () {
        // canvas.bringToFront(this.parent.rect).renderAll();        
    // })
    // small_circle2.on('mousedown:before', function () {
        // canvas.bringToFront(this.parent.rect).renderAll();        
    // })                       
}
//////////////////////////// Legend ////////////////////////////
function add_legend_back(obj){
    let rect;
    if(obj != undefined){
        rect = obj;
    }
    else{
        rect = editor_obj.add_rect(null, false, canvas.width - 210, canvas.height - 160, 200, 150, "#eee", "gray", "back", true); 
        canvas.add(rect);
        rect.rx = 10;
        rect.ry = 10;        
    }
        rect.originX = 'left';
        rect.originY = 'top';    
        rect.oldX = rect.left;
        rect.oldY = rect.top;
        rect.selectable = false;
        rect.hasControls = true;
        rect.lockRotation = true;
        rect.parent = rect;

        canvas.on('mouse:down', reCalculate);        
        rect.on('moving', reCalculate);
        rect.on('scaling', reCalculate);
        function reCalculate(e) {
            // не даем уменьшить легенду, если ее ширина меньше самого длинного айтема
            // if(rect.width * rect.scaleX < (legend_back.margin + biggest_legend_item_width) || canvas.getPointer(e.e).x < rect.left &&
            // (legend_back.__corner == "mr" || legend_back.__corner == "tr" || legend_back.__corner == "br" ) ){
               
            //////////// top - bottom ///////////////

            if( (rect.height * rect.scaleY < text_height || canvas.getPointer(e.e).y > rect.top + text_height) &&
            (legend_back.__corner == "mt" || legend_back.__corner == "tl" || legend_back.__corner == "tr" ) ){

                var t = canvas.viewportTransform; 

                rect.scaleY = text_height / legend_back.height;
                rect.top = Math.round((rect.oldrY - t[5]) / t[3]) - text_height - rect.strokeWidth;
            } 

            if( (rect.height * rect.scaleY < text_height || canvas.getPointer(e.e).y < rect.top) &&
            (legend_back.__corner == "mb" || legend_back.__corner == "bl" || legend_back.__corner == "br" ) ){
                rect.scaleY = text_height / legend_back.height;
                rect.top = rect.oldY;
            } 
            
            //////////// left - right ////////////

            if( (rect.width * rect.scaleX < (legend_back.margin + biggest_legend_item_width) || canvas.getPointer(e.e).x > rect.left + biggest_legend_item_width)
                && (legend_back.__corner == "ml" || legend_back.__corner == "tl" || legend_back.__corner == "bl" ) ){

                var t = canvas.viewportTransform; 

                rect.scaleX = 1; 
                rect.left = Math.round((rect.oldrX - t[4]) / t[0]) - rect.oldWidth - rect.strokeWidth;
               
                canvas.renderAll();                                                            
            }

            if( (rect.width * rect.scaleX < (legend_back.margin + biggest_legend_item_width) || canvas.getPointer(e.e).x < rect.left) &&
            (legend_back.__corner == "mr" || legend_back.__corner == "tr" || legend_back.__corner == "br" ) ){

                rect.scaleX = 1; 
                rect.left = rect.oldX;
               
                canvas.renderAll();                                                            
            }                                  


            canvas.bringToFront(rect).renderAll();
            let deltaX = rect.left - rect.oldX;
            let deltaY = rect.top - rect.oldY;

            rect.oldX = rect.left;
            rect.oldrX = rect.oCoords.tr.x;
   
            rect.oldY = rect.top;
            rect.oldrY = rect.oCoords.br.y;

            rect.oldWidth = rect.width; 

            let array = canvas.getObjects();
            for(var i in array){
                let shape_type = /legend.*/.test(array[i].groupName);
                if(shape_type){
                    array[i].left += deltaX;
                    array[i].top += deltaY;
                    canvas.bringToFront(array[i]);
                }
            }
            rect.rx = rect.border_radius * (1 / rect.scaleX); 
            rect.ry = rect.border_radius * (1 / rect.scaleY);                   
        }
        rect.on('mousedown', function (e) {
            canvas.preserveObjectStacking = true;
            rect.hasControls = true;
            canvas.setActiveObject(rect);     
        })
        rect.on('mouseover', function () {
            rect.selectable = true;          
        })
        rect.on('mouseout', function () {
            editor_obj.add_legend();         
        })        
        canvas.on('mouse:down:before', function (e) {
            canvas.preserveObjectStacking = true;
            let array = canvas.getObjects();
            for(var i in array){
                let shape_type = /legend.*/.test(array[i].groupName);
                if(shape_type && array[i].type == "i-text"
                    && canvas.getPointer(e.e).x > array[i].left && canvas.getPointer(e.e).x < array[i].left + array[i].width
                    && canvas.getPointer(e.e).y > array[i].top && canvas.getPointer(e.e).y < array[i].top + array[i].height){
                    
                    canvas.setActiveObject(array[i]);
                    array[i].enterEditing();            
                }
            }       
        })              
    legend_back = rect;
    legend_back.border_radius = 10;     
}
// Общие функции

function stick(e, obj){
    for(key in stick_array){
        if(obj.groupName != key){
            for(var n = 0; n <= Object.keys(stick_array[key]).length / 2; n++){
                if(obj.name != "line_part1" && obj.name != "line_part2"){
                    obj_x = obj.get('left');
                    obj_y = obj.get('top');
                }
                else{
                    obj_x = obj.get('x1');
                    obj_y = obj.get('y1');
                }
                let stick_distance_line = {
                    x1: obj_x,
                    y1: obj_y,
                    x2: stick_array[key]["x" + n],
                    y2: stick_array[key]["y" + n],                     
                };

                if(Math.abs(calcLineLength(stick_distance_line)) < 20){
                    var stick_x = stick_distance_line["x2"];
                    var stick_y = stick_distance_line["y2"];                     
                }
            }
        }
    }

    if(obj.name != "line_part1" && obj.name != "line_part2" ){
        if(obj.parent.name == "rect"){
            var opposite_point =obj.parent;
        }
        else{
            if(obj.pointType == "arrow_start"){
                var opposite_point = obj.parent.arrow2;
            }
            if(obj.pointType == "arrow_end"){
                var opposite_point = obj.parent.arrow;    
            }            
        }

        var angle = calcArrowAngle(canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, opposite_point.left, opposite_point.top, false);
        var gap = 5;
        var katet = obj.line.length;
        var cross = false;

        if(angle <= 0 + gap && angle >= 0 || angle >= 360 - gap && angle <= 360){
            cross = "horizontal";
            var stick_x = opposite_point.left - katet * Math.cos(0 * Math.PI / 180);                       
            var stick_y = opposite_point.top;
        } 

        if(angle <= 180 + gap && angle >= 180 - gap){
            cross = "horizontal";
            var stick_x = opposite_point.left - katet * Math.cos(180 * Math.PI / 180);                       
            var stick_y = opposite_point.top;
        } 

        if(angle <= 90 + gap && angle >= 90 - gap){
            cross = "vertical";
            var stick_x = opposite_point.left;                       
            var stick_y = opposite_point.top - katet * Math.sin(90 * Math.PI / 180);

        } 
        if(angle <= 270 + gap && angle >= 270 - gap){
            cross = "vertical";
            var stick_x = opposite_point.left;                       
            var stick_y = opposite_point.top - katet * Math.sin(270 * Math.PI / 180);
        }

        var ctx = canvas.contextTop;
        ctx.strokeStyle = "lime";
        ctx.beginPath();
        ctx.setLineDash([5, 5]);

        if(cross == "horizontal"){ 
            var transform = fabric.util.transformPoint(new fabric.Point(canvas.vptCoords.tr.x, stick_y), canvas.viewportTransform);   
            ctx.moveTo(transform.x, transform.y);
            ctx.lineTo(0, transform.y);
            ctx.stroke();
        }
        else if(cross == "vertical"){
            var transform = fabric.util.transformPoint(new fabric.Point(stick_x, canvas.vptCoords.br.y), canvas.viewportTransform);   
            ctx.moveTo(transform.x, transform.y);        
            ctx.lineTo(transform.x, 0);
            ctx.stroke();
        }
        else{
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }
    }


    if(typeof stick_x == "number"){
        obj.set('left', stick_x);
        obj.set('top', stick_y);
        if(obj.pointType == "arrow_start"){
            obj.line.set('x1', stick_x);
            obj.line.set('y1', stick_y);              
        }                                   
    }    
}

function moveEnd(e, obj, cap_flag = false) {

    if(btn_stick.classList.contains("top-menu-button-selected")){
        stick(e, obj);
        var stick_flag = true;        
    }
    else{
        var stick_flag = false;         
    }

    if(obj.name == "arrow" || obj.name == "arrow_part1" || obj.name == "line_part1"){
        obj.line.oldX = obj.line.x1;
        obj.line.oldY = obj.line.y1;

        if(obj.parent.groupType == "geometry"){
            obj.line.old_fake_X = obj.parent.line_part1.x1;
            obj.line.old_fake_Y = obj.parent.line_part1.y1;             
        }
       
    }
    else{
        // obj.fill = "blue";
        obj.line.oldX = obj.line.x2;
        obj.line.oldY = obj.line.y2;

        if(obj.parent.groupType == "geometry"){
            obj.line.old_fake_X = obj.parent.line_part2.x1;
            obj.line.old_fake_Y = obj.parent.line_part2.y1;
        }              
    }
    if(obj.name == "arrow" || obj.name == "arrow2" || obj.name == "arrow_part1" || obj.name == "arrow_part2"){       
        if(!obj.parent.border){
            obj.line.set('x1', obj.arrow.get('left'));
            obj.line.set('y1', obj.arrow.get('top'));             
            if(obj.arrow2 != undefined){ 
                obj.line.set('x2', obj.arrow2.get('left'));
                obj.line.set('y2', obj.arrow2.get('top'));                                         
            }
            else{           
                obj.line.set('x2', obj.parent.get('left'));
                obj.line.set('y2', obj.parent.get('top'));  
            }            
        }

        textRotate(obj);
        var x1, y1, x2, y2;
        var side_line1, side_line2;
        if(obj.name == "arrow"){
            side_line1 = obj.parent.line_part1;
            side_line2 = obj.parent.line_part2;
            var param1 = "1";           
            var param2 = "2";
            var param3 = "2";                                     
        }
        else if(obj.name == "arrow2"){
            side_line1 = obj.parent.line_part2;
            side_line2 = obj.parent.line_part1;
            var param1 = "2";           
            var param2 = "1";
            var param3 = "";              
        }
        if(side_line2 != undefined){ // объект geometry       

            var diag_angle_new = calcArrowAngle(canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, side_line2.x1, side_line2.y1, true);

            //////////
            var r2 = obj.parent.diag = calcLineLength({x1: side_line2.x1, y1: side_line2.y1, x2: canvas.getPointer(e.e).x, y2: canvas.getPointer(e.e).y});

            obj.parent.old_diag_angle = diag_angle_new;

            var katet2 = side_line2.length;

            // угол между курсором (стрелкой основной линии) и нижней точкой боковой линии
            var temp_angle = calcArrowAngle(canvas.getPointer(e.e).x, canvas.getPointer(e.e).y, side_line1.x1, side_line1.y1, true);

            var rotate_angle = Math.PI/2 - temp_angle + diag_angle_new; // угол, нак который поворачивается
            // координатная плоскость, угол между диагональю и линией,
            // проходящей мужду двух нижних точек боковых линий (фактически основание прямоугольника фигуры geometry)   
            // x3 показывает, слева или справа находится x в координатной плоскости центром которой является точка, за которую тянем,
            // если x3 слева от x4, то мы поворачивали фигуру, а не перетаскивали курсор так, чтобы она зеркально отразилась
            // иначе говоря, x3 это проекция точки курсора на линию, проходящую мужду двух нижних точек боковых линий, которая становится осью Х
            var luft = 50;            
            if(obj.parent.opposite == false){
                luft = -50;
            }
            let x3 = side_line2.x1 - r2 * Math.cos(rotate_angle) + luft;
            let y3 = side_line2.y1 + r2 * Math.cos(Math.PI - rotate_angle);             
            let x4 = side_line2.x1;
            let y4 = side_line2.y1;            

            var ctx = canvas.upperCanvasEl.getContext('2d');
            ctx.fillStyle = "lime";  

            // var ctx = canvas.upperCanvasEl.getContext('2d');
            // ctx.fillStyle = "orange";
            // ctx.clearRect(0, 0, canvas.width, canvas.height); 
            // var transform = fabric.util.transformPoint(new fabric.Point(x3, 300), canvas.viewportTransform);   
            // ctx.fillRect(transform.x, transform.y, 10, 10);
         
            // obj.parent.opposite — если true, это означает, что мы переместили край, за который тянем, за точку,
            // из которой начали рисовать
            var hyp = Math.sqrt(Math.pow(r2, 2) - Math.pow(katet2, 2));
            if(hyp <= 150 || isNaN(hyp)){
                if(!obj.parent.border){
                    side_line1.start = {
                        x: obj.left,
                        y: obj.top
                    }
                }
                obj.parent.border = true;
                // console.log(obj.parent.border);                               
            }
            else{
                obj.parent.border = false;                                 
            }

            if(x3 < x4 && y3 < y4){
                obj.parent.opposite = true;
                // console.log("слева");                              
            }
            else{
                obj.parent.opposite = false;
                // console.log("справа");                             
            }             

            if(obj.parent.border){
                let beta = calcArrowAngle(obj.line.get("x" + param2), obj.line.get("y" + param2), side_line1.start.x, side_line1.start.y, true);                   
                // console.log(beta * 180 / Math.PI);
                var x0 = side_line1.start.x;
                var y0 = side_line1.start.y;

                var xr = canvas.getPointer(e.e).x;
                var yr = y0 - (x0 - xr) * Math.tan(beta);        

                obj.left = xr;
                obj.top = yr;        
                obj.line.set("x" + param1, obj.get('left'));
                obj.line.set("y" + param1, obj.get('top'));             
            }
         
            // в некотором диапазоне от начальной точки меняем формулу, но не меняем obj.parent.opposite. Также меняем
            // формулу, если obj.parent.opposite равен true
            if(!obj.parent.opposite || (!obj.parent.opposite && !obj.parent.added)){
                var signX = 1;                    
                var signY = 1;
                var ugol2 = diag_angle_new - Math.acos(katet2 / r2);
            }               
            else{
                var signX = -1;
                var signY = 1;                
                var ugol2 = Math.PI - (diag_angle_new + Math.acos(katet2 / r2));                 
            }
            // если больше определенной длины линии между курсором и верхней точкой боковой линии          
            if(Math.sqrt(Math.pow(r2, 2) - Math.pow(katet2, 2)) >= 5){
                // side_line2 изменяется и определен выше
                // тут рисуем точку основной линии, противоположной той, со стороны которй находится стрелка, за которую тянем                
                // x1 — расчетная нижняя точка боковой линии
                // x2 — расчетная верхняя точка боковой линии 
                x2 = side_line2.x1 - signX * katet2 * Math.cos(ugol2);                       
                y2 = side_line2.y1 - signY * katet2 * Math.sin(ugol2);
                if(!obj.parent.border){
                    obj.line.set("x" + param2, x2);
                    obj.line.set("y" + param2, y2);                    
                }

                obj.lockMovementX = false;
                obj.lockMovementY = false;                                               
            }
            else{                              
                obj.lockMovementX = true;
                obj.lockMovementY = true;

                obj.line.set("x" + param1, obj.line["lastX" + param1]);
                obj.line.set("y" + param1, obj.line["lastY" + param1]);                               

                obj.set('left', obj.line.get("x" + param1));
                obj.set('top', obj.line.get("y" + param1)); 
            }

        }
        else{ // если не объект geometry
            var coords;   
               
            if(obj.arrow2){ 
                var angle = calcArrowAngle(obj.arrow.get('left'), obj.arrow.get('top'), obj.arrow2.get('left'), obj.arrow2.get('top'), true);                 
            } 
            else{
                var angle = calcArrowAngle(obj.arrow.get('left'), obj.arrow.get('top'), obj.line.get('x2'), obj.line.get('y2'), true);                             
            }          
        }

        obj.line._setWidthHeight();

        var key = obj.groupName;
        stick_array[key] = {};

        let param = text2Angle(obj.line.get('x1'), obj.line.get('y1'), obj.line.get('x2'), obj.line.get('y2'));
        x0 = stick_array[key].x0 = param.x;
        x0 = stick_array[key].y0 = param.y;

        x1 = stick_array[key].x1 = obj.line.get('x1');
        y1 = stick_array[key].y1 = obj.line.get('y1');
        x2 = stick_array[key].x2 = obj.line.get('x2');
        y2 = stick_array[key].y2 = obj.line.get('y2');
    
            // var t = canvas.viewportTransform;

            // canvas.contextTop.fillRect(t[0]*x0+t[4], t[3]*y0+t[5], 10, 10)
            // canvas.contextTop.fillRect(t[0]*x1+t[4], t[3]*y1+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x2+t[4], t[3]*y2+t[5], 10, 10)  

        if(obj.parent.groupType == "geometry"){
            x3 = stick_array[key].x3 = obj.parent.line_part1.get('x1');
            y3 = stick_array[key].y3 = obj.parent.line_part1.get('y1');
            x4 = stick_array[key].x4 = obj.parent.line_part2.get('x1');
            y4 = stick_array[key].y4 = obj.parent.line_part2.get('y1');
            param = text2Angle(obj.parent.line_part1.get('x1'), obj.parent.line_part1.get('y1'), obj.parent.line.get('x1'), obj.parent.line.get('y1'));
            x5 = stick_array[key].x5 = param.x;
            y5 = stick_array[key].y5 = param.y;
            param = text2Angle(obj.parent.line_part2.get('x1'), obj.parent.line_part2.get('y1'), obj.parent.line.get('x2'), obj.parent.line.get('y2'));
            x6 = stick_array[key].x6 = param.x;
            y6 = stick_array[key].y6 = param.y;

            // var t = canvas.viewportTransform;

            // canvas.contextTop.fillRect(t[0]*x0+t[4], t[3]*y0+t[5], 10, 10)
            // canvas.contextTop.fillRect(t[0]*x1+t[4], t[3]*y1+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x2+t[4], t[3]*y2+t[5], 10, 10)  
            // canvas.contextTop.fillRect(t[0]*x3+t[4], t[3]*y3+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x4+t[4], t[3]*y4+t[5], 10, 10)
            // canvas.contextTop.fillRect(t[0]*x5+t[4], t[3]*y5+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x6+t[4], t[3]*y6+t[5], 10, 10)                     
        } 
        if(obj.parent.groupType == "socket"){
            if(obj.name == "arrow_part1"){
                param = text2Angle(obj.parent.line_part2.get('x1'), obj.parent.line_part2.get('y1'), obj.parent.line_part2.get('x2'), obj.parent.line_part2.get('y2'));
                x3 = stick_array[key].x3 = param.x;
                y3 = stick_array[key].y3 = param.y;                
                x4 = stick_array[key].x4 = obj.parent.line_part2.get('x1');
                y4 = stick_array[key].y4 = obj.parent.line_part2.get('y1');                
            }
            else{
                param = text2Angle(obj.parent.line_part1.get('x1'), obj.parent.line_part1.get('y1'), obj.parent.line_part1.get('x2'), obj.parent.line_part1.get('y2'));
                x3 = stick_array[key].x3 = param.x;
                y3 = stick_array[key].y3 = param.y;                            
                x4 = stick_array[key].x4 = obj.parent.line_part1.get('x1');
                y4 = stick_array[key].y4 = obj.parent.line_part1.get('y1');                  
            }

            // var t = canvas.viewportTransform;

            // canvas.contextTop.fillRect(t[0]*x0+t[4], t[3]*y0+t[5], 10, 10)
            // canvas.contextTop.fillRect(t[0]*x1+t[4], t[3]*y1+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x2+t[4], t[3]*y2+t[5], 10, 10)  
            // canvas.contextTop.fillRect(t[0]*x3+t[4], t[3]*y3+t[5], 10, 10)              
            // canvas.contextTop.fillRect(t[0]*x4+t[4], t[3]*y4+t[5], 10, 10)                        
        }              
                                       
        if(typeof angle == "undefined"){
           var angle = calcArrowAngle(x1, y1, x2, y2);
        }
        else{
            angle = angle * 180 / Math.PI;          
        }
       
        if(obj.parent.groupType != "cap"){
            obj.arrow.set('angle', angle);
        }

        if(obj.arrow2 != undefined && obj.type != "circle"){        
            obj.arrow2.set('angle', angle);           
        }    

        obj.line.setCoords();

        if(obj.parent.groupType == "cap" && !cap_flag){                
            if(Math.sign(Math.cos(angle * Math.PI/180))>0){
                obj.arrow.originX = 'right';
            }
            else{
                obj.arrow.originX = 'left';
            }         
        }  
        if(obj.parent.groupType == "geometry" && ugol2 != NaN){
            let lines_coords = calcLineAngle(obj, e); 
            if(obj.name == "arrow" || obj.name == "arrow2"){
                let rotate_obj = obj.parent.line_part2;                
                if(!obj.parent.added){ // фигура geometry не создана, растягивается бъект parent
                    r2 = rotate_obj.length;      
                    x2 = rotate_obj.x1 - 1 * r2 * Math.cos(ugol2);                       
                    y2 = rotate_obj.y1 - 1 * r2 * Math.sin(ugol2);

                    obj.parent.line_part1.set('x1', lines_coords.x1);
                    obj.parent.line_part1.set('y1', lines_coords.y1);
                    obj.parent.line_part2.set('x2', x2);
                    obj.parent.line_part2.set('y2', y2);                                     
                    
                    obj.parent.arrow2.set('left', x2);
                    obj.parent.arrow2.set('top', y2);

                    obj.parent.arrow_part1.set('left', lines_coords.x1);
                    obj.parent.arrow_part1.set('top', lines_coords.y1);                 
                    obj.parent.arrow_part2.set('left', lines_coords.x2);
                    obj.parent.arrow_part2.set('top', lines_coords.y2);

                    obj.parent.arrow_part1.setCoords();
                    obj.parent.arrow_part2.setCoords();
                                   
                    obj.parent.line_part1.length = obj.parent.line_part2.length = calcLineLength({x1: obj.parent.line_part2.x1, y1: obj.parent.line_part2.y1, x2: obj.parent.line_part2.x2, y2: obj.parent.line_part2.y2});                     
                }
                else{                
                    // нижняя точка боковой линии                    
                    side_line1.set('x1', lines_coords["x" + param1]);
                    side_line1.set('y1', lines_coords["y" + param1]);

                    // элемент arrow (тут пустой круг), нижняя точка боковой линии
                    obj.parent.arrow_part1.set('left', lines_coords.x1);
                    obj.parent.arrow_part1.set('top', lines_coords.y1);
                    // элемент arrow (тут пустой круг), нижняя точка боковой линии                                     
                    obj.parent.arrow_part2.set('left', lines_coords.x2);
                    obj.parent.arrow_part2.set('top', lines_coords.y2);

                }
            }
            textRotate(obj.parent);
            // Если тянем за стрелки
            if(canvas.getActiveObject()){
                var active_obj = canvas.getActiveObject().name;
                if(active_obj != "line_part1" && active_obj != "line_part2"){
                    // верхняя точка боковой линии у той стрелки, за которую тянем
                    side_line1.set('x2', obj.parent["x" + param1]);
                    side_line1.set('y2', obj.parent["y" + param1]);
                    // верхняя точка противоположной боковой линии 
                    var side_ang = calcArrowAngle(side_line1.x1, side_line1.y1, side_line1.x2, side_line1.y2, true)    
                    var side_top_x = side_line2.x1 + 1 * side_line1.length * Math.cos(side_ang);                       
                    var side_top_y = side_line2.y1 + 1 * side_line1.length * Math.sin(side_ang);

                    side_line2.set('x2', side_top_x);
                    side_line2.set('y2', side_top_y);

                    if(obj.parent.groupType == "geometry"){
                        if(!obj.parent.border && obj.parent.added){
                            obj.line.set("x" + param2, side_top_x);
                            obj.line.set("y" + param2, side_top_y);                    
                        } 
                        obj.parent["arrow" + param3].set('left', obj.parent["x" + param2]);
                        obj.parent["arrow" + param3].set('top', obj.parent["y" + param2]); 
                    }    
                }
                // if(stick_flag){
                //     // stick(e, side_line1);
                //     // stick(e, side_line2);
                // }                                                          
            }
            if(testgo){
                side_line1.set('x2', obj.parent["x" + param1]);
                side_line1.set('y2', obj.parent["y" + param1]);        
                side_line2.set('x2', obj.parent["x" + param2]);
                side_line2.set('y2', obj.parent["y" + param2]);                  
            }              
        }
    }
    obj.line.lastX1 = obj.line.x1;
    obj.line.lastX2 = obj.line.x2;
    obj.line.lastY1 = obj.line.y1;
    obj.line.lastY2 = obj.line.y2;        
    if(!obj.parent.added){
        //иначе при создании стрелки рисуются с опозданием
        canvas.renderAll();          
    }     
}

function moveLine(e, line) {
    if(line.parent.arrow_part1 != undefined && line.parent.groupType != "socket"){ // Объет geometry
        if(line.name != "line_part1" && line.name != "line_part2"){
            line.parent.line_part1.length = line.parent.line_part2.length = calcLineLength({x1: line.parent.line_part1.x1, y1: line.parent.line_part1.y1, x2: line.parent.line_part1.x2, y2: line.parent.line_part1.y2});
        }

        let x0 = line.first_click.x;
        let y0 = line.first_click.y;
       
        let beta = calcArrowAngle(line.parent.line.x1, line.parent.line.y1, line.parent.line.x2, line.parent.line.y2, true);       

        // let alpha = beta + Math.PI/2; 
                      
        if ((beta > Math.PI / 2 - 0.1 && beta < Math.PI / 2 + 0.1) || 
            (beta > Math.PI * 3 / 2 - 0.1 && beta < Math.PI * 3 / 2 + 0.1)){
            var x = canvas.getPointer(e.e).x;
            var y = y0 - (x - x0) * 1 / Math.tan(beta);         
        }
        else{
            var y = canvas.getPointer(e.e).y;       
            var x = x0 - (y - y0) * Math.tan(beta);           
        }
        var delta_first_clickX = x - x0;
        var delta_first_clickY = y - y0;
        if(line.name != "line_part1" && line.name != "line_part2"){
            line.set({
                'left': line.first_center.x + delta_first_clickX,
                'top': line.first_center.y + delta_first_clickY
            });
            // canvas.renderAll();
        } 
        if(line == line.parent){
            if(Math.sign(line.parent.line_part1.y2 - line.parent.line_part1.y1) < 0){
                line.parent.text2.originY = 'bottom'; 
                if(beta * 180 / Math.PI >= 90 && beta * 180 / Math.PI <= 270){
                    line.parent.direction = 1; // устанавливаем направление относительно начального положения — требуется,
                    // чтобы понять, с какой стороны от основной линии рисовать боковые линии               
                }
                else{
                    line.parent.direction = -1; // устанавливаем направлени относительно начального положенияе — требуется,
                    // чтобы понять, с какой стороны от основной линии рисовать боковые линии                               
                }
            }
            else{
                line.parent.text2.originY = 'top';
                if(beta * 180 / Math.PI >= 90 && beta * 180 / Math.PI <= 270){
                    line.parent.direction = -1;                
                }
                else{
                    line.parent.direction = 1;                
                }           
            }
        }          
        if(line.name != "line_part2"){
            line.parent.line_part2.set('x2', line.parent.x2);
            line.parent.line_part2.set('y2', line.parent.y2);            
            line.parent.line_part2.setCoords();            
        }
        if(line.name != "line_part1"){
            line.parent.line_part1.set('x2', line.parent.x1);
            line.parent.line_part1.set('y2', line.parent.y1); 
            line.parent.line_part1.setCoords();                        
        }        
        if(line.name == "line_part1"){
            line.parent.arrow.set('left', line.parent.line_part1.x2);
            line.parent.arrow.set('top', line.parent.line_part1.y2); 
            line.parent.arrow_part1.set('left', line.parent.line_part1.x1);
            line.parent.arrow_part1.set('top', line.parent.line_part1.y1);            
        }
        if(line.name == "line_part2"){     
            line.parent.arrow2.set('left', line.parent.line_part2.x2);
            line.parent.arrow2.set('top', line.parent.line_part2.y2); 
            line.parent.arrow_part2.set('left', line.parent.line_part2.x1);
            line.parent.arrow_part2.set('top', line.parent.line_part2.y1);                    
        }
        if(line.name != "line_part1" && line.name != "line_part2"){
            line.parent.diag_length = calcLineLength({x1: line.parent.line_part2.x1, y1: line.parent.line_part2.y1, x2: line.line.x1, y2: line.line.y1});
        }
    }
    line.setCoords();
    angle = calcArrowAngle(line.x1, line.y1, line.x2, line.y2);  
    var oldCenterX = (line.x1 + line.x2) / 2,
        oldCenterY = (line.y1 + line.y2) / 2,
        deltaX = Math.round(line.left - oldCenterX),
        deltaY = Math.round(line.top - oldCenterY);
    line.arrow.set({
        'left': line.x1 + deltaX,
        'top': line.y1 + deltaY
    }).setCoords();
    if(line.arrow2 != undefined){
        line.arrow2.set({
            'left': line.x2 + deltaX,
            'top': line.y2 + deltaY
        }).setCoords();        
    }
    line.set({
        'x1': line.x1 + deltaX,
        'y1': line.y1 + deltaY,
        'x2': line.x2 + deltaX,
        'y2': line.y2 + deltaY
    }); 
    line.set({
        'left': (line.x1 + line.x2) / 2,
        'top': (line.y1 + line.y2) / 2
    });

    if(line.text2 != undefined && line.parent.groupType != "cap"){
        textRotate(line);
    }

    var key = line.groupName;
    if(stick_array[key]){
        stick_array[key].x1 = line.get('x1');
        stick_array[key].y1 = line.get('y1');
        stick_array[key].x2 = line.get('x2');
        stick_array[key].y2 = line.get('y2');        
    }
    if(line.parent.groupType == "geometry"){
        // Иначе стрелки рисуются с опозданием
        canvas.renderAll();         
    }
}

function calcLineLength(line){
    let a = line.x2 - line.x1;
    let b = line.y2 - line.y1; 
    let length = Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    return length;
}

function calcLineAngle(obj, e){
    // здесь высчитываются координаты концов боковых линий в зависимости от угла основной линии к горизонтали  
    if(obj.angleDelta == undefined){
        obj.angleDelta = 0;
    }
    var beta = calcArrowAngle(obj.parent.x1, obj.parent.y1, obj.parent.x2, obj.parent.y2, true);   

    if(obj.name == "arrow" || obj.name == "arrow_part1" || obj.name == "line_part1"){
        var line_main = {
            x1: obj.parent.line.x1,
            y1: obj.parent.line.y1,
            x2: obj.parent.line.x2,
            y2: obj.parent.line.y2                        
        }       
    }
    else{
        var line_main = {
            x1: obj.parent.line.x2,
            y1: obj.parent.line.y2,
            x2: obj.parent.line.x1,
            y2: obj.parent.line.y1                        
        }        
    }

    var beta_deg = beta * 180 / Math.PI;
    if((beta_deg > 135 && beta_deg < 225) || (beta_deg > 315 && beta_deg < 360) || (beta_deg < 45)){
        //console.log("Под 45!");
        if(Math.sign(line_main.x2 - obj.parent.line.oldX) == -Math.sign(line_main.x2 - line_main.x1)){
            //console.log("Сменили знак!");            
            if(obj.angleDelta == 0){
                obj.angleDelta = Math.PI;
            }
            else{
                obj.angleDelta = 0;
            }
        } 
    }
    else{
        if(Math.sign(line_main.y2 - obj.parent.line.oldY) == -Math.sign(line_main.y2 - line_main.y1)){
            if(obj.angleDelta == 0){
                obj.angleDelta = Math.PI;
            }
            else{
                obj.angleDelta = 0;
            }
            beta += Math.PI;      
        } 
    }

    beta += obj.angleDelta; // угол основной линии
    //console.log(beta * 180 / Math.PI);
    // боковая линия рисуется под углом девянсото градусов к основной с учетом направления:
    if(obj.name == "arrow" || obj.name == "line_part1"){ // в зависимости от того, за какую часть деформируем        
        if(obj.parent.direction == -1){
            var alpha = beta + Math.PI/2;     
        } 
        else{
            var alpha = beta - Math.PI/2;
        }         
    }    
    if(obj.name == "arrow2" || obj.name == "line_part2"){ // в зависимости от того, за какую часть деформируем
        if(obj.parent.direction == 1){
            var alpha = beta - Math.PI/2;     
        } 
        else{
            var alpha = beta + Math.PI/2;
        }         
    }   

    let r2 = obj.parent.line_part2.length; // длина боковой линии 

    if(obj.name == "arrow" || obj.name == "arrow2"){
       sign = 1;       
    }
    else{
       sign = -1;  
    }

    if(obj.name == "arrow" || obj.name == "arrow2"){
        var x0 = obj.parent.x1;
        var y0 = obj.parent.y1;       
    }
    else{
        var x0 = obj.parent.line_part1.x1;
        var y0 = obj.parent.line_part1.y1;        
    }

    // нижняя точка первой линии
    let x1 = x0 + sign * r2 * Math.cos(alpha);                        
    let y1 = y0 + sign * r2 * Math.sin(alpha);

    if(obj.name == "arrow" || obj.name == "arrow2"){
        var x0 = obj.parent.x2;
        var y0 = obj.parent.y2;       
    }
    else{
        var x0 = obj.parent.line_part2.x1;
        var y0 = obj.parent.line_part2.y1;        
    }

    // нижняя точка второй линии
    let x2 = x0 + sign * r2 * Math.cos(alpha);                        
    let y2 = y0 + sign * r2 * Math.sin(alpha);
     
    return {x1: x1, y1: y1, x2: x2, y2: y2, parent_ang: beta_deg};
}

function calcArrowAngle(x1, y1, x2, y2, rad = false) {
    let r = calcLineLength({x1: x1, y1: y1, x2: x2, y2: y2}); // длинна линии
    let beta = Math.acos((x2 - x1) / r); // угол между линией и осью абсцисс

    beta = beta + (Math.PI - beta) * 2 * -1 * Math.sign(-1 + Math.sign(y2 - y1));
    let angle = beta * 180 / Math.PI;
    if(rad){
        return beta;
    }
    else{
        return angle;        
    }
}

function textRotate(obj){
    if(obj.text2 != undefined && obj.parent.groupType != "cap"){
        let param = text2Angle(obj.line.x1, obj.line.y1, obj.line.x2, obj.line.y2);
        let alpha = param.ang;
        if(alpha != 360){
            obj.text2.angle = alpha - 180*Math.floor(Math.cos(alpha*Math.PI/180));            
        }
        else{
            obj.text2.angle = alpha;             
        }

        obj.text2.setCoords();

        // центрирование текста

        obj.text2.left =  param.x - (obj.text2.width/2)*Math.cos(alpha*Math.PI/180)*Math.sign(Math.cos(alpha*Math.PI/180));            
        obj.text2.top = param.y - (obj.text2.width/2)*Math.sin(alpha*Math.PI/180)*Math.sign(Math.cos(alpha*Math.PI/180));            
    }   
}

function text2Angle(x1, y1, x2, y2){  
    let ang = calcArrowAngle(x1, y1, x2, y2);

    let x3 = (x2 - x1) / 2 + x1;
    let y3 = (y2 - y1) / 2 + y1;            
    let coords = {
        ang: ang,
        x: x3,
        y: y3
    };
    
    return coords;
}

function getRadiusCoords(x0 = 0, y0 = 0, radius = 10, angle){ 
    // x = Math.ceil(x0 + radius * Math.cos(angle));
    // y = Math.ceil(y0 + radius * Math.sin(angle));       
    var x = x0 + radius * Math.cos(angle);
    var y = y0 + radius * Math.sin(angle);
    return {x: x, y: y, angle: angle}
}   

function gof(){

    // shape[0].line.set("y1", shape[0].line.y2);
    shape[0].line.set("x2", 300);    
    shape[0].line.set("y2", 200);    
    shape[0].line.set("x1", 600);    
    shape[0].line.set("y1", 200);
    shape[0].line_part1.set("y1", shape[0].line.y1);
    shape[0].line_part1.set("x1", shape[0].line.x1);
    shape[0].line_part1.set("x2", shape[0].line.x1);
    shape[0].line_part1.set("y2", shape[0].line.y1 + 100); 

    shape[0].line_part2.set("y1", shape[0].line.y2);
    shape[0].line_part2.set("x1", shape[0].line.x2);
    shape[0].line_part2.set("x2", shape[0].line.x2);
    shape[0].line_part2.set("y2", shape[0].line.y2 + 100);                  
    shape[0].arrow.set("left", shape[0].line.x1);
    shape[0].arrow2.set("left", shape[0].line.x2);        
    shape[0].arrow.set("top", shape[0].line.y1);
    shape[0].arrow2.set("top", shape[0].line.y2);        
    canvas.renderAll();
}
var shag = -60;
var testgo = false;
function go(){
    testgo = true;
    shape[0].line.arrow2.set("left", shape[0].line.arrow2.left - shag);
    moveEnd({e: {clientX: shape[0].line.arrow2.left, clientY: shape[0].line.arrow2.top}}, shape[0].line.arrow2);
    // testgo = true;
    // shape[1].line_part1.arrow.set("left", shape[1].line_part1.arrow.left - shag);
    // moveEnd({pointer: {x: shape[1].line_part1.arrow.left, y: shape[1].line_part1.arrow.top}}, shape[1].line_part1.arrow);    
    canvas.renderAll()
}

function moveByStep(stepX, stepY){
    item = canvas.getActiveObject();
    var move_obj;
    if(item){

        if(group.cloneOne){ // __onMouseDown в fabric.js, там установлен getActiveObject().setCoords()
            let item = canvas.getActiveObject();       
            editor_obj.setClone(item);
            canvas.renderAll(); 
            element_cursor();        
        } 
        else{
            oldParam(item);            
        }

        if( (item.parent.objects || item.parent._objects != undefined) ){ // если не примтив,
            //проходимся по всем частям составного объекта
            if(item.parent.objects != undefined){
                for(let i of item.parent.objects){
                    move_obj = item.parent[i];
                    move_obj.left += stepX;
                    move_obj.top += stepY;      
                }
            } 
            if(item.parent._objects != undefined){ // выделение или группа и не часть другого объекта
                for(let obj of item.parent._objects){
                    move_obj = obj;
                    move_obj.left += stepX;
                    move_obj.top += stepY;                                
                }
            }               
        }else{
            move_obj = item;
            move_obj.left += stepX;
            move_obj.top += stepY;    
        }

        changes(item, "12");

        poupup.classList.add("mfp-hide");
        var ctx = canvas.contextTop;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        canvas.renderAll();
    }
}