源码安装
zhangsan / pz3D

// ==UserScript==
// @name         pz3D
// @namespace    http://tampermonkey.net/
// @version      2024.12.18.4
// @description  try it
// @author       You
// @match        http://139.224.232.214/labeltools/?type=BATCHWORK&*
// @grant        none
// @license      MIT
// @downloadURL https://update.greasyfork.org/scripts/506727/pz3D.user.js
// @updateURL https://update.greasyfork.org/scripts/506727/pz3D.meta.js
// ==/UserScript==

const _ds = {
    isHighlight: false,
    isSetOutside: false,
    isSetHidden: false,
}
window._ds = _ds;

(async function() {
    window.$ = Document.prototype.$ = Element.prototype.$ = $
    window.$$ = Document.prototype.$$ = Element.prototype.$$ = $$

    if(await awaitLoad()) showMessage('脚本生效中...', {type: 'success'})


    document.addEventListener('keydown', (e) => {
        // console.log(e.keyCode, e)
        if(['input[type="text"]', 'input[type=""]', 'textarea'].find(lab => document.activeElement?.matches(lab))) return

        const is_inTracking = $('.index_container__v2bdb:not(.hidden)')

        //【4】或【5】
        if(e.keyCode == 52 || e.keyCode == 53) {
            $$('.index_TopBar__atir2 .anticon').find(item => {
                const use = item.$('use')
                return use.getAttribute('xlink:href') == '#navbar-icon-setting'
            }).click();
            e.keyCode == 52 ? (_ds.isSetOutside = true) : (_ds.isSetHidden = true)
        }

        //【Tab】
        if(e.keyCode == 9) {
            e.preventDefault()

            const images_2d = [...$('.index_control__C8NVb .index_footer__9yCgR').children].filter(item => item.textContent !== 'M')
            let curSelIdx = images_2d.findIndex(img => img.matches('.index_selected__cO5nI'))
            if(e.shiftKey) curSelIdx = images_2d.length-1 - curSelIdx
            let imagesList = !e.shiftKey ? images_2d : images_2d.toReversed()
            const nextImg = imagesList.find((img, idx) => idx > curSelIdx && img.$('.index_exhibition-wrap__Su7aO'))?.$('.index_posBtName__MUO2p')
            if(nextImg) {
                nextImg.click()
            } else {
                imagesList.find((img, idx) => idx >= 0 && img.$('.index_exhibition-wrap__Su7aO'))?.$('.index_posBtName__MUO2p').click()
            }
        }

        //【B】
        if(e.keyCode == 66 ) {
            _ds.isHighlight = !_ds.isHighlight;

            [
                ...$$('.index_flexGrowView__IV3Bl canvas'),
                $('.index_container__v2bdb .index_container__63WZq'),
                $('.index_main__dnpUj .index_container__63WZq'),
                ...$$('.index_container__0mnyY'),
                $('.index_container__fp054'),
                $('.index_container__fp054 .index_container__63WZq'),
            ].forEach((item) => {
                if(!item) return

                item.style.transition = '.5s all'
                item.style.filter = _ds.isHighlight ? 'brightness(2)' : null
            })
        }

        //【Q】
        if(e.keyCode == 81 && is_inTracking) {
            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                key: "r",
                keyCode: 82,
                code: "KeyR",
                bubbles: true,
                cancelable: true
            }));
        }

        //【E】
        if(e.keyCode == 69 && is_inTracking) {
            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                key: "t",
                keyCode: 84,
                code: "KeyT",
                bubbles: true,
                cancelable: true
            }));
        }

        if([32, 192, 49, 50, 51].includes(e.keyCode)) {
            let commonKey = {
                32: {
                    key: "ArrowRight",
                    keyCode: 39,
                    code: "ArrowRight",
                    bubbles: true,
                    cancelable: true,
                    ctrlKey: true,
                    shiftKey: document.querySelector('.index_view__MhyQ9') ? true : false,
                },
                192: {
                    key: "ArrowLeft",
                    keyCode: 37,
                    code: "ArrowLeft",
                    bubbles: true,
                    cancelable: true,
                    ctrlKey: true,
                    shiftKey: document.querySelector('.index_view__MhyQ9') ? true : false,
                },
            }
            let {32: right, 192: left} = commonKey
            let keydownMap = {
                49: right,
                32: right,
                51: right,
                192: left,
                50: left,
            }

            if(document.querySelector('.index_view__MhyQ9')) {
                document.body.dispatchEvent(new KeyboardEvent("keydown", keydownMap[e.keyCode]));
            } else if(document.querySelector('.index_container__v2bdb').children.length || document.querySelector('.index_small-container__aVxtr')){
                if([192, 49, 32].includes(e.keyCode) && !e.altKey) {
                    turn( [32, 49].includes(e.keyCode) ? 'right' : 'left')
                } else if([192, 49].includes(e.keyCode)) {
                    document.body.dispatchEvent(new KeyboardEvent("keydown", keydownMap[e.keyCode]));
                }

                function turn(direction) {
                    let visiblePages = [...document.querySelector('.index_trackItem__6QT87').querySelectorAll('.index_track-rect__AvEjU')];

                    if(document.querySelector('.index_absolute-center__GLVXE')) { //判断是否超出分页区
                        let curPageDom = document.querySelector('.index_absolute-center__GLVXE').parentElement
                        let curPage = document.querySelector('.index_absolute-center__GLVXE').innerText -0

                        visiblePages.some((curPage, idx) => {
                            if(curPage === curPageDom) {
                                let pages = direction === 'right' ? visiblePages.slice(idx+1) : visiblePages.slice(0, idx).reverse()
                                if(!pages.length) { //处理首页和末页
                                    let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
                                    if((direction === 'right' && parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100) || (direction === 'left' && left !== '0%')) { //判断是否需要更新预览区
                                        updatePages(direction)
                                        turn(direction)
                                        return true
                                    }
                                }
                                pages.some((nextPage, nextIdx) => {
                                    if(nextPage.className.includes('index_relation__pabdZ') || nextPage.className.includes('index_key-relation__qe-c4')) {
                                        nextPage.click()
                                        return true
                                    }
                                    if(nextIdx == pages.length-1) {
                                        let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
                                        if(parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100) { //判断是否需要更新预览区
                                            updatePages(direction)
                                            turn(direction)
                                            return true
                                        }
                                    }
                                })
                                return true
                            }

                            let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
                            if(direction === 'right' && curPage === curPageDom && idx == visiblePages.length-1) { //当前页位于分页区末页
                                if(parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) == 100) return true

                                updatePages(direction)
                                turn(direction)
                                return true
                            } else if(direction === 'left' && curPage === curPageDom && idx == 0) { //当前页位于首页
                                if(left == '0%') return true //判断是否需要更新分页区

                                updatePages(direction)
                                turn(direction)
                                return true
                            }
                        })
                    } else {
                        let pages = direction === 'right' ? visiblePages : visiblePages.reverse()
                        // console.log('pages', pages)
                        pages.some((curPage, idx) => {
                            if(curPage.className.includes('index_relation__pabdZ') || curPage.className.includes('index_key-relation__qe-c4')) {
                                console.log(curPage)
                                curPage.click()
                                return true
                            }

                            let {width, left} = document.querySelector('.index_scroll-line__GHJXu').children[0].style //progress bar
                            if(direction === 'right' && idx == pages.length-1 && (parseInt(/(.*)%/.exec(width)[1]) + parseInt(/(.*)%/.exec(left)[1]) !== 100)) {
                                updatePages(direction)
                                turn(direction)
                                return true

                            } else if(direction === 'left' && idx == pages.length-1 && left !== '0%') { //遍历至首页 且 需要更新分页区
                                updatePages(direction)
                                turn(direction)
                                return true

                            }
                        })
                    }

                    function updatePages(direction) {
                        let viewTurnBtn = document.querySelector('.index_scroll-bar__V5IcI').querySelectorAll('.anticon')[direction === 'left' ? 0 : 1]
                        viewTurnBtn.click()
                    }
                }
            } else if(!document.querySelector('.index_container__v2bdb').children.length && !document.querySelector('.index_small-container__aVxtr')){
                document.body.dispatchEvent(new KeyboardEvent("keydown", keydownMap[e.keyCode]));
            }
        }

        //【Esc】
        if(e.keyCode == 68 && e.ctrlKey) {
            e.preventDefault()

            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                key: "Backspace",
                keyCode: 8,
                code: "Backspace",
                ctrlKey: true,
                bubbles: true,
                cancelable: true
            }))
            showMessage('删除当前对象')
        }

        //【Z】
        if(e.keyCode == 90 && $('.index_left__UteiD')) {
            if(!e.shiftKey) {
                document.body.dispatchEvent(new KeyboardEvent("keydown", {
                    key: "+",
                    keyCode: 187,
                    code: "Equal",
                    shiftKey: true,
                    bubbles: true,
                }))
                showMessage('复制到下一帧', {showTime: 1500})
            } else {
               document.body.dispatchEvent(new KeyboardEvent("keydown", {
                    key: "_",
                    keyCode: 189,
                    code: "Minus",
                    shiftKey: true,
                    bubbles: true,
                }))
                showMessage('复制到上一帧', {showTime: 1500})
            }

        }
    });

    document.addEventListener('mousedown', (e) => {
        if(e.button == 1) {
            e.preventDefault()
            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                key: "e",
                keyCode: 69,
                code: "KeyE",
                altKey: true,
                bubbles: true,
                cancelable: true
            }))
        }
    })
})();


