// ==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:
- 新增:自动同步点云数量
- 新增:一系列的点云数前缀按钮
*/
Wrap
Beautify