源码安装
Makinohara / QC3D

// ==UserScript==
// @name         QC3D
// @namespace    http://tampermonkey.net/
// @version      2025.7.30.1
// @description  try to take over the world!
// @author       You
// @match        http://127.0.0.1:8081/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=0.1
// @grant        none
// @license MIT
// @copyright 2025, Makinohara (https://youhou8.com/users/Makinohara)
// @updateURL https://youhou8.com/meta/Makinohara/QC3D.meta.js
// ==/UserScript==
window.$ = Document.prototype.$ = Element.prototype.$ = $;
window.$$ = Document.prototype.$$ = Element.prototype.$$ = $$;


(function() {
    let _gd = window._gd = {
        get isDev() {
            return localStorage.getItem('isDev')
        },
        set isDev(newVal) {
            localStorage.setItem('isDev', newVal)
        }
    }

    waitEl()

    let roundCount = 1
    let appear = {}
    Obs(document, mrs => {
        // console.log(roundCount, mrs)
        mrs.forEach(mr => {
            ;[...mr.addedNodes].forEach(an => {
                if(!appear.logPanel && (appear.logPanel = $('#log-wrapper #btn-exit'))) {
                    appear.logPanel.click()
                    appear.logPanel = true
                }

                if(_gd.isDev === 'true' && !appear.sceneOption && an.matches?.('option[value="ceshi100"]')) {
                    let selector = $('#scene-selector')
                    selector.value = 'ceshi100'
                    selector.dispatchEvent(new Event('change'))

                    appear.sceneOption = true
                }
                if(_gd.isDev === 'true' && !appear.frameOption && an.matches?.('#frame-selector option') && $('#frame-selector').length > 1) {
                    let frameSelector = $('#frame-selector')
                    frameSelector.selectedIndex = 1
                    frameSelector.dispatchEvent(new Event('change'))

                    appear.frameOption = true
                }

                if(!appear.attrInput && $('input[title="attribute"]')) {
                    let attrEditor = $('#obj-editor #attr-editor')
                    let btnWrapper = createEl('div', {
                        style: {
                            display: 'flex',
                        }
                    })

                    ;['pick-up truck', 'suv', 'iveco','static'].forEach(text => {
                        let btn = createEl('div', {
                            textContent: text,
                            onclick: function() {
                                const input = $('input[title="attribute"]')
                                const point_num = /\D*(\d*)/.exec(input.value)[1]
                                setInputValue(input, `${text} ${point_num}`)
                            },
                            style: {
                                marginRight: '5px',
                                background: 'lightgray',
                                color: '#333',
                                borderRadius: '3px',
                                padding: '0px 3px',
                                cursor: 'pointer'
                            }
                        })
                        btnWrapper.append(btn)
                    })
                    attrEditor.insertAdjacentElement('afterend', btnWrapper)

                    appear.attrInput = true
                }

                if(!appear.boxInfo && $('#header #box')) {
                    let boxInfoWrapper = $('#header #box')

                    Obs(boxInfoWrapper, mrs => {
                        console.log(mrs)
                        let points = undefined
                        const isAppear_points = mrs.some(mr => {
                            return [...mr.addedNodes].some(an => {
                                if(an.matches?.('span[title="points"]')) {
                                    points = an.textContent
                                    return true
                                }

                            })
                        })
                        // debugger
                        if(!isAppear_points) return

                        let attrPanel = $('#obj-editor')
                        if(attrPanel && getComputedStyle(attrPanel).display !== 'none') {
                            let input = $('input[title="attribute"]')
                            let str = /(\D*)\d*/.exec(input.value)[1]
                            let value = `${str ? str: ''}${points}` //判断是否存在前缀,并添加
                            setInputValue(input, value)

                        }
//                         if(getComputedStyle(attrPanel).display !== 'none') {

//                         }

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

                    appear.boxInfo = true
                }
                // an.matches?.('div[id="^log-"]') && console.log(roundCount, an)
            })
        })

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


    //地线更改颜色和宽度
    function waitEl() {
        setTimeout (()=>{
            let bottomLine_x = document.querySelector('#x-view-manipulator #line-bottom')
            let bottomLine_y = document.querySelector('#y-view-manipulator #line-bottom')

            if (bottomLine_x) {
                let style = document.createElement('style')
                style.textContent = '.bottomLine { stroke-width: 2.5px; stroke: rgba(255, 255, 0, .5) !important}'
                document.body.append(style)

                bottomLine_x.classList.add('bottomLine')
                bottomLine_y.classList.add('bottomLine')

                return
            } else {
                waitEl()
            }
        })
    }


})();

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 Obs(target, callBack, options = { childList: true, subtree: true, attributes: true, attributeOldValue: true, attributeFilter: ['class', 'style']}) {
    if(!target) return console.error('目标不存在')

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

function clickTrigger(el, fn, button, moveThreshold = 0, isPreventDefault = true) {
    let movement = 0
    let allowTrigger = false
    let isRightdown = false

    el.addEventListener('mousedown', (e)=>{
        if(isPreventDefault) e.preventDefault()

        if(e.which !== button) return
        movement = 0
        isRightdown = true
        allowTrigger = true
    })
    el.addEventListener('mousemove', (e)=>{
        if(!isRightdown || (isRightdown && (movement+=(Math.sqrt(e.movementX**2 + e.movementY**2))) <= moveThreshold)) return

        allowTrigger && (allowTrigger = false)
    })
    el.addEventListener('mouseup', (e)=>{
        if(e.which !== button) return
        if(allowTrigger) fn(e)
        isRightdown = false
    })
}


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
}

function setInputValue(input, value) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
    nativeInputValueSetter.call(input, value);
    ['input', 'change'].forEach((event) => input.dispatchEvent(new Event(event, { bubbles: true })))
};

function setTextAreaValue(textarea, value) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set
    nativeInputValueSetter.call(textarea, value);
    ['input', 'change'].forEach((event) => textarea.dispatchEvent(new Event(event, { bubbles: true })))
};


/*
日志:
2025/7/29:
- 新增:地线加粗并常亮

2025/7/30:
- 新增:自动同步点云数量
- 新增:一系列的点云数前缀按钮
*/