function awaitLoad() {
    let isLoading = false
    let isObsToolBar = false

    return new Promise((res, rej) => {
        let mX = 0
        let mY = 0
        const groups_turnLight = [
            ['左转向灯亮', '左转向灯熄灭', '左转向灯不确定', '左转向灯不可见'],
            ['右转向灯亮', '右转向灯熄灭', '右转向灯不确定', '右转向灯不可见'],
        ]
        const groups_carDoor = [
            ['左边门开', '左边门关', '左门无法判断'],
            ['右边门开', '右边门关', '右门无法判断'],
            ['后备箱门开', '后备箱门关', '后备箱门无法判断'],
        ]

        Obs(document, (mrs) => {
            //过滤尺寸参数更新的记录
            if(!mrs.every(mr => mr.target?.matches?.('.index_cube-info__wYdK3') || mr.target?.matches?.('.index_cube-info__wYdK3 span'))) console.log(mrs)

            mrs.some((mr) => {
                [...mr.addedNodes].some((an) => {
                    // an.nodeName !== '#text' && console.log(an)

                    if(!isLoading && an?.nodeName === 'DIV' && $('.index_container__v2bdb')) res(true)

                    if(an?.nodeName === 'DIV' && an.className == '' && an.textContent.startsWith('显示配置')) {
                        Obs(an, (mrs) => {
                            mrs.forEach(mr => {
                                if(mr.type == 'attributes' && mr.target.matches('.ant-modal-wrap')) {
                                    if(_ds.isSetHidden && mr.target.style.display !== 'none') {
                                        func()
                                    }
                                }
                            })
                            // console.log(mrs)
                        }, {childList: true, subtree: true, attributes: true, attributeOldValue: true})
                        func()
                        function func() {
                            const settingPanel = $('.ant-modal.index_TheStopModal__RTmQK')
                            const li_obj = settingPanel.$$('li').find(item => item.textContent.startsWith('标注对象'))
                            const li_common = settingPanel.$$('li').find(item => { //通用功能
                                return item.textContent.startsWith('通用功能') && item.parentElement.parentElement.textContent.startsWith('标注对象')
                            })
                            const targetBtn = settingPanel.$$('.DP-row').find(item => item.textContent.includes('选中不隐藏其他对象'))?.$('button')
                            if(targetBtn) {
                                targetBtn.click()
                                settingPanel.$$('.DP-bottom button').find(btn => btn.textContent == '应 用')?.click()
                                settingPanel.$('.ant-modal-close')?.click()
                                _ds.isSetHidden = false
                            } else if(li_common){
                                li_common.click()
                            } else {
                                li_obj.children[0].click()
                            }
                        }
                    }
                    if(_ds.isSetHidden && an.matches?.('.ant-menu.ant-menu-sub') && an.textContent.startsWith('通用功能快捷属性')) {
                        const lis = $$('.ant-modal.index_TheStopModal__RTmQK li')
                        lis.find(item => {
                            return item.textContent.startsWith('通用功能') && item.parentElement.parentElement.textContent.startsWith('标注对象')
                        })?.click()
                    }
                    if(_ds.isSetHidden && an.matches?.('.DP-view') && an.textContent.includes('选中不隐藏其他对象')) {
                        const settingPanel = $('.ant-modal.index_TheStopModal__RTmQK')
                        const targetBtn = settingPanel.$$('.DP-row').find(item => item.textContent.includes('选中不隐藏其他对象'))?.$('button')
                        targetBtn.click()
                        settingPanel.$$('.DP-bottom button').find(btn => btn.textContent == '应 用')?.click()
                        settingPanel.$('.ant-modal-close')?.click()
                        _ds.isSetHidden = false
                    }


                    if(an?.matches?.('.ant-modal-root') && an.textContent.startsWith('多矩形框 对象属性')) {
                        
                        setTimeout(() => $$('.index_title__KB3CQ')[1].scrollIntoView({block: "start"}), 300)

                        const li_turnLight = an.$$('.index_MatchInput__ZcwID').find(item => item.$('.index_title__Fzt5I').textContent.startsWith('* 转向灯'))
                        const li_carDoor = an.$$('.index_MatchInput__ZcwID').find(item => {
                            const header = item.$('.index_title__Fzt5I')
                            const res = header.textContent.startsWith('* 开车门')
                            if(res && !$('.travel-btn')) header.append(createBtn())
                            return res
                        })
                        ObsAttr(li_turnLight, groups_turnLight)
                        ObsAttr(li_carDoor, groups_carDoor)
                    }

                    if(an.matches?.('.index_MatchInput__ZcwID') && an.textContent.startsWith('* 转向灯')) {
                        ObsAttr(an, groups_turnLight)
                    }
                    if(an.matches?.('.index_MatchInput__ZcwID') && an.textContent.startsWith('* 开车门')) {
                        if(!$('.travel-btn')) an.$('.index_title__Fzt5I').append(createBtn())

                        ObsAttr(an, groups_carDoor)
                    }

                    function createBtn() {
                        return createEl('div', {
                            className: 'travel-btn',
                            innerText: '行驶',
                            style: {
                                padding: '0px 5px',
                                height: '20px',
                                lineHeight: '20px',
                                margin: '0 0 0 5px',
                                backgroundColor: 'rgb(136, 136, 136)',
                                color: 'rgb(255, 255, 255)',
                                fontSize: '12px',
                                cursor: 'pointer',
                                userSelect: 'none',
                                borderRadius: '5px',
                            },
                            onclick: function() {
                                const selList = ['左边门关', '右边门关', '后备箱门关']
                                const targetItem = $$('.index_MatchInput__ZcwID').find(item => item.textContent.startsWith('* 开车门'))
                                targetItem.$$('label').forEach(label => {
                                    const matchRes = selList.some(content => label.textContent.includes(content))
                                    if(matchRes) {
                                        if(!label.matches('.ant-checkbox-wrapper-checked')) label.click()
                                    } else if(label.matches('.ant-checkbox-wrapper-checked')) {
                                        label.click()
                                    }
                                })
                            }
                        })
                    }
                    function ObsAttr(obsTarget, groups) {
                        Obs(obsTarget, mrs => {
                            mrs.forEach(mr => {
                                const target = mr.target
                                if(mr.attributeName == 'class' && target.matches('.ant-checkbox-wrapper.ant-checkbox-wrapper-checked')) {
                                    const labels = obsTarget.$$('label')
                                    groups.forEach(group => {
                                        const findRes = group.find(option => option == target.textContent)
                                        if(!findRes) return

                                        group.some(option => {
                                            if(option == target.textContent) return

                                            const optionLabel = labels.find(label => label.textContent.includes(option))
                                            if(optionLabel && optionLabel.matches('.ant-checkbox-wrapper-checked')) optionLabel.click()
                                        })
                                        const label_notAno = labels.find(label => label.textContent.includes('无需标注'))
                                        if(label_notAno && label_notAno.matches('.ant-checkbox-wrapper-checked')) label_notAno.click()
                                    })

                                    if(target.textContent === '无需标注') {
                                        labels.forEach(label => {
                                            if(label.textContent !== '无需标注' && label.matches('.ant-checkbox-wrapper-checked')) label.click()
                                        })
                                    }
                                }
                            })
                        }, {childList: true, subtree: true, attributes: true})
                    }
                    if(an?.nodeName === 'DIV' && an.className == '' && an.textContent.startsWith('显示配置')) {
                        Obs(an, (mrs) => {
                            mrs.forEach(mr => {
                                if(mr.type == 'attributes' && mr.target.matches('.ant-modal-wrap')) {
                                    if(_ds.isSetOutside && mr.target.style.display !== 'none') {
                                        func()
                                    }
                                }
                            })
                            // console.log(mrs)
                        }, {childList: true, subtree: true, attributes: true, attributeOldValue: true})
                        func()
                        function func() {
                            const settingPanel = $('.ant-modal.index_TheStopModal__RTmQK')
                            const li_obj = settingPanel.$$('li').find(item => item.textContent.startsWith('标注对象'))
                            const li_imgObj = settingPanel.$$('li').find(item => { //图像对象
                                return item.textContent.startsWith('图像对象') && item.parentElement.parentElement.textContent.startsWith('标注对象')
                            })
                            const targetBtn = settingPanel.$$('.DP-row').find(item => item.textContent.includes('允许绘制到图像外'))?.$('button')
                            if(targetBtn) {
                                targetBtn.click()
                                settingPanel.$$('.DP-bottom button').find(btn => btn.textContent == '应 用')?.click()
                                settingPanel.$('.ant-modal-close')?.click()
                                _ds.isSetOutside = false
                            } else if(li_imgObj){
                                li_imgObj.click()
                            } else {
                                li_obj.children[0].click()
                            }
                        }
                    }

                    if(_ds.isSetOutside && an.matches?.('.ant-menu.ant-menu-sub') && an.textContent.startsWith('通用功能快捷属性')) {
                        const lis = $$('.ant-modal.index_TheStopModal__RTmQK li')
                        lis.find(item => {
                            return item.textContent.startsWith('图像对象') && item.parentElement.parentElement.textContent.startsWith('标注对象')
                        })?.click()
                    }

                    if(_ds.isSetOutside && an.matches?.('.DP-view') && an.textContent.includes('允许绘制到图像外')) {
                        const settingPanel = $('.ant-modal.index_TheStopModal__RTmQK')
                        const targetBtn = settingPanel.$$('.DP-row').find(item => item.textContent.includes('允许绘制到图像外'))?.$('button')
                        targetBtn.click()
                        settingPanel.$$('.DP-bottom button').find(btn => btn.textContent == '应 用')?.click()
                        settingPanel.$('.ant-modal-close')?.click()
                        _ds.isSetOutside = false
                    }

                    if(an?.matches?.('.index_tracking-view__6ChPR')) {
                        const indicator = [...an.children].find(item => item.classList.contains('index_view-instance__0KM+j'))
                        indicator.style.zIndex = 9999
                        indicator.style.bottom = '-10px'
                    }

                    if(an?.classList?.contains('index_main__L+qBm')) {
                         const btn_scale = $('.index_cube-scale-translation-tracking__2BRIw')
                         const btn_scale_value = btn_scale.$$('span')[1].firstChild
                        btn_scale_value.nodeValue == '缩放' && lockTranslation()

                        Object.defineProperty(btn_scale_value, 'nodeValue', {
                            get: function() {
                                return this.textContent;
                            },
                            set: function(newValue) {
                                if(newValue == '缩放') setTimeout(() => lockTranslation())
                                this.textContent = newValue;
                            },
                            enumerable: true,
                            configurable: true
                        });

                        function lockTranslation() {
                            const btn_scale_btn = btn_scale.$('button')
                            if(btn_scale_btn.textContent == '缩放') {
                                btn_scale_btn.click()
                                setTimeout(() => lockTranslation())
                            }
                        }
                    }

//                     if(an?.matches?.('.index_body-shape-tracking__NGXB3 .ant-btn-primary')) {
//                         const btn_shape_value = an.$('span').firstChild
//                         btn_shape_value.nodeValue == '变形刚体' && lockStabilize()

//                         Object.defineProperty(btn_shape_value, 'nodeValue', {
//                             get: function() {
//                                 return this.textContent;
//                             },
//                             set: function(newValue) {
//                                 if(newValue == '变形刚体') setTimeout(() => lockStabilize())
//                                 this.textContent = newValue;
//                             },
//                             enumerable: true,
//                             configurable: true
//                         });
//                     }

                    if(an?.matches?.('.ant-modal-root') && an.textContent.startsWith('多矩形框 对象属性')) {
                        const mask = an.$('.ant-modal-mask')
                        mask.style.background = 'transparent'

                        const dialogWrap = an.$('.ant-modal')
                        setStyle(dialogWrap, {
                            transition: '.2s all',
                            position: 'absolute',
                            top: `calc(50% - 362px + ${mY}px)`,
                            left: `calc(70% + ${mX}px)`,
                        })

                        let isdrag = false


                        dialogWrap.$('.ant-modal-header').addEventListener('mousedown', (e)=> {
                            isdrag = true
                        })
                        document.body.addEventListener('mousemove', (e)=> {
                            if(!isdrag) return

                            setStyle(dialogWrap, {
                                top: `calc(50% - 362px + ${mY += e.movementY}px)`,
                                left: `calc(70% + ${mX += e.movementX}px)`,
                            })
                        }, true)
                        dialogWrap.$('.ant-modal-header').addEventListener('mouseup', (e)=> {
                            isdrag = false
                        })
                    }


                    if(_ds.isHighlight
                       && ( (['.index_flexGrowView__IV3Bl','.index_container__63WZq','.index_control__C8NVb', '.index_container__fp054', '.index_main__dnpUj'].some(sel => an?.matches?.(sel)))
                           || an?.firstChild?.matches?.('.index_container__0mnyY')
                           || an?.classList?.contains('index_container__+Ha-u') )
                      ) {
                        const image2D = an.matches('canvas') ? [an] : an.$$('canvas')

                        image2D.forEach(item => {
                            setTimeout(() => {
                                item.style.transition = '.5s all'
                                item.style.filter = 'brightness(2)'
                            })

                        })

                    }

                    if(an?.matches?.('.index_mask__uUfCW')) {
                        const isTarget = ['放大', 'edit2d tracking编辑'].every((findTit) => {
                            return an.$$('.ant-menu-title-content').find(tit => tit.textContent == findTit)
                        })

                        if(isTarget) an.$$('.ant-menu-item').find(item => item.textContent === '放大').click()

                    }


                    if(an?.matches?.('.index_control__C8NVb')) {
                        an.$$('.index_posBtName__MUO2p')
                        .filter(item => item.textContent !== 'M')
                        .forEach(indicator => {
                            indicator.addEventListener('click', () => {
                                image2D_Zoom()
                            })
                        })

                        an.$('.index_channel-view__VH8ql').addEventListener('dblclick', () => {
                            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                                key: "'",
                                keyCode: 222,
                                code: "Quote",
                                bubbles: true,
                            }))
                        })
                    }

                    if(an?.matches?.('.index_exhibition-wrap__Su7aO')) image2D_Zoom()

                    if(an?.matches?.('.index_question__4Tm9w') || an?.matches?.('.index_QuestionTabs__kk6nt') && an.parentElement?.className !== 'index_trackingEditWrap__c2kEj') { //ctrl E 的面板不监视
                        (function resetTrasitionPattern() {
                            let btn = $('.index_cube-scale-translation-tracking__2BRIw > button:first-child')
                            if(btn && !btn.className.includes('index_translation-btn__+VcaE')) btn.click()
                        })()



                        if(an?.matches?.('.index_question__4Tm9w')) an = an.$('.index_QuestionTabs__kk6nt')

                        an.style.border = '2px solid red'

                        hiddenAttrTit()

                        Obs(an, (mrs) => {
                            mrs.some((mr) => {
                                if(mr.type === 'attributes' && mr.target.matches('.ant-radio-wrapper-checked') && mr.oldValue === 'ant-radio-wrapper') {
                                    // console.log('new select')
                                    $('.index_question__4Tm9w .ant-btn').click()
                                }
                            })
                        }, { childList: true, subtree: true, attributes: true, attributeOldValue: true})

                    }

                    if(!isObsToolBar) {
                        let toolBar = [...document.querySelectorAll('.toolBar_toolBar__q14CL')].find(item => item.innerHTML.includes('快捷属性') );
                        if(toolBar) {
                            ['展示映射'].some((title, i) => toolBar.$(`button[title="${title}"]`).click())

                            Obs(toolBar, (mrs) => {
                                // console.log(123, mrs)
                                if(mrs.find((mr) => mr.removedNodes[0]?.innerHTML.includes('自动外扩'))) return

                                ['显示其他框', '十字参考线', '展示映射'].some((title, i) => {
                                    let btn = $(`button[title="${title}"]`)
                                    setTimeout(() => { !btn?.className.includes('toolBar_active__PfJFI') ? btn?.click() : void 0 })
                                })


                            }, { childList: true, subtree: true})

                            isObsToolBar = true
                        }
                    }


                    if(an?.matches?.('.ant-message-notice') && an.innerText === '只允许切换锚点类型') an.remove()

                })
            })
        }, { childList: true, subtree: true})
    })


    function image2D_Zoom() {
        setTimeout(() => {
            document.body.dispatchEvent(new KeyboardEvent("keydown", {
                key: ";",
                keyCode: 186,
                code: "Semicolon",
                bubbles: true,
                cancelable: true
            }))
        }, 500)
    }

    function lockStabilize() {
        const btn_shape = $('.index_body-shape-tracking__NGXB3 button')
        if(btn_shape.textContent == '变形刚体') {
            btn_shape.click()
            setTimeout(() => lockStabilize())
        }
    }
}

function hiddenAttrTit() {
    const attrPanel = getAttrPanel()

    attrPanel.$$('.index_title__KB3CQ').forEach(titleWrap => titleWrap.remove())

    const tit_secondLevel = ['物体形态', '对象类型', '物体运动状态']
    attrPanel.$$('.index_title__Fzt5I')
    .filter(titleWrap => tit_secondLevel.find(tit => titleWrap.textContent.includes(tit)))
    .forEach(titleWrap => titleWrap.remove())
}

function getAttrPanel() {
    return $('.index_container__v2bdb .index_QuestionTabs__kk6nt')
}


function Obs(target, callBack, options = { childList: true, subtree: true, attributes: true, attributeOldValue: true}) {
    if(!target) return console.error('目标不存在')

    const ob = new MutationObserver(callBack);
    ob.observe(target, options);
    return ob
}

function $(selector) {
    const _this = Element.prototype.isPrototypeOf(this) ? this : document
    const sel = String(selector).trim();

    const id = /^#([^ +>~\[:]*)$/.exec(sel)?.[1]
    return (id && _this === document) ? _this.getElementById(id) : _this.querySelector(sel)
}

function $$(selector) {
    const _this = Element.prototype.isPrototypeOf(this) ? this : document
    return Array.from(_this.querySelectorAll(selector))
}

function setStyle() {
    [[Map, ()=> {
        const styleMap = arguments[0]
        for (const [el, styleObj] of styleMap) {
            !Array.isArray(el) ? setStyleObj(el, styleObj) : el.forEach((el) => setStyleObj(el, styleObj))
        }
    }], [Element, () => {
        const [el, styleObj] = arguments
        setStyleObj(el, styleObj)
    }], [Array, () => {
        const [els, styleObj] = arguments
        els.forEach((el) => setStyleObj(el, styleObj))
    }]].some(([O, fn]) => O.prototype.isPrototypeOf(arguments[0]) ? (fn(), true) : false)

    function setStyleObj(el, styleObj) {
        for (const attr in styleObj) {
            if (el.style[attr] !== undefined) {
                el.style[attr] = styleObj[attr]
            } else {
                //将key转为标准css属性名
                const formatAttr = attr.replace(/[A-Z]/, match => `-${match.toLowerCase()}`)
                console.error(el, `的 ${formatAttr} CSS属性设置失败!`)
            }
        }
    }
}

function createEl(elName, options) {
    const el = document.createElement(elName)
    for(let opt in options) {
        if(opt !== 'style') {
            el[opt] = options[opt]
        } else {
            let styles = options[opt]
            setStyle(el, styles)
        }
    }
    return el
}

/**
     * @description 展示消息框
     * @param {string} message 展示内容
     * @param {object} [config] 配置对象
     * @param {string} [config.type='default'] 内容类型(可选值:'default'、'success'、'warning'、'error')
     * @param {number} [showTime=3000] 展示时间
     * @param {string} [direction='top]' 展示的位置(可选值:'top'、'top left'、'left'、'top right'、'right'、'bottom'、'bottom left'、'bottom right')
     * @return {void}
     */
function showMessage(message, config) { //type = 'default', showTime = 3000, direction
    let MessageWrap = document.createElement('div')
    MessageWrap.className = 'messageWrap'
    setStyle(MessageWrap, {
        position: 'absolute',
        zIndex: '9999'
    })

    let MessageBox = document.createElement('div')
    MessageBox.innerText = message

    let closeBtn = document.createElement('div')
    closeBtn.textContent = '×'
    closeBtn.addEventListener('click', MessageBox.remove.bind(MessageBox)) //关闭消息提示

    setStyle(MessageBox, {
        position: 'relative',
        minWidth: '200px',
        marginTop: '5px',
        padding: '6px 50px',
        lineHeight: '25px',
        backgroundColor: 'pink',
        textAlign: 'center',
        fontSize: '16px',
        borderRadius: '5px',
        transition: 'all 1s'
    })

    setStyle(closeBtn, {
        position: 'absolute',
        top: '-3px',
        right: '3px',
        width: '15px',
        height: '15px',
        zIndex: '999',
        fontWeight: '800',
        fontSize: '15px',
        borderRadius: '5px',
        cursor: 'pointer',
        userSelect: 'none'
    })
    //控制方向
    switch(config?.direction) {
        case 'top': setStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
        case 'top left': setStyle(MessageWrap, {top: '1%', left: '.5%'}); break;
        case 'left': setStyle(MessageWrap, {top: '50%', left: '1%', transform: 'translateY(-50%)'}); break;
        case 'top right': setStyle(MessageWrap, {top: '1%', right: '.5%', }); break;
        case 'right': setStyle(MessageWrap, {top: '50%', right: '.5%', transform: 'translateY(-50%)'}); break;
        case 'bottom': setStyle(MessageWrap, {bottom: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
        case 'bottom left': setStyle(MessageWrap, {bottom: '1%'}); break;
        case 'bottom right': setStyle(MessageWrap, {bottom: '1%', right: '.5%'}); break;
        default: setStyle(MessageWrap, {top: '1%', left: '50%', transform: 'translateX(-50%)'}); break;
    }

    switch(config?.type) {
        case 'success': setStyle(MessageBox, {border: '1.5px solid rgb(225, 243, 216)', backgroundColor: 'rgb(240, 249, 235)', color: 'rgb(103, 194, 58)'}); break;
        case 'warning': setStyle(MessageBox, {border: '1.5px solid rgb(250, 236, 216)', backgroundColor: 'rgb(253, 246, 236)', color: 'rgb(230, 162, 60)'}); break;
        case 'error': setStyle(MessageBox, {border: '1.5px solid rgb(253, 226, 226)', backgroundColor: 'rgb(254, 240, 240)', color: 'rgb(245, 108, 108)'}); break;
        default: setStyle(MessageBox, {border: '1.5px solid rgba(202, 228, 255) ', backgroundColor: 'rgba(236, 245, 255)', color: 'rgb(64, 158, 255)'}); break;
    }

    MessageBox.appendChild(closeBtn)
    let oldMessageWrap = document.querySelector('.messageWrap')
    if(oldMessageWrap) {
        oldMessageWrap.appendChild(MessageBox)
    } else {
        MessageWrap.appendChild(MessageBox)
        document.body.appendChild(MessageWrap)
    }
    let ani = MessageBox.animate([
        {
            transform: "translate(0, -100%)" ,
            opacity: 0.3,
        },
        {
            transform: "translate(0, 18px)",
            opacity: 0.7,
            offset: 0.9,
        },
        {
            transform: "translate(0, 15px)",
            opacity: 1,
            offset: 1,
        },
    ], {
        duration: 300,
        fill: 'forwards',
        easing: 'ease-out',
    })

    //控制消失
    let timer = setTimeout(() => {
        ani.onfinish = () => {
            MessageBox.remove()
        }
        ani.reverse()
    }, (config?.showTime || 3000))

    //鼠标悬停时不清除,离开时重新计时
    MessageBox.addEventListener('mouseenter', () => clearTimeout(timer))
    MessageBox.addEventListener('mouseleave', () => {
        timer = setTimeout(() => {
            ani.reverse()
            ani.onfinish = () => {
                MessageBox.remove()
            }
        }, (config?.showTime || 3000))
    })
}


/**
2024/9/4
- 新增:【`】后退一帧(选中对象时默认切换关联帧)/一组缩略帧
- 新增:【空格】 或 【1】前进一帧(选中对象时默认切换关联帧)/一组缩略帧
- 新增:【鼠标中键】进入/退出 三视图编辑模式
- 新增:【Alt + `】后退一帧
- 新增:【Alt + 1】前进一帧
- 新增:【Q】逆时针旋转航向角
- 新增:【E】顺时针旋转航向角
- 新增:【Esc】删除对象
- 新增:【Z】取消选中当前对象
- 新增:个体属性面板自动保存
- 新增:首次进入数据包,勾选必要的工具栏选项
- 新增:进入三视图编辑模式,勾选必要的工具栏选项
- 新增:三视图编辑模式默认平移调整

2024/9/8
- 调整:删除对象快捷键由【Esc】改为【Ctrl + D】
- 优化:简化个体属性面板内容
- 新增:自动放大展示映射(在进入三视图编辑模式、切帧 时触发)
- 新增:双击映射图还原缩放比
- 新增:锁定稳定刚体
- 新增:点云全景模式下,右键映射图放大
- 新增:【B】映射图亮度增强

2024/9/13
- 调整:匹配平台最新链接

2024/9/14
- 新增:2D属性面板可拖动

2024/12/5
- 调整:目标切换置顶(审核便利)
- 调整:鼠标中键进入2D图通道模式
- 新增:【Tab】向后切换关联映射图
- 新增:【Shift Tab】向前切换关联映射图

2024/12/11
- 新增:行驶状态下,特殊属性的勾选方案
- 新增:【4】开关设置项[允许绘制到图像外]
- 新增:特殊属性中各种同类型属性的单选方案

2024/12/12
- 新增:通道模式下【Z】复制到下一帧,【Shift Z】复制到上一帧
- 适配:新规则,车灯属性调整

2024/12/18
- 调整:取消稳定刚体的锁定
- 新增:【5】开关设置项[选中不隐藏其他对象]
- 新增:2D属性面板默认下滑至私有属性区域
**/