活动介绍

$emit(update:xxx)

时间: 2023-11-21 09:55:34 浏览: 120
`$emit(update:xxx)`是Vue.js中的一个自定义事件,用于在子组件中向父组件传递数据。其中`xxx`是自定义的事件名称,可以根据需要进行更改。在子组件中使用`$emit(update:xxx)`触发该事件,并将需要传递的数据作为参数传入。在父组件中,可以通过在子组件上使用`.sync`修饰符来监听该事件,并在事件触发时更新父组件中的数据。以下是一个简单的示例: 子组件中触发自定义事件: ```javascript this.$emit('update:count', this.count) ``` 父组件中监听自定义事件: ```html <template> <div> <child-component :count.sync="parentCount"></child-component> <p>Parent Count: {{ parentCount }}</p> </div> </template> <script> import ChildComponent from './ChildComponent.vue' export default { components: { ChildComponent }, data() { return { parentCount: 0 } } } </script> ``` 在上面的示例中,子组件中触发了一个名为`update:count`的自定义事件,并将`this.count`作为参数传入。在父组件中,我们使用`.sync`修饰符将`parentCount`与子组件中的`count`进行双向绑定。当子组件中触发了`update:count`事件时,父组件中的`parentCount`会自动更新为子组件中的`count`。
阅读全文

相关推荐

<script setup lang="tsx"> import { DataTableSortState,NButton, NPopconfirm, NTag, SelectOption } from "naive-ui"; import { fetchGetChangeLedgerList,fetchBatchDeleteChange,fetchDeleteChange,clearDeptCache } from "@/service/api"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerSearch from "./modules/ledger-search.vue"; import { useBoolean } from "@sa/hooks"; import { canUpgradeRecord } from "@/constants/business"; import { ref, onMounted, watch } from "vue"; // import {useDepartment} from "@/hooks/common/useDepartment" import { exportXlsx, SheetData } from "@/utils/export-excel"; // 导入嵌入模态框 import { getFullDeptOptions } from "@/service/api/"; // 导入完整部门选项服务 import useTableSort from "@/hooks/common/useTableSort" import tableDownload from "@/components/common/table-Download.vue"; // 存储完整部门选项 const fullDeptOptions = ref<SelectOption[]>([]); // 加载完整部门列表 onMounted(async () => { fullDeptOptions.value = await getFullDeptOptions(); }); const appStore = useAppStore(); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetChangeLedgerList, showTotal: true, apiParams: { current: 1, size: 10, // if you want to use the searchParams in Form, you need to define the following properties, and the value is null // the value can not be undefined, otherwise the property in Form will not be reactive source: null, pecoName: null, peco: null, softwareChange: null, projectno: null, dept: null, canUpgrade: null, softResponsiblePerson: null, elecResponsiblePerson: null, changeResponsiblePerson: null, customerDemandTime: null, softCompletionTime:null, hardCompletionTime: null, fmtCustomerDemandTime:null, fmtSoftCompletionTime:null, fmtHardCompletionTime: null, }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 64, sorter: true, }, { key: "hwswcoId", title: $t("page.change.ledger.hwswcoId"), align: "center", minWidth: 50, sorter: true, render: (row) => ( {row.hwswcoId} ), }, { key: "source", title: $t("page.change.ledger.source"), align: "center", width: 50, sorter: true, }, { key: "pecoName", title: $t("page.change.ledger.pecoName"), align: "center", minWidth: 50, sorter: true, }, { key: "peco", title: $t("page.change.ledger.peco"), align: "center", minWidth: 50, sorter: true, }, { key: "softwareChange", title: $t("page.change.ledger.softwareChange"), align: "center", minWidth: 150, sorter: true, }, { key: "projectno", title: $t("page.change.ledger.projectno"), align: "center", minWidth: 50, sorter: true, }, { key: "dept", title: $t("page.change.ledger.dept"), align: "center", width: 130, sorter: true, render: (row) => { // 使用完整部门选项映射部门名称 if (row.dept && fullDeptOptions.value.length > 0) { const dept = fullDeptOptions.value.find( (option) => option.value === row.dept ); if (dept) { return ( <NTag type="info" class="dept-tag"> {dept.label} </NTag> ); } } return null; }, }, { key: "softResponsiblePersonName", title: $t("page.change.ledger.softResponsiblePersonName"), align: "center", minWidth: 50, sorter: true, }, { key: "elecResponsiblePersonName", title: $t("page.change.ledger.elecResponsiblePersonName"), align: "center", minWidth: 50, sorter: true, }, // { // key: "changeResponsiblePerson", // title: $t("page.change.ledger.changeResponsiblePerson"), // align: "center", // minWidth: 50, // }, { key: "canUpgrade", title: $t("page.change.ledger.canUpgrade"), align: "center", minWidth: 50, sorter: true, render: (row) => { console.log(row.canUpgrade) if (row.canUpgrade) { // console.log( row.canUpgrade, canUpgradeRecord[ // row.canUpgrade as Api.Change.canUpgrade // ]) const label = $t( canUpgradeRecord[ row.canUpgrade as Api.Change.canUpgrade ] ); return <NTag type="default">{label}</NTag>; } return null; }, }, { key: "fmtCustomerDemandTime", title: $t("page.change.ledger.customerDemandTime"), align: "center", minWidth: 50, sorter: true, }, { key: "fmtSoftCompletionTime", title: $t("page.change.ledger.softCompletionTime"), align: "center", minWidth: 50, sorter: true, }, { key: "fmtHardCompletionTime", title: $t("page.change.ledger.hardCompletionTime"), align: "center", minWidth: 50, sorter: true, }, // { // key: "fmtPlanCompletionTime", // title: $t("page.change.ledger.planCompletionTime"), // align: "center", // minWidth: 50, // }, { key: "operate", title: $t("common.operate"), align: "center", width: 130, render: (row) => ( {hasAuth("B_ChangeLedger_Upgrade") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open(${row.hwswco_upgrade_url}${row.hwswcoId}.html,"_blank")} > {$t("common.upgrade")} </NButton> ) : null} {hasAuth("B_ChangeLedger_Assign") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open(${row.hwswco_url}${row.hwswcoId}.html,"_blank")} > {$t("common.assign")} </NButton> ) : null} ), }, ], }); const { drawerVisible, operateType, editingData, handleAdd, handleEdit, handleClone, checkedRowKeys, onBatchDeleted, onDeleted, // closeDrawer } = useTableOperate(data, getData); // 定义排序器 const sorter = ref<DataTableSortState | null>(null); // 使用自定义钩子 const { sortTable } = useTableSort(data, sorter); // 处理排序器变化 const handleSorterChange = (newSorter: DataTableSortState) => { sorter.value = newSorter; }; const { hasAuth } = useAuth(); const { bool: uploadVisible, setTrue: openModal } = useBoolean(); async function handleBatchDelete() { // request const { error } = await fetchBatchDeleteChange({ ids: checkedRowKeys.value, }); if (!error) { onBatchDeleted(); } } async function handleDelete(id: number) { // request const { error } = await fetchDeleteChange({ id }); if (!error) { onDeleted(); } } function edit(id: number) { handleEdit(id); } function clone(id: number) { handleClone(id); } interface Emits { (e: "update-total", payload: number | undefined): void; } const emit = defineEmits<Emits>(); watch( () => mobilePagination.value.itemCount, (newVal, oldVal) => { emit("update-total", newVal); }, { deep: true }, ); async function handleDownload() { // 请求cosnt {data, error } = await fetchGetProblemList({size:xxx,current:1}) const { data:responseData, error } = await fetchGetChangeLedgerList({ size: checkedRowKeys.value.length, current: 1, ids: checkedRowKeys.value, }); if (error) { console.error("获取数据时出错:", error); alert("获取数据时发生错误,请稍后再试。"); return; } // 关键修改:将部门ID转换为部门名称 const processedRecords = responseData.records.map(record => { // 查找匹配的部门选项 const deptOption = fullDeptOptions.value.find( option => option.value === record.dept ); // 转换canUpgrade数值为文本标签 const upgradeLabel = canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord] ? $t(canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord]) : record.canUpgrade; // 未匹配时保留原值 // 返回新对象(保留原数据,替换dept和canUpgrade字段) return { ...record, dept: deptOption ? deptOption.label : record.dept, canUpgrade: upgradeLabel // 替换为转换后的文本 }; }); // 翻译表头 const titleMap = { hwswcoId: $t("page.change.ledger.hwswcoId"), source: $t("page.change.ledger.source"), pecoName: $t("page.change.ledger.pecoName"), peco: $t("page.change.ledger.peco"), softwareChange:$t("page.change.ledger.softwareChange"), projectno: $t("page.change.ledger.projectno"), dept: $t("page.change.ledger.dept"), softResponsiblePersonName: $t("page.change.ledger.softResponsiblePersonName"), elecResponsiblePersonName: $t("page.change.ledger.elecResponsiblePersonName"), canUpgrade: $t("page.change.ledger.canUpgrade"), // customerDemandTime: $t("page.change.ledger.customerDemandTime"), // softCompletionTime: $t("page.change.ledger.softCompletionTime"), // hardCompletionTime: $t("page.change.ledger.hardCompletionTime"), fmtCustomerDemandTime: $t("page.change.ledger.fmtCustomerDemandTime"), fmtSoftCompletionTime: $t("page.change.ledger.fmtSoftCompletionTime"), fmtHardCompletionTime: $t("page.change.ledger.fmtHardCompletionTime"), }; const headers = Object.keys(titleMap).map((key) => titleMap[key]); const sheetData = [headers]; processedRecords.forEach(record => { const row = Object.keys(titleMap).map(key => record[key]); sheetData.push(row); }); const sheetName = $t("page.change.ledger.title"); const sheets: SheetData[] = [ { sheetName: sheetName, data: sheetData, }, ]; exportXlsx(sheets, ${sheetName}); } // 在页面加载时获取最新数据 getData(); </script> <template> <LedgerSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <NCard :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper" > <template #header> {{ $t('page.change.ledger.title') }} <NTag type="info" round v-if="mobilePagination?.itemCount !== undefined"> {{ mobilePagination.itemCount }} </NTag> </template> <template #header-extra> <template #default> <NButton size="small" @click="handleDownload" type="primary" ghost> <template #icon> <icon-mdi-tray-download class="text-icon" :class="{ 'animate-spin': loading }" /> </template> {{ $t("common.download") }} </NButton> </template> </template> <NDataTable v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" :flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="(row) => row.id" :pagination="mobilePagination" class="sm:h-full" @update:sorter="handleSorterChange" /> </NCard> </template> <style scoped></style> 如果没是有选中任何id,则提示请选择需要导出的数据!

<script setup lang="tsx"> import { NButton, NPopconfirm, NTag, SelectOption } from "naive-ui"; import { fetchGetChangeLedgerList,fetchBatchDeleteChange,fetchDeleteChange,clearDeptCache } from "@/service/api"; import { $t } from "@/locales"; import { useAppStore } from "@/store/modules/app"; import { useTable, useTableOperate } from "@/hooks/common/table"; import { useAuth } from "@/hooks/business/auth"; import LedgerSearch from "./modules/ledger-search.vue"; import { useBoolean } from "@sa/hooks"; import { canUpgradeRecord } from "@/constants/business"; import { ref, onMounted, watch } from "vue"; // import {useDepartment} from "@/hooks/common/useDepartment" import { exportXlsx, SheetData } from "@/utils/export-excel"; // 导入嵌入模态框 import { getFullDeptOptions } from "@/service/api/"; // 导入完整部门选项服务 // 存储完整部门选项 const fullDeptOptions = ref<SelectOption[]>([]); // 加载完整部门列表 onMounted(async () => { fullDeptOptions.value = await getFullDeptOptions(); }); const appStore = useAppStore(); const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams, } = useTable({ apiFn: fetchGetChangeLedgerList, showTotal: true, apiParams: { current: 1, size: 10, // if you want to use the searchParams in Form, you need to define the following properties, and the value is null // the value can not be undefined, otherwise the property in Form will not be reactive source: null, pecoName: null, peco: null, softwareChange: null, projectno: null, dept: null, canUpgrade: null, softResponsiblePerson: null, elecResponsiblePerson: null, changeResponsiblePerson: null, customerDemandTime: null, softCompletionTime:null, hardCompletionTime: null, fmtCustomerDemandTime:null, fmtSoftCompletionTime:null, fmtHardCompletionTime: null, }, columns: () => [ { type: "selection", align: "center", width: 48, }, { key: "index", title: $t("common.index"), align: "center", width: 64, }, { key: "hwswcoId", title: $t("page.change.ledger.hwswcoId"), align: "center", minWidth: 50, render: (row) => ( {row.hwswcoId} ), }, { key: "source", title: $t("page.change.ledger.source"), align: "center", width: 50, }, { key: "pecoName", title: $t("page.change.ledger.pecoName"), align: "center", minWidth: 50, }, { key: "peco", title: $t("page.change.ledger.peco"), align: "center", minWidth: 50, }, { key: "softwareChange", title: $t("page.change.ledger.softwareChange"), align: "center", minWidth: 150, }, { key: "projectno", title: $t("page.change.ledger.projectno"), align: "center", minWidth: 50, }, { key: "dept", title: $t("page.change.ledger.dept"), align: "center", width: 130, render: (row) => { // 使用完整部门选项映射部门名称 if (row.dept && fullDeptOptions.value.length > 0) { const dept = fullDeptOptions.value.find( (option) => option.value === row.dept ); if (dept) { return ( <NTag type="info" class="dept-tag"> {dept.label} </NTag> ); } } return null; }, }, { key: "softResponsiblePersonName", title: $t("page.change.ledger.softResponsiblePersonName"), align: "center", minWidth: 50, }, { key: "elecResponsiblePersonName", title: $t("page.change.ledger.elecResponsiblePersonName"), align: "center", minWidth: 50, }, // { // key: "changeResponsiblePerson", // title: $t("page.change.ledger.changeResponsiblePerson"), // align: "center", // minWidth: 50, // }, { key: "canUpgrade", title: $t("page.change.ledger.canUpgrade"), align: "center", minWidth: 50, render: (row) => { console.log(row.canUpgrade) if (row.canUpgrade) { // console.log( row.canUpgrade, canUpgradeRecord[ // row.canUpgrade as Api.Change.canUpgrade // ]) const label = $t( canUpgradeRecord[ row.canUpgrade as Api.Change.canUpgrade ] ); return <NTag type="default">{label}</NTag>; } return null; }, }, { key: "fmtCustomerDemandTime", title: $t("page.change.ledger.customerDemandTime"), align: "center", minWidth: 50, }, { key: "fmtSoftCompletionTime", title: $t("page.change.ledger.softCompletionTime"), align: "center", minWidth: 50, }, { key: "fmtHardCompletionTime", title: $t("page.change.ledger.hardCompletionTime"), align: "center", minWidth: 50, }, // { // key: "fmtPlanCompletionTime", // title: $t("page.change.ledger.planCompletionTime"), // align: "center", // minWidth: 50, // }, { key: "operate", title: $t("common.operate"), align: "center", width: 130, render: (row) => ( {hasAuth("B_ChangeLedger_Upgrade") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open(${row.hwswco_upgrade_url}${row.hwswcoId}.html,"_blank")} > {$t("common.upgrade")} </NButton> ) : null} {hasAuth("B_ChangeLedger_Assign") ? ( <NButton type="primary" ghost size="small" onClick={() => window.open(${row.hwswco_url}${row.hwswcoId}.html,"_blank")} > {$t("common.assign")} </NButton> ) : null} ), }, ], }); const { drawerVisible, operateType, editingData, handleAdd, handleEdit, handleClone, checkedRowKeys, onBatchDeleted, onDeleted, // closeDrawer } = useTableOperate(data, getData); const { hasAuth } = useAuth(); const { bool: uploadVisible, setTrue: openModal } = useBoolean(); async function handleBatchDelete() { // request const { error } = await fetchBatchDeleteChange({ ids: checkedRowKeys.value, }); if (!error) { onBatchDeleted(); } } async function handleDelete(id: number) { // request const { error } = await fetchDeleteChange({ id }); if (!error) { onDeleted(); } } function edit(id: number) { handleEdit(id); } function clone(id: number) { handleClone(id); } interface Emits { (e: "update-total", payload: number | undefined): void; } const emit = defineEmits<Emits>(); watch( () => mobilePagination.value.itemCount, (newVal, oldVal) => { emit("update-total", newVal); }, { deep: true }, ); async function handleDownload() { // 请求cosnt {data, error } = await fetchGetProblemList({size:xxx,current:1}) const { data:responseData, error } = await fetchGetChangeLedgerList({ size: checkedRowKeys.value.length, current: 1, ids: checkedRowKeys.value, }); if (error) { console.error("获取数据时出错:", error); alert("获取数据时发生错误,请稍后再试。"); return; } // 关键修改:将部门ID转换为部门名称 const processedRecords = responseData.records.map(record => { // 查找匹配的部门选项 const deptOption = fullDeptOptions.value.find( option => option.value === record.dept ); // 转换canUpgrade数值为文本标签 const upgradeLabel = canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord] ? $t(canUpgradeRecord[record.canUpgrade as keyof typeof canUpgradeRecord]) : record.canUpgrade; // 未匹配时保留原值 // 返回新对象(保留原数据,替换dept和canUpgrade字段) return { ...record, dept: deptOption ? deptOption.label : record.dept, canUpgrade: upgradeLabel // 替换为转换后的文本 }; }); // 翻译表头 const titleMap = { hwswcoId: $t("page.change.ledger.hwswcoId"), source: $t("page.change.ledger.source"), pecoName: $t("page.change.ledger.pecoName"), peco: $t("page.change.ledger.peco"), softwareChange:$t("page.change.ledger.softwareChange"), projectno: $t("page.change.ledger.projectno"), dept: $t("page.change.ledger.dept"), softResponsiblePersonName: $t("page.change.ledger.softResponsiblePersonName"), elecResponsiblePersonName: $t("page.change.ledger.elecResponsiblePersonName"), canUpgrade: $t("page.change.ledger.canUpgrade"), // customerDemandTime: $t("page.change.ledger.customerDemandTime"), // softCompletionTime: $t("page.change.ledger.softCompletionTime"), // hardCompletionTime: $t("page.change.ledger.hardCompletionTime"), fmtCustomerDemandTime: $t("page.change.ledger.fmtCustomerDemandTime"), fmtSoftCompletionTime: $t("page.change.ledger.fmtSoftCompletionTime"), fmtHardCompletionTime: $t("page.change.ledger.fmtHardCompletionTime"), }; const headers = Object.keys(titleMap).map((key) => titleMap[key]); const sheetData = [headers]; processedRecords.forEach(record => { const row = Object.keys(titleMap).map(key => record[key]); sheetData.push(row); }); const sheetName = $t("page.change.ledger.title"); const sheets: SheetData[] = [ { sheetName: sheetName, data: sheetData, }, ]; exportXlsx(sheets, ${sheetName}); } // 在页面加载时获取最新数据 getData(); </script> <template> <LedgerSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <NCard :title="$t('page.change.ledger.title')" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper" > <template #header-extra> <template #default> <NButton size="small" @click="handleDownload" type="primary" ghost> <template #icon> <icon-mdi-tray-download class="text-icon" :class="{ 'animate-spin': loading }" /> </template> {{ $t("common.download") }} </NButton> </template> </template> <NDataTable v-model:checked-row-keys="checkedRowKeys" :columns="columns" :data="data" size="small" :flex-height="!appStore.isMobile" :scroll-x="962" :loading="loading" remote :row-key="(row) => row.id" :pagination="mobilePagination" class="sm:h-full" /> </NCard> </template> <style scoped></style> 在page.change.ledger.title旁边显示itemCount

void wizard::WsfEditor::SetTextFormat(size_t aStart, size_t aCount, const QTextCharFormat& aFmt) { // 将字节位置转换为Qt字符位置以正确处理中文字符 size_t qtStart = BytePosToQtPos(aStart); size_t qtEnd = BytePosToQtPos(aStart + aCount); size_t qtCount = qtEnd - qtStart; size_t end = std::min(qtStart + qtCount, mCurrentStylePos + mCurrentStyleLength); if (end >= mCurrentStylePos) { for (size_t i = std::max(mCurrentStylePos, qtStart) - mCurrentStylePos; i < end - mCurrentStylePos; ++i) { mFormatChanges[ut::cast_to_int(i)] = aFmt; } } } void wizard::WsfEditor::MergeTextFormat(size_t aStart, size_t aCount, const QTextCharFormat& aFmt) { // 将字节位置转换为Qt字符位置以正确处理中文字符 size_t qtStart = BytePosToQtPos(aStart); size_t qtEnd = BytePosToQtPos(aStart + aCount); size_t qtCount = qtEnd - qtStart; size_t end = std::min(qtStart + qtCount, mCurrentStylePos + mCurrentStyleLength); if (end >= mCurrentStylePos) { for (size_t i = std::max(mCurrentStylePos, qtStart) - mCurrentStylePos; i < end - mCurrentStylePos; ++i) { mFormatChanges[ut::cast_to_int(i)].merge(aFmt); } } } size_t wizard::WsfEditor::BytePosToQtPos(size_t aBytePos) { // 如果没有中文字符,字节位置和字符位置是相同的 if (aBytePos == 0) return 0; // 获取当前整个文档内容 QString fullText = toPlainText(); // 将文档转换为UTF-8字节流 std::string utf8Text = ToAscii(fullText); // 如果字节位置超出范围,返回文档末尾 if (aBytePos >= utf8Text.size()) { return fullText.length(); } // 使用二分查找找到对应的Qt字符位置 int left = 0, right = fullText.length(); while (left < right) { int mid = (left + right) / 2; // 获取从开始到mid位置的Qt字符串 QString partialText = fullText.left(mid); std::string partialUtf8 = ToAscii(partialText); if (partialUtf8.size() < aBytePos) { left = mid + 1; } else { right = mid; } } return left; }bool wizard::TextSource::ReadSource(bool aAlwaysFullyReload) { bool loaded(false); UtPath::StatData statData; GetFilePath().Stat(statData); if (statData.mStatType == UtPath::cDIRECTORY) { return false; } if (statData.mStatType == UtPath::cFILE) { if (statData.mFileSizeBytes < (size_t)cMAXIMUM_FILE_SIZE) { // After first read, ensure the parser isn't running. if (mLoaded) { GetWorkspace()->WaitForAbortParsing(); } if (UtTextDocument::ReadFile(GetFilePath())) { SetDeleted(false); SetModified(false); bool requiresParse = GetProject()->SourceReloaded(this); if (requiresParse) { GetProject()->TriggerReparse(); } loaded = true; } } else { const QString msg("The file %1 is too large to open. (%2 MB)."); const QString filePath(QString::fromUtf8(mFilePath.GetSystemPath().c_str())); const double sizeMb = (statData.mFileSizeBytes / 1000000.0); QMessageBox::warning(nullptr, "File too large", msg.arg(filePath).arg(sizeMb)); if (mLoaded) { SetModified(false); Unload(); } loaded = false; } mFileSignature.Update(mFilePath); } else if (statData.mStatType == UtPath::cSTAT_ERROR) { Clear(); Insert(0, "\0", 1); // null terminator SetDeleted(true); SetModified(false); loaded = true; } if (!mViews.empty()) { ReloadEditor(*mViews[0]->mEditorPtr, false, true, aAlwaysFullyReload); } if (loaded) { mPendingChanges.clear(); mAppliedChanges.clear(); } mLoaded = loaded; return loaded; }void wizard::TextSource::ReloadEditor(Editor& aEditor, bool aInitialLoad, bool aForceUnmodified, bool aForceReload) { aEditor.BeginNonUserAction(); if (aInitialLoad || aForceReload) { // Copy text to scintilla char* textPtr = GetText().GetPointer(0); aEditor.BeginNonUserAction(); aEditor.document()->setPlainText(QString::fromUtf8(textPtr)); QTextCursor cur = aEditor.textCursor(); cur.setPosition(0); aEditor.setTextCursor(cur); aEditor.EndNonUserAction(); if (aInitialLoad) { aEditor.document()->clearUndoRedoStacks(); aEditor.document()->setModified(false); } } else { // Diff this text document with scintilla's copy. Apply only the changes. std::string oldText = aEditor.ToAscii(aEditor.ToPlainText()); QVector<TextSourceChange> changes; DiffDocuments(oldText.c_str(), GetPointer(), changes); if (!changes.empty()) { int firstChangePos = -1; QTextCursor cur = aEditor.textCursor(); cur.beginEditBlock(); for (int i = 0; i < changes.size(); ++i) { const TextSourceChange& change = changes[i]; if (i == 0) firstChangePos = ut::cast_to_int(change.mPos); ApplyChangeToQt(changes[i], cur); } cur.endEditBlock(); if (!aForceUnmodified) { SetModified(true, true); } // scroll to first change if (firstChangePos != -1) { QTextCursor cur = aEditor.textCursor(); cur.setPosition(firstChangePos); aEditor.setTextCursor(cur); aEditor.centerCursor(); } } if (!mModified) { // aEditor.SendScintilla(SCI_SETSAVEPOINT); } } aEditor.EndNonUserAction(); aEditor.ReloadComplete(); }void wizard::TextSource::SaveAs() { UtPath dir = GetFilePath(); dir.Up(); QFileDialog dlg(nullptr, "Save File As", dir.GetSystemPath().c_str(), "*.*"); dlg.setDefaultSuffix("txt"); dlg.selectFile(GetFileName().c_str()); dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setFileMode(QFileDialog::AnyFile); if (dlg.exec()) { QStringList selFiles = dlg.selectedFiles(); if (selFiles.length() == 1) { // Ensure the source is ready WaitForSourceModifications(); std::string newFilePath = selFiles[0].toUtf8().toStdString(); std::ofstream outfile(newFilePath.c_str(), std::ios::binary); while (!outfile) { std::stringstream ss; ss << "Could not save file " + newFilePath + ", check that the file/directory is writable."; int bn = QMessageBox::warning(nullptr, "Could not save", ss.str().c_str(), "Retry", "Cancel"); if (bn != 0) { break; } } if (outfile) { WriteFile(outfile); outfile.close(); UtPath newFile(newFilePath); if (newFile.Stat() == UtPath::cFILE) { // WIZARD_TODO: Open the file for the user? // GetProject()->AddTransientFile(newFile); } emit TextSourceSignals::GetInstance().requestBackup(); } } } } void wizard::TextSource::CopyPathToClipboard() { QApplication::clipboard()->setText(QString::fromUtf8(GetSystemPath().c_str())); }void wizard::TextSource::ApplyChangeToQt(const TextSourceChange& aChange, QTextCursor& aCursor) { if (aCursor.document()->toPlainText().size() < static_cast<int>(aChange.mPos)) { // Adding at the end of the document aCursor.movePosition(QTextCursor::End); aCursor.insertText("\n"); } aCursor.setPosition(ut::cast_to_int(aChange.mPos)); aCursor.setPosition(ut::cast_to_int(aChange.mPos + aChange.mCharsRemoved), QTextCursor::KeepAnchor); // 正确处理UTF-8字符串到Qt的转换 aCursor.insertText(QString::fromUtf8(aChange.mText.c_str())); } void wizard::TextSource::FindIncludeLocations() { if (GetProject()) { GetProject()->FindIncludes(this); } } void wizard::TextSource::SaveAndStore() { SaveWithNotifications(); emit TextSourceSignals::GetInstance().requestBackup(); } void wizard::TextSource::BrowseContainingFolder() { UtPath path = mFilePath; path.Up(); QString pathStr("file:///"); pathStr += QString::fromUtf8(path.GetSystemPath().c_str()); QDesktopServices::openUrl(QUrl(pathStr)); }// 将Qt字符位置转换为对应的UTF-8字节位置 size_t wizard::Editor::QtPosToUtf8Pos(int aQtPos) { if (aQtPos <= 0) return 0; // 获取从开始到指定位置的文本 QTextCursor cur(document()); cur.setPosition(0); cur.setPosition(aQtPos, QTextCursor::KeepAnchor); QString partialText = cur.selectedText(); // 转换为UTF-8并返回字节长度 std::string utf8Text = ToAscii(partialText); return utf8Text.size(); } // 将UTF-8字节位置转换为对应的Qt字符位置 int wizard::Editor::Utf8PosToQtPos(size_t aUtf8Pos) { if (aUtf8Pos == 0) return 0; QString fullText = toPlainText(); std::string fullUtf8 = ToAscii(fullText); if (aUtf8Pos >= fullUtf8.size()) { return fullText.length(); } // 使用二分查找找到对应的Qt位置 int left = 0, right = fullText.length(); while (left < right) { int mid = (left + right) / 2; size_t midUtf8Pos = QtPosToUtf8Pos(mid); if (midUtf8Pos < aUtf8Pos) { left = mid + 1; } else { right = mid; } } return left; } std::string wizard::Editor::ToAscii(const QString& aText) { // 处理段落分隔符,然后转换为UTF-8 QString processedText = aText; processedText.replace(QChar(0x2029), QChar('\n')); // paragraph separator // 直接使用UTF-8编码保持中文字符 return processedText.toUtf8().toStdString(); } void wizard::Editor::DocContentsChange(int aFrom, int aCharsRemoves, int aCharsAdded) { if (mNonUserAction > 0) { return; } // 简化处理:对于任何变化,都重新同步整个文档内容 // 这避免了复杂的位置转换问题,特别是中英文混合的情况 mSourcePtr->HandleDelete(0, 0x0fffffff); QTextCursor cur(document()); cur.setPosition(0); cur.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QString newText = cur.selectedText(); // Convert to UTF-8 representation std::string text = ToAscii(newText); // 使用UTF-8字符串的实际字节长度 mSourcePtr->HandleInsert(0, text.size(), text.c_str()); }void wizard::ProjectWorkspace::NewProject() { // Use the location of the last opened project // otherwise use the user's home directory // else use the location of the executable QString newLocationDir = QDir::currentPath(); if (!QDir::current().exists()) { // Absolute path of the user's home directory newLocationDir = QDir::toNativeSeparators(QDir::homePath()); // Home directory // openLocationDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); // Documents directory if (newLocationDir.isEmpty()) { newLocationDir = QString::fromStdString(UtPath::GetExePath().GetSystemPath()); } } // Get the new project file name QString fileName = QFileDialog::getSaveFileName(nullptr, "New Project", newLocationDir, "AFSIM Project (*.afproj)"); if (!fileName.isEmpty() && // set the working directory to the new project's directory wizRunMgr.SetWorkingDirectoryToProject(fileName)) { // 使用UTF-8编码处理文件名 std::string utf8FileName = fileName.toUtf8().toStdString(); UtPath fileNamePath(utf8FileName); UtPath filePath = fileNamePath; filePath.Up(); Project* projectPtr = new Project(this); projectPtr->SetProjectLocation(filePath.GetSystemPath(), fileNamePath); } }wizard::Runner::Runner(int& argc, char* argv[], const char* aCompanyStr, const char* aCompanyDomainStr, const char* aProductNameStr, const char* aProductVersionStr) { // Get the executable path // Trim the name of the executable mBinDir = UtPath::GetExePath(); mBinDir.Up(); QApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); mApp = ut::make_unique<QApplication>(argc, argv); // 设置UTF-8编码支持中文 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); #endif mApp->setQuitOnLastWindowClosed(true); mApp->setWindowIcon(QIcon(":icons/wizard_logo_icon.png")); qApp->setOrganizationName(aCompanyStr); qApp->setOrganizationDomain(aCompanyDomainStr); qApp->setApplicationName(aProductNameStr); qApp->setApplicationDisplayName(aProductNameStr); qApp->setApplicationVersion(aProductVersionStr); }void Scanner::Init() { NewTokCb = nullptr; mNewTokenDataPtr = nullptr; EOL = '\n'; eofSym = 0; // ***Declarations Begin maxT = 58; noSym = 58; int i; for (i = 65; i <= 90; ++i) start.set(i, 1); for (i = 95; i <= 95; ++i) start.set(i, 1); for (i = 97; i <= 122; ++i) start.set(i, 1); for (i = 48; i <= 57; ++i) start.set(i, 31); for (i = 34; i <= 34; ++i) start.set(i, 7); start.set(46, 32); start.set(39, 9); start.set(59, 13); start.set(40, 14); start.set(41, 15); start.set(61, 33); start.set(33, 43); start.set(62, 44); start.set(60, 45); start.set(43, 34); start.set(45, 35); start.set(38, 20); start.set(124, 22); start.set(125, 24); start.set(44, 25); start.set(42, 46); start.set(47, 47); start.set(123, 38); start.set(58, 39); start.set(94, 40); start.set(91, 41); start.set(93, 42); start.set(Buffer::EoF, -1); keywords.set("do", 26); keywords.set("if", 27); keywords.set("for", 28); keywords.set("foreach", 29); keywords.set("in", 30); keywords.set("else", 31); keywords.set("while", 32); keywords.set("break", 33); keywords.set("continue", 34); keywords.set("return", 35); keywords.set("null", 36); keywords.set("NULL", 37); keywords.set("true", 38); keywords.set("false", 39); keywords.set("string", 40); keywords.set("int", 41); keywords.set("double", 42); keywords.set("char", 43); keywords.set("bool", 44); keywords.set("global", 45); keywords.set("static", 46); keywords.set("extern", 47); // ***Declarations End tvalLength = 128; tval = new cocochar_t[tvalLength]; // text of current token // COCO_HEAP_BLOCK_SIZE byte heap + pointer to next heap block heap = malloc(COCO_HEAP_BLOCK_SIZE + sizeof(void*)); firstHeap = heap; heapEnd = (void**)(((char*)heap) + COCO_HEAP_BLOCK_SIZE); *heapEnd = nullptr; heapTop = heap; if (sizeof(Token) > COCO_HEAP_BLOCK_SIZE) { ut::log::fatal() << "Too small COCO_HEAP_BLOCK_SIZE."; exit(1); } pos = -1; line = 1; col = 0; charPos = -1; oldEols = 0; NextCh(); #if !COCO_USE_INLINE_BUFFER bool hasUtf8Bom = false; if (ch == 0xEF) { // check optional byte order mark for UTF-8 NextCh(); int ch1 = ch; NextCh(); int ch2 = ch; if (ch1 == 0xBB && ch2 == 0xBF) { hasUtf8Bom = true; } else { // 不是UTF-8 BOM,回退到开始位置 buffer->SetPos(0); pos = -1; line = 1; col = 0; charPos = -1; NextCh(); } } // 默认启用UTF-8支持,无论是否有BOM // 这样可以支持没有BOM的UTF-8文件(包含中文的脚本文件) Buffer* oldBuf = buffer; buffer = new UTF8Buffer(buffer); if (hasUtf8Bom) { col = 0; charPos = -1; NextCh(); // 跳过BOM } else { // 重新开始读取,使用UTF8Buffer buffer->SetPos(0); pos = -1; line = 1; col = 0; charPos = -1; NextCh(); } delete oldBuf; oldBuf = nullptr; #endif pt = tokens = CreateToken(); // first token is a dummy }UsVal UsCtx::StringLiteral(UtScriptLanguage::Token* t) { std::string str(t->val + 1, t->val + t->len - 1); std::string unescapedStr; bool hasSlash = false; // 使用UTF-8安全的字符迭代 for (size_t i = 0; i < str.length(); ++i) { unsigned char c = static_cast<unsigned char>(str[i]); if (!hasSlash) { if (c == '\\') { hasSlash = true; } else { // 检查是否是UTF-8多字节字符的开始 if (c >= 0x80) // UTF-8多字节字符 { // 计算UTF-8字符的字节数 size_t utf8_len = 1; if ((c & 0xE0) == 0xC0) utf8_len = 2; // 110xxxxx else if ((c & 0xF0) == 0xE0) utf8_len = 3; // 1110xxxx else if ((c & 0xF8) == 0xF0) utf8_len = 4; // 11110xxx // 确保不会越界,并复制完整的UTF-8字符 if (i + utf8_len <= str.length()) { unescapedStr.append(str, i, utf8_len); i += utf8_len - 1; // -1因为for循环会++i } else { // 如果UTF-8字符不完整,按原样添加 unescapedStr += c; } } else { // ASCII字符,直接添加 unescapedStr += c; } } } else { switch (c) { case 'a': unescapedStr += '\a'; break; case 'b': unescapedStr += '\b'; break; case 'r': unescapedStr += '\r'; break; case 'f': unescapedStr += '\f'; break; case 'n': unescapedStr += '\n'; break; case 't': unescapedStr += '\t'; break; case 'v': unescapedStr += '\v'; break; case '\'': case '"': case '\\': unescapedStr += c; break; default: // 对于不认识的转义序列,保留反斜杠和字符 unescapedStr += '\\'; unescapedStr += c; break; } hasSlash = false; } } return CreateData(unescapedStr); }{ StatType fileType(cSTAT_ERROR); std::string sysPath = GetSystemPath(); #ifdef _WIN32 // 使用Unicode版本的文件检查 if (UtPathUnicode::FileExists(mPathString)) { fileType = cFILE; } else if (UtPathUnicode::DirectoryExists(mPathString)) { fileType = cDIRECTORY; } #else struct stat buff; if (0 == ::stat(sysPath.c_str(), &buff)) { if ((buff.st_mode & S_IFDIR) != 0) { fileType = cDIRECTORY; } else { fileType = cFILE; } } #endif return fileType; }// // CUI // // The Advanced Framework for Simulation, Integration, and Modeling (AFSIM) // // Copyright 2003-2015 The Boeing Company. All rights reserved. // // The use, dissemination or disclosure of data in this file is subject to // limitation or restriction. See accompanying README and LICENSE for details. // #include "UtPathUnicode.hpp" #include <sys/stat.h> #ifdef _WIN32 #include <io.h> #include <direct.h> #else #include <unistd.h> #include <sys/types.h> #include <dirent.h> #endif #ifdef _WIN32 std::wstring UtPathUnicode::Utf8ToWide(const std::string& aUtf8) { if (aUtf8.empty()) return std::wstring(); int wideSize = MultiByteToWideChar(CP_UTF8, 0, aUtf8.c_str(), -1, nullptr, 0); if (wideSize <= 0) return std::wstring(); std::wstring wide(wideSize - 1, 0); MultiByteToWideChar(CP_UTF8, 0, aUtf8.c_str(), -1, &wide[0], wideSize); return wide; } std::string UtPathUnicode::WideToUtf8(const std::wstring& aWide) { if (aWide.empty()) return std::string(); int utf8Size = WideCharToMultiByte(CP_UTF8, 0, aWide.c_str(), -1, nullptr, 0, nullptr, nullptr); if (utf8Size <= 0) return std::string(); std::string utf8(utf8Size - 1, 0); WideCharToMultiByte(CP_UTF8, 0, aWide.c_str(), -1, &utf8[0], utf8Size, nullptr, nullptr); return utf8; } std::string UtPathUnicode::AnsiToUtf8(const std::string& aAnsi) { if (aAnsi.empty()) return std::string(); // 先转换为宽字符 int wideSize = MultiByteToWideChar(CP_ACP, 0, aAnsi.c_str(), -1, nullptr, 0); if (wideSize <= 0) return aAnsi; std::wstring wide(wideSize - 1, 0); MultiByteToWideChar(CP_ACP, 0, aAnsi.c_str(), -1, &wide[0], wideSize); // 再转换为UTF-8 return WideToUtf8(wide); } #endif std::string UtPathUnicode::ToSystemPath(const std::string& aUtf8Path) { #ifdef _WIN32 // 在Windows上,将UTF-8路径转换为系统路径 // 对于文件系统操作,我们需要使用宽字符API return aUtf8Path; // 保持UTF-8格式,在具体的文件操作中转换 #else // 在Unix系统上,文件系统通常使用UTF-8 return aUtf8Path; #endif } std::string UtPathUnicode::FromSystemPath(const std::string& aSystemPath) { #ifdef _WIN32 // 假设系统路径可能是ANSI编码,转换为UTF-8 return AnsiToUtf8(aSystemPath); #else return aSystemPath; #endif } bool UtPathUnicode::FileExists(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); DWORD attributes = GetFileAttributesW(widePath.c_str()); return (attributes != INVALID_FILE_ATTRIBUTES) && !(attributes & FILE_ATTRIBUTE_DIRECTORY); #else struct stat buffer; return (stat(aUtf8Path.c_str(), &buffer) == 0) && S_ISREG(buffer.st_mode); #endif } bool UtPathUnicode::DirectoryExists(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); DWORD attributes = GetFileAttributesW(widePath.c_str()); return (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY); #else struct stat buffer; return (stat(aUtf8Path.c_str(), &buffer) == 0) && S_ISDIR(buffer.st_mode); #endif } bool UtPathUnicode::CreateDirectory(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); return CreateDirectoryW(widePath.c_str(), nullptr) != 0; #else return mkdir(aUtf8Path.c_str(), 0755) == 0; #endif } bool UtPathUnicode::DeleteFile(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); return DeleteFileW(widePath.c_str()) != 0; #else return unlink(aUtf8Path.c_str()) == 0; #endif } size_t UtPathUnicode::GetFileSize(const std::string& aUtf8Path) { #ifdef _WIN32 std::wstring widePath = Utf8ToWide(aUtf8Path); WIN32_FILE_ATTRIBUTE_DATA fileInfo; if (GetFileAttributesExW(widePath.c_str(), GetFileExInfoStandard, &fileInfo)) { LARGE_INTEGER size; size.HighPart = fileInfo.nFileSizeHigh; size.LowPart = fileInfo.nFileSizeLow; return static_cast<size_t>(size.QuadPart); } return 0; #else struct stat buffer; if (stat(aUtf8Path.c_str(), &buffer) == 0) { return static_cast<size_t>(buffer.st_size); } return 0; #endif } 这些代码是为了在QPlainTextEdit中支持中文进行的所有修改,但是目前高亮和自动补全有问题,提供的文件是高亮和自动补全的cpp实现,请协助我修改代码,给出每个需要修改的函数的完整实现,不要省略不需要修改的代码

# -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QButtonGroup, QCheckBox) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\BasicDemo") import ctypes from datetime import datetime from MvCameraControl_class import * from MvErrorDefine_const import * from CameraParams_header import * from PyUICBasicDemo import Ui_MainWindow import logging import platform import serial import socket import time from scipy import ndimage import skimage.measure from skimage.feature import ORB, match_descriptors import subprocess # 配置日志系统 logging.basicConfig( level=logging.DEBUG, # 设置为DEBUG级别获取更多信息 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" # 当前使用的样本路径 detection_history = [] # 检测历史记录 isGrabbing = False # 相机取流状态 isOpen = False # 相机打开状态 obj_cam_operation = None # 相机操作对象 frame_monitor_thread = None # 帧监控线程 sensor_monitor_thread = None # 传感器监控线程 sensor_controller = None # 传感器控制器 # ==================== 传感器通讯模块 ==================== class SensorController: def __init__(self): self.sensor_type = None # 'serial' 或 'ethernet' self.serial_conn = None self.socket_conn = None self.sensor_data = { 'tension': 0.0, # 布料张力 (N) 'speed': 0.0, # 布料速度 (m/s) 'temperature': 25.0, # 环境温度 (°C) 'humidity': 50.0 # 环境湿度 (%) } self.connected = False def connect(self, config): """连接传感器""" try: if config['type'] == 'serial': self.sensor_type = 'serial' self.serial_conn = serial.Serial( port=config['port'], baudrate=config['baudrate'], timeout=config['timeout'] ) logging.info(f"串口传感器已连接: {config['port']}") elif config['type'] == 'ethernet': self.sensor_type = 'ethernet' self.socket_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket_conn.connect((config['ip'], config['port'])) self.socket_conn.settimeout(config['timeout']) logging.info(f"以太网传感器已连接: {config['ip']}:{config['port']}") self.connected = True return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}") self.connected = False return False def disconnect(self): """断开传感器连接""" if self.serial_conn and self.serial_conn.is_open: self.serial_conn.close() if self.socket_conn: try: self.socket_conn.shutdown(socket.SHUT_RDWR) self.socket_conn.close() except: pass self.connected = False logging.info("传感器已断开") def read_data(self): """从传感器读取数据(模拟实现)""" if not self.connected: return None # 实际应用中应替换为真实传感器协议 if self.sensor_type == 'serial' and self.serial_conn: try: # 模拟串口数据读取 self.sensor_data = { 'tension': np.random.uniform(5.0, 20.0), 'speed': np.random.uniform(0.5, 2.5), 'temperature': 25.0 + np.random.uniform(-2, 2), 'humidity': 50.0 + np.random.uniform(-10, 10) } return self.sensor_data except serial.SerialException as e: logging.error(f"串口读取失败: {str(e)}") self.disconnect() return None elif self.sensor_type == 'ethernet' and self.socket_conn: try: # 模拟以太网数据读取 self.sensor_data = { 'tension': np.random.uniform(5.0, 20.0), 'speed': np.random.uniform(0.5, 2.5), 'temperature': 25.0 + np.random.uniform(-2, 2), 'humidity': 50.0 + np.random.uniform(-10, 10) } return self.sensor_data except (socket.timeout, socket.error) as e: logging.error(f"网络传感器读取失败: {str(e)}") self.disconnect() return None return None def send_command(self, command): """向传感器发送控制命令""" if not self.connected: return False try: if self.sensor_type == 'serial' and self.serial_conn: # 实际应用中应根据传感器协议构造命令 self.serial_conn.write(command.encode()) return True elif self.sensor_type == 'ethernet' and self.socket_conn: self.socket_conn.send(command.encode()) return True return False except Exception as e: logging.error(f"发送传感器命令失败: {str(e)}") return False # 帧监控线程 class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True def run(self): while self.running: if self.cam_operation: status = self.cam_operation.get_frame_status() frame_text = "有帧" if status.get('current_frame', False) else "无帧" self.frame_status.emit(f"帧状态: {frame_text}") QThread.msleep(500) def stop(self): self.running = False # 传感器数据监控线程 class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def __init__(self, sensor_controller): super().__init__() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) QThread.msleep(1000) # 每秒更新一次 def stop极(self): self.running = False # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): """ 优化版布料印花检测算法,增加图像配准和特征匹配 :param sample_image_path: 合格样本图像路径 :param test_image: 测试图像 (numpy数组) :param threshold: 差异阈值 :param sensor_data: 传感器数据字典 :return: 是否合格,差异值,标记图像 """ # 根据传感器数据动态调整阈值 if sensor_data: # 速度越高,允许的差异阈值越大 speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) # 温度/湿度影响 env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: # 读取样本图像 sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None # 确保测试图像是灰度图 if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() # 1. 图像预处理 sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) # 2. 图像配准(解决位置偏移问题) try: # 使用ORB特征匹配进行图像配准 orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: # 使用BFMatcher进行特征匹配 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: # 提取匹配点的坐标 src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) # 计算单应性矩阵 H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: # 应用变换 aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image # 3. 确保图像大小一致 try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None # 4. 计算结构相似性(SSIM)和差异 ssim_score, ssim_diff = skimage.measure.compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) ssim_diff = (1 - ssim_diff) * 255 # 转换为0-255范围 # 5. 计算绝对差异 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) # 6. 组合差异(SSIM差异对结构变化敏感,绝对差异对亮度变化敏感) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) # 7. 二值化差异 _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) # 8. 形态学操作去除噪声 kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) # 9. 计算差异比例 diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels # 10. 判断是否合格 is_qualified = diff_ratio <= adjusted_threshold # 11. 创建标记图像 marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] # 红色标记缺陷 # 12. 标记大面积缺陷区域 labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: # 只标记大于50像素的区域 y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): """传感器控制的质量检测主流程""" global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("传感器控制的质量检测启动") # 1. 检查传感器连接 if not sensor_controller or not sensor_controller.connected: QMessageBox.warning(mainWindow, "传感器错误", "传感器未连接,请先连接传感器!", QMessageBox.Ok) return # 2. 读取传感器数据 sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器错误", "无法读取传感器数据!", QMessageBox.Ok) return # 3. 根据传感器数据调整生产参数 adjust_production_parameters(sensor_data) # 4. 执行图像捕获和检测 check_print_with_sensor(sensor_data) def adjust_production_parameters(sensor_data): """根据传感器数据调整生产参数""" global obj_cam_operation # 示例:根据张力调整相机参数 tension = sensor_data['tension'] # 张力过大时增加曝光时间 if tension > 15.0 and obj_cam_operation: logging.info(f"高张力({tension}N)环境,增加曝光时间") try: current_exposure = obj_cam_operation.exposure_time new_exposure = min(current_exposure * 1.2, 100000) # 增加20%,上限100ms obj_cam_operation.set_exposure(new_exposure) logging.info(f"曝光时间调整为: {new_exposure}us") except Exception as e: logging.error(f"调整曝光失败: {str(e)}") # 速度过快时降低图像分辨率 speed = sensor_data['speed'] if speed > 2.0 and obj_cam_operation: logging.info(f"高速度({speed}m/s)环境,降低分辨率") try: # 实际应用中应调用相机SDK的分辨率设置 # 这里仅为示例 pass except Exception as e: logging.error(f"调整分辨率失败: {str(e)}") # 发送控制命令到传感器 if tension < 5.0 and sensor_controller: sensor_controller.send_command("INCREASE_TENSION") logging.info("发送增加张力命令") elif tension > 18.0 and sensor_controller: sensor_controller.send_command("DECREASE_TENSION") logging.info("发送减少张力命令") # 布料印花检测函数(使用优化算法) def check_print_with_sensor(sensor_data=None): """ 使用优化算法检测布料印花是否合格 """ global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") # 1. 检查相机状态 if not isGrabbing: logging.warning("相机未取流") QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 2. 检查相机操作对象 if not obj_cam_operation: logging.error("相机操作对象未初始化") QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return # 3. 检查样本路径 if not current_sample_path or not os.path.exists(current_sample_path): logging.warning(f"无效样本路径: {current_sample_path}") QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return # 使用进度对话框防止UI阻塞 progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: # 4. 获取当前帧 logging.info("尝试获取当前帧") test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: logging.warning("获取当前帧失败") QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return # 5. 获取差异度阈值 diff_threshold = ui.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) # 6. 执行检测 is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) # 检查返回结果是否有效 if is_qualified is None: logging.error("检测函数返回无效结果") QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) # 7. 更新UI update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() else: logging.warning("标记图像为空") # 8. 记录检测结果 detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() # 更新检测结果显示 def update_diff_display(diff_ratio, is_qualified): """ 更新差异度显示控件 """ # 更新当前差异度显示 ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 根据合格状态设置颜色 if is_qualified: ui.lblDiffStatus.setText("状态: 合格") ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: ui.lblDiffStatus.setText("状态: 不合格") ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") # 更新差异度阈值显示 def update_diff_threshold(value): """ 当滑块值改变时更新阈值显示 """ ui.lblDiffValue.setText(f"{value}%") # 保存标准样本函数 def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有有效图像 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 读取上次使用的路径 settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) # 创建默认文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" # 弹出文件保存对话框 file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: logging.info("用户取消了图像保存操作") return # 用户取消保存 # 处理文件扩展名 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: # 根据选择的过滤器添加扩展名 if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: # 默认使用BMP格式 file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 根据扩展名设置保存格式 format_mapping = { ".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg" } save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) logging.info(f"创建目录: {directory}") except OSError as e: error_msg = f"无法创建目录 {directory}: {str(e)}" QMessageBox.critical(mainWindow, "目录创建错误", error_msg, QMessageBox.Ok) return # 保存当前帧作为标准样本 try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: success_msg = f"标准样本已保存至:\n{file_path}" QMessageBox.information(mainWindow, "成功", success_msg, QMessageBox.Ok) # 更新当前样本路径 current_sample_path = file_path update_sample_display() # 保存当前目录 settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: error_msg = f"保存图像时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "异常错误", error_msg, QMessageBox.Ok) logging.exception("保存样本图像时发生异常") # 预览当前样本 def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 使用安全方法读取图像 img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) # 更新样本路径显示 def update_sample_display(): global current_sample_path if current_sample_path: ui.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") ui.lblSamplePath.setToolTip(current_sample_path) ui.bnPreviewSample.setEnabled(True) else: ui.lblSamplePath.setText("当前样本: 未设置样本") ui.bnPreviewSample.setEnabled(False) # 更新历史记录显示 def update_history_display(): global detection_history ui.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): # 显示最近10条记录 timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" ui.cbHistory.addItem(f"[{timestamp}] {status} - 差异: {ratio}") # 获取选取设备信息的索引,通过[]之间的字符去解析 def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() # 将返回的错误码转换为十六进制显示 def ToHexStr(num): """将错误码转换为十六进制字符串""" # 处理非整数输入 if not isinstance(num, int): try: # 尝试转换为整数 num = int(num) except: # 无法转换时返回类型信息 return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" # 处理负数 if num < 0: num = num + 2 ** 32 # 转换为十六进制 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr # 绑定下拉列表至设备信息索引 def xFunc(event): global nSelCamIndex nSelCamIndex = TxtWrapBy("[", "]", ui.ComboDevices.get()) # Decoding Characters def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') # Chinese characters except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str # ch:枚举相机 | en:enum devices def enum_devices(): global deviceList global obj_cam_operation deviceList = MV_CC_DEVICE_INFO_LIST() n_layer_type = (MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE) ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != 0: strError = "Enum devices fail! ret = :" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "Info", "Find no device", QMessageBox.Ok) return ret print("Find %d devices!" % deviceList.nDeviceNum) devList = [] for i in range(0, deviceList.nDeviceNum): mvcc_dev_info = cast(deviceList.pDeviceInfo[i], POINTER(MV_CC_DEVICE_INFO)).contents if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE or mvcc_dev_info.nTLayerType == MV_GENTL_GIGE_DEVICE: print("\ngige device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stGigEInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) nip1 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0xff000000) >> 24) nip2 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x00ff0000) >> 16) nip3 = ((mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x0000ff00) >> 8) nip4 = (mvcc_dev_info.SpecialInfo.stGigEInfo.nCurrentIp & 0x000000ff) print("current ip: %d.%d.%d.%d " % (nip1, nip2, nip3, nip4)) devList.append( "[" + str(i) + "]GigE: " + user_defined_name + " " + model_name + "(" + str(nip1) + "." + str( nip2) + "." + str(nip3) + "." + str(nip4) + ")") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: print("\nu3v device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stUsb3VInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stUsb3VInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]USB: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_CAMERALINK_DEVICE: print("\nCML device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCMLInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCMLInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]CML: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_CXP_DEVICE: print("\nCXP device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stCXPInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stCXPInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: "+strSerialNumber) devList.append("[" + str(i) + "]CXP: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") elif mvcc_dev_info.nTLayerType == MV_GENTL_XOF_DEVICE: print("\nXoF device: [%d]" % i) user_defined_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chUserDefinedName) model_name = decoding_char(mvcc_dev_info.SpecialInfo.stXoFInfo.chModelName) print("device user define name: " + user_defined_name) print("device model name: " + model_name) strSerialNumber = "" for per in mvcc_dev_info.SpecialInfo.stXoFInfo.chSerialNumber: if per == 0: break strSerialNumber = strSerialNumber + chr(per) print("user serial number: " + strSerialNumber) devList.append("[" + str(i) + "]XoF: " + user_defined_name + " " + model_name + "(" + str(strSerialNumber) + ")") ui.ComboDevices.clear() ui.ComboDevices.addItems(devList) ui.ComboDevices.setCurrentIndex(0) # ch:打开相机 | en:open device def open_device(): global deviceList global nSelCamIndex global obj_cam_operation global isOpen global frame_monitor_thread if isOpen: QMessageBox.warning(mainWindow, "Error", 'Camera is Running!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = ui.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", 'Please select a camera!', QMessageBox.Ok) return MV_E_CALLORDER obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "Open device failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(ui.statusBar.showMessage) frame_monitor_thread.start() # ch:开始取流 | en:Start grab image def start_grabbing(): global obj_cam_operation global isGrabbing ret = obj_cam_operation.start_grabbing(ui.widgetDisplay.winId()) if ret != 0: strError = "Start grabbing failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # ch:停止取流 | en:Stop grab image def stop_grabbing(): global obj_cam_operation global isGrabbing ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "Stop grabbing failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() # ch:关闭设备 | Close device def close_device(): global isOpen global isGrabbing global obj_cam_operation global frame_monitor_thread # 停止帧监控线程 if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() # ch:设置触发模式 | en:set trigger mode def set_continue_mode(): ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "Set continue mode failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: ui.radioContinueMode.setChecked(True) ui.radioTriggerMode.setChecked(False) ui.bnSoftwareTrigger.setEnabled(False) # ch:设置软触发模式 | en:set software trigger mode def set_software_trigger_mode(): ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "Set trigger mode failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: ui.radioContinueMode.setChecked(False) ui.radioTriggerMode.setChecked(True) ui.bnSoftwareTrigger.setEnabled(isGrabbing) # ch:设置触发命令 | en:set trigger software def trigger_once(): ret = obj_cam_operation.trigger_once() if ret != 0: strError = "TriggerSoftware failed ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) # 保存图像对话框 def save_image_dialog(): """ 打开保存图像对话框并保存当前帧 """ global isGrabbing, obj_cam_operation # 检查相机状态 if not isGrabbing: QMessageBox.warning(mainWindow, "相机未就绪", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有有效图像 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 读取上次使用的路径 settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) # 创建默认文件名 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"capture_{timestamp}" # 弹出文件保存对话框 file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存图像", os.path.join(last_dir, default_filename), # 初始路径 "BMP 图像 (*.bmp);;JPEG 图像 (*.jpg);;PNG 图像 (*.png);;TIFF 图像 (*.tiff);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) # 用户取消操作 if not file_path: logging.info("用户取消了图像保存操作") return # 处理文件扩展名 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: # 根据选择的过滤器添加扩展名 if "BMP" in selected_filter: file_path += ".bmp" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" elif "PNG" in selected_filter: file_path += ".png" elif "TIFF" in selected_filter: file_path += ".tiff" else: # 默认使用BMP格式 file_path += ".bmp" # 确定保存格式 format_mapping = { ".bmp": "bmp", ".jpg": "jpg", ".jpeg": "jpg", ".png": "png", ".tiff": "tiff", ".tif": "tiff" } file_extension = os.path.splitext(file_path)[1].lower() save_format = format_mapping.get(file_extension, "bmp") # 确保目录存在 directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录错误", f"无法创建目录:\n{str(e)}", QMessageBox.Ok) return # 保存图像 try: ret = obj_cam_operation.save_image(file_path, save_format) if ret == MV_OK: QMessageBox.information(mainWindow, "保存成功", f"图像已保存至:\n{file_path}", QMessageBox.Ok) logging.info(f"图像保存成功: {file_path}") # 保存当前目录 settings.setValue("last_save_dir", os.path.dirname(file_path)) else: error_msg = f"保存失败! 错误代码: {hex(ret)}" QMessageBox.warning(mainWindow, "保存失败", error_msg, QMessageBox.Ok) logging.error(f"图像保存失败: {file_path}, 错误代码: {hex(ret)}") except Exception as e: QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) logging.exception(f"保存图像时发生异常: {file_path}") def is_float(str): try: float(str) return True except ValueError: return False # ch: 获取参数 | en:get param def get_param(): try: # 调用方法获取参数 ret = obj_cam_operation.get_parameters() # 记录调用结果(调试用) logging.debug(f"get_param() 返回: {ret} (类型: {type(ret)})") # 处理错误码 if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: # 成功获取参数后更新UI ui.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) ui.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) ui.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) # 记录成功信息 logging.info("成功获取相机参数") except Exception as e: # 处理所有异常 error_msg = f"获取参数时发生错误: {str(e)}" logging.error(error_msg) QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) # ch: 设置参数 | en:set param def set_param(): frame_rate = ui.edtFrameRate.text() exposure = ui.edtExposureTime.text() gain = ui.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: # 使用正确的参数顺序和关键字 ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: logging.info("参数设置成功") return MV_OK except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" logging.error(error_msg) QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) return MV_E_STATE # ch: 设置控件状态 | en:set enable status def enable_controls(): global isGrabbing global isOpen # 先设置group的状态,再单独设置各控件状态 ui.groupGrab.setEnabled(isOpen) ui.groupParam.setEnabled(isOpen) ui.bnOpen.setEnabled(not isOpen) ui.bnClose.setEnabled(isOpen) ui.bnStart.setEnabled(isOpen and (not isGrabbing)) ui.bnStop.setEnabled(isOpen and isGrabbing) ui.bnSoftwareTrigger.setEnabled(isGrabbing and ui.radioTriggerMode.isChecked()) ui.bnSaveImage.setEnabled(isOpen and isGrabbing) # 添加检测按钮控制 ui.bnCheckPrint.setEnabled(isOpen and isGrabbing) ui.bnSaveSample.setEnabled(isOpen and isGrabbing) ui.bnPreviewSample.setEnabled(bool(current_sample_path)) class MainWindow(QMainWindow): def __init__(self): super().__init__() # 创建UI实例 self.ui = Ui_MainWindow() self.ui.setupUi(self) def closeEvent(self, event): """重写关闭事件,执行清理操作""" logging.info("主窗口关闭,执行清理...") # 关闭设备 close_device() # 断开传感器 disconnect_sensor() event.accept() if __name__ == "__main__": # 初始化UI app = QApplication(sys.argv) # 创建主窗口实例 mainWindow = MainWindow() # 扩大主窗口尺寸 mainWindow.resize(1200, 800) # 宽度1200,高度800 # 创建工具栏 toolbar = mainWindow.addToolBar("检测工具") # 添加检测按钮 mainWindow.ui.bnCheckPrint = QPushButton("检测印花质量") toolbar.addWidget(mainWindow.ui.bnCheckPrint) # 添加保存样本按钮 mainWindow.ui.bnSaveSample = QPushButton("保存标准样本") toolbar.addWidget(mainWindow.ui.bnSaveSample) # 添加预览样本按钮 mainWindow.ui.bnPreviewSample = QPushButton("预览样本") toolbar.addWidget(mainWindow.ui.bnPreviewSample) # 添加历史记录下拉框 mainWindow.ui.cbHistory = QComboBox() mainWindow.ui.cbHistory.setMinimumWidth(300) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(mainWindow.ui.cbHistory) # 添加当前样本显示标签 mainWindow.ui.lblSamplePath = QLabel("当前样本: 未设置样本") status_bar = mainWindow.statusBar() status_bar.addPermanentWidget(mainWindow.ui.lblSamplePath) # === 新增差异度调整控件 === # 创建右侧面板容器 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 创建差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) # 差异度阈值控制 mainWindow.ui.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") mainWindow.ui.sliderDiffThreshold = QSlider(Qt.Horizontal) mainWindow.ui.sliderDiffThreshold.setRange(0, 100) # 0-100% mainWindow.ui.sliderDiffThreshold.setValue(5) # 默认5% mainWindow.ui.lblDiffValue = QLabel("5%") # 当前差异度显示 mainWindow.ui.lblCurrentDiff = QLabel("当前差异度: -") mainWindow.ui.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") # 差异度状态指示器 mainWindow.ui.lblDiffStatus = QLabel("状态: 未检测") mainWindow.ui.lblDiffStatus.setStyleSheet("font-size: 12px;") # 布局控件 diff_layout.addWidget(mainWindow.ui.lblDiffThreshold) diff_layout.addWidget(mainWindow.ui.sliderDiffThreshold) diff_layout.addWidget(mainWindow.ui.lblDiffValue) diff_layout.addWidget(mainWindow.ui.lblCurrentDiff) diff_layout.addWidget(mainWindow.ui.lblDiffStatus) # 添加差异度组到右侧布局 right_layout.addWidget(diff_group) # === 新增传感器控制面板 === sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) # 传感器类型选择 sensor_type_layout = QHBoxLayout() mainWindow.ui.lblSensorType = QLabel("传感器类型:") mainWindow.ui.cbSensorType = QComboBox() mainWindow.ui.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(mainWindow.ui.lblSensorType) sensor_type_layout.addWidget(mainWindow.ui.cbSensorType) # 串口参数 mainWindow.ui.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(mainWindow.ui.serialGroup) mainWindow.ui.lblComPort = QLabel("端口:") mainWindow.ui.cbComPort = QComboBox() # 获取可用串口 (Windows) if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] mainWindow.ui.cbComPort.addItems(ports) mainWindow.ui.lblBaudrate = QLabel("波特率:") mainWindow.ui.cbBaudrate = QComboBox() mainWindow.ui.cbBaudrate.addItems(["9600", "19200", "38400", "57600", "115200"]) mainWindow.ui.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(mainWindow.ui.lblComPort) serial_layout.addWidget(mainWindow.ui.cbComPort) serial_layout.addWidget(mainWindow.ui.lblBaudrate) serial_layout.addWidget(mainWindow.ui.cbBaudrate) # 以太网参数 mainWindow.ui.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(mainWindow.ui.ethernetGroup) mainWindow.ui.lblIP = QLabel("IP地址:") mainWindow.ui.edtIP = QLineEdit("192.168.1.100") mainWindow.ui.lblPort = QLabel("端口:") mainWindow.ui.edtPort = QLineEdit("502") ethernet_layout.addWidget(mainWindow.ui.lblIP) ethernet_layout.addWidget(mainWindow.ui.edtIP) ethernet_layout.addWidget(mainWindow.ui.lblPort) ethernet_layout.addWidget(mainWindow.ui.edtPort) # 连接/断开按钮 mainWindow.ui.bnConnectSensor = QPushButton("连接传感器") mainWindow.ui.bnDisconnectSensor = QPushButton("断开传感器") mainWindow.ui.bnDisconnectSensor.setEnabled(False) # 传感器数据显示 mainWindow.ui.lblSensorData = QLabel("传感器数据: 未连接") mainWindow.ui.lblSensorData.setStyleSheet("font-size: 10pt;") # 添加到布局 sensor_layout.addLayout(sensor_type_layout) sensor_layout.addWidget(mainWindow.ui.serialGroup) sensor_layout.addWidget(mainWindow.ui.ethernetGroup) sensor_layout.addWidget(mainWindow.ui.bnConnectSensor) sensor_layout.addWidget(mainWindow.ui.bnDisconnectSensor) sensor_layout.addWidget(mainWindow.ui.lblSensorData) # 添加到右侧面板 right_layout.addWidget(sensor_panel) # 添加拉伸项使控件靠上 right_layout.addStretch(1) # 创建停靠窗口 dock = QDockWidget("检测控制面板", mainWindow) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) mainWindow.addDockWidget(Qt.RightDockWidgetArea, dock) # === 差异度调整功能实现 === # 更新差异度阈值显示 def update_diff_threshold(value): mainWindow.ui.lblDiffValue.setText(f"{value}%") # 连接滑块信号 mainWindow.ui.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) # 更新检测结果显示 def update_diff_display(diff_ratio, is_qualified): # 更新当前差异度显示 mainWindow.ui.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") # 根据合格状态设置颜色 if is_qualified: mainWindow.ui.lblDiffStatus.setText("状态: 合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.ui.lblDiffStatus.setText("状态: 不合格") mainWindow.ui.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") # 绑定按钮事件 mainWindow.ui.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.ui.bnSaveSample.clicked.connect(save_sample_image) mainWindow.ui.bnPreviewSample.clicked.connect(preview_sample) # 传感器类型切换 def update_sensor_ui(index): mainWindow.ui.serialGroup.setVisible(index == 0) mainWindow.ui.ethernetGroup.setVisible(index == 1) mainWindow.ui.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) # 初始显示串口 # 传感器连接 def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.ui.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.ui.cbComPort.currentText(), 'baudrate': int(mainWindow.ui.cbBaudrate.currentText()), 'timeout': 1.0 } else: # 以太网 config = { 'type': 'ethernet', 'ip': mainWindow.ui.edtIP.text(), 'port': int(mainWindow.ui.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.ui.bnConnectSensor.setEnabled(False) mainWindow.ui.bnDisconnectSensor.setEnabled(True) # 启动传感器数据监控线程 sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() # 传感器断开 def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.ui.bnConnectSensor.setEnabled(True) mainWindow.ui.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.ui.lblSensorData.setText("传感器数据: 未连接") mainWindow.ui.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.ui.bnDisconnectSensor.clicked.connect(disconnect_sensor) def update_sensor_display(data): text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.ui.lblSensorData.setText(text) # 绑定其他按钮事件 mainWindow.ui.bnEnum.clicked.connect(enum_devices) mainWindow.ui.bnOpen.clicked.connect(open_device) mainWindow.ui.bnClose.clicked.connect(close_device) mainWindow.ui.bnStart.clicked.connect(start_grabbing) mainWindow.ui.bnStop.clicked.connect(stop_grabbing) mainWindow.ui.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.ui.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.ui.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.ui.bnGetParam.clicked.connect(get_param) mainWindow.ui.bnSetParam.clicked.connect(set_param) # 修改保存图像按钮连接 mainWindow.ui.bnSaveImage.clicked.connect(save_image_dialog) # 显示主窗口 mainWindow.show() # 执行应用 app.exec_() # 关闭设备 close_device() # 断开传感器 disconnect_sensor() sys.exit() 这个代码出现了下面的问题 Find 1 devices! gige device: [0] device user define name: device model name: MV-CU120-10GM current ip: 169.254.79.151 Traceback (most recent call last): File "d:\海康\MVS\Development\Samples\Python\MvImport\three.py", line 804, in enum_devices ui.ComboDevices.clear() NameError: name 'ui' is not defined

最新推荐

recommend-type

springboot尿毒症健康管理系统的设计与实现论文

springboot尿毒症健康管理系统的设计与实现
recommend-type

python 列表文本转表格

python
recommend-type

关于多视图几何的论文,以及与点云深度学习相关的文章

资源下载链接为: https://pan.quark.cn/s/a447291460bd 关于多视图几何的论文,以及与点云深度学习相关的文章(最新、最全版本!打开链接下载即可用!)
recommend-type

基于MATLAB Simulink的质子交换膜燃料电池系统模型开发与控制策略仿真

内容概要:本文详细探讨了质子交换膜燃料电池(PEMFC)系统模型的构建与开发,特别是利用MATLAB Simulink平台进行建模和仿真的方法。文中涵盖了空压机、供气系统(阴极和阳极)、背压阀和电堆四个核心组件的数学模型建立,旨在帮助研究人员更好地理解PEMFC系统的运行机制。此外,还介绍了如何通过调整各组件的关键参数来优化系统的性能,从而为实际应用提供了理论依据和技术支持。 适合人群:从事燃料电池研究的技术人员、高校相关专业师生、新能源领域的科研工作者。 使用场景及目标:适用于希望深入了解PEMFC系统内部运作机制的研究人员,以及需要进行控制系统设计和优化的工程技术人员。目标是掌握MATLAB Simulink建模技巧,提升对PEMFC系统的理解和应用能力。 其他说明:文章不仅提供了详细的模型构建步骤,还包括具体的示例代码和讨论,便于读者实践操作并加深理解。
recommend-type

三菱FX3U与东元TECO N310变频器Modbus通讯实战及触摸屏程序详解

三菱FX3U PLC通过485ADP-MB板与东元TECO N310变频器进行Modbus通讯的具体实现方法。主要内容涵盖硬件连接、参数设置、PLC编程技巧以及触摸屏程序的设计。文中提供了详细的接线图、参数配置步骤、核心代码片段,并分享了一些实用的经验和避坑指南。此外,还特别强调了终端电阻的作用、波特率一致性、接地线的重要性以及CRC校验的正确使用。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是需要进行PLC与变频器通讯项目的从业者。 使用场景及目标:适用于需要将三菱FX3U PLC与东元TECO N310变频器进行Modbus通讯的实际工程项目。主要目标是帮助工程师快速掌握通讯配置和编程技巧,确保系统稳定可靠地运行。 其他说明:本文不仅提供理论指导,还包括大量实践经验,如硬件接线注意事项、常见错误排查等,有助于提高工作效率并减少调试时间。同时,附带的触摸屏程序也为不同品牌设备间的兼容性提供了保障。
recommend-type

Mockingbird v2:PocketMine-MP新防作弊机制详解

标题和描述中所涉及的知识点如下: 1. Mockingbird反作弊系统: Mockingbird是一个正在开发中的反作弊系统,专门针对PocketMine-MP服务器。PocketMine-MP是Minecraft Pocket Edition(Minecraft PE)的一个服务器软件,允许玩家在移动平台上共同游戏。随着游戏的普及,作弊问题也随之而来,因此Mockingbird的出现正是为了应对这种情况。 2. Mockingbird的版本迭代: 从描述中提到的“Mockingbird的v1变体”和“v2版本”的变化来看,Mockingbird正在经历持续的开发和改进过程。软件版本迭代是常见的开发实践,有助于修复已知问题,改善性能和用户体验,添加新功能等。 3. 服务器性能要求: 描述中强调了运行Mockingbird的服务器需要具备一定的性能,例如提及“WitherHosting的$ 1.25计划”,这暗示了反作弊系统对服务器资源的需求较高。这可能是因为反作弊机制需要频繁处理大量的数据和事件,以便及时检测和阻止作弊行为。 4. Waterdog问题: Waterdog是另一种Minecraft服务器软件,特别适合 PocketMine-MP。描述中提到如果将Mockingbird和Waterdog结合使用可能会遇到问题,这可能是因为两者在某些机制上的不兼容或Mockingbird对Waterdog的特定实现尚未完全优化。 5. GitHub使用及问题反馈: 作者鼓励用户通过GitHub问题跟踪系统来报告问题、旁路和功能建议。这是一个公共代码托管平台,广泛用于开源项目协作,便于开发者和用户进行沟通和问题管理。作者还提到请用户在GitHub上发布问题而不是在评论区留下不好的评论,这体现了良好的社区维护和用户交流的实践。 6. 软件标签: “pocketmine”和“anticheat”(反作弊)作为标签,说明Mockingbird是一个特别为PocketMine-MP平台开发的反作弊软件。而“PHP”则可能指的是Mockingbird的开发语言,虽然这个信息与常见的Java或C++等开发Minecraft相关软件的语言不同,但并不排除使用PHP进行服务器端开发的可能性,尤其是对于处理动态网页、服务器端脚本等场景。 7. 压缩包文件: “Mockingbird-stable”是一个文件名称,很可能表示这是一个包含最新稳定版Mockingbird反作弊系统的压缩包。通常,这样的文件名中包含“stable”意味着这是一个经过充分测试且推荐用于生产环境的版本。 8. 社区协作和用户参与: 特别感谢部分提到了shur,这可能是对某位贡献者或社区成员的感激之情。这种感谢表明了软件开发不仅是开发者个人的劳动成果,同时也依赖于社区的支持和参与,包括提供反馈、报告问题、贡献代码和文档等。 总结以上内容,我们可以看到Mockingbird作为一款反作弊系统,其开发和维护需要依赖于社区的广泛参与和支持,同时还需要强大的服务器后端作为支撑。通过不断迭代更新版本,开发者希望解决现有问题,提高反作弊效率,并希望与社区保持良好的沟通,以持续优化产品。
recommend-type

“历史人物独白解说”视频:数据处理的6种革命性技术

# 1. 数据处理的历史回顾与技术演进 数据处理的历史,从最初的简单机械记录,到如今复杂的数据处理体系,不仅反映了技术的演进,也映射了人类社会进步的轨迹。在计算机技术发展初期,数据处理主要依靠打孔卡片和磁带,通过批处理的方式进行,效率低下且出错率高。随着技术的发展,上世纪70年代,关系型数据库的出现大大提高了数据处理的效率和准确性,为数据处理技术的发展奠定了基础。 随后,随着互
recommend-type

怎么判断多级运放电路的稳定性?

<think>首先,用户的问题是关于判断多级运算放大器电路的稳定性。我需要根据系统级指令来构建回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考引用: - 引用[1]:关于集成运算放大电路的设计、组成和性能评估。 - 引用[2]:高频电路中运放的带宽限制,一级放大电路的增益通常为100倍,过高会引起振
recommend-type

利用AHP和节点集中度解决影响力最大化问题的Flask应用教程

从给定的文件信息中,我们可以提取以下相关知识点进行详细说明: ### 标题知识点 **IM问题与AHP结合** IM问题(Influence Maximization)是网络分析中的一个核心问题,旨在识别影响网络中信息传播的关键节点。为了求解IM问题,研究者们常常结合使用不同的算法和策略,其中AHP(Analytic Hierarchy Process,分析层次结构过程)作为一种决策分析方法,被用于评估网络节点的重要性。AHP通过建立层次模型,对各个因素进行比较排序,从而量化影响度,并通过一致性检验保证决策结果的有效性。将AHP应用于IM问题,意味着将分析网络节点影响的多个维度,比如节点的中心性(centrality)和影响力。 **集中度措施** 集中度(Centralization)是衡量网络节点分布状况的指标,它反映了网络中节点之间的连接关系。在网络分析中,集中度常用于识别网络中的“枢纽”或“中心”节点。例如,通过计算网络的度中心度(degree centrality)可以了解节点与其他节点的直接连接数量;接近中心度(closeness centrality)衡量节点到网络中其他所有节点的平均距离;中介中心度(betweenness centrality)衡量节点在连接网络中其他节点对的最短路径上的出现频率。集中度高意味着节点在网络中处于重要位置,对信息的流动和控制具有较大影响力。 ### 描述知识点 **Flask框架** Flask是一个轻量级的Web应用框架,它使用Python编程语言开发。它非常适合快速开发小型Web应用,以及作为微服务架构的一部分。Flask的一个核心特点是“微”,意味着它提供了基本的Web开发功能,同时保持了框架的小巧和灵活。Flask内置了开发服务器,支持Werkzeug WSGI工具包和Jinja2模板引擎,提供了RESTful请求分发和请求钩子等功能。 **应用布局** 一个典型的Flask应用会包含以下几个关键部分: - `app/`:这是应用的核心目录,包含了路由设置、视图函数、模型和控制器等代码文件。 - `static/`:存放静态文件,比如CSS样式表、JavaScript文件和图片等,这些文件的内容不会改变。 - `templates/`:存放HTML模板文件,Flask将使用这些模板渲染最终的HTML页面。模板语言通常是Jinja2。 - `wsgi.py`:WSGI(Web Server Gateway Interface)是Python应用程序和Web服务器之间的一种标准接口。这个文件通常用于部署到生产服务器时,作为应用的入口点。 **部署到Heroku** Heroku是一个支持多种编程语言的云平台即服务(PaaS),它允许开发者轻松部署、运行和管理应用。部署Flask应用到Heroku,需要几个步骤:首先,创建一个Procfile文件,告知Heroku如何启动应用;其次,确保应用的依赖关系被正确管理,通常通过一个requirements.txt文件列出所有依赖;最后,使用Git将应用推送到Heroku提供的仓库,Heroku会自动识别Procfile并开始部署过程。 ### 标签知识点 **HTML** HTML(HyperText Markup Language,超文本标记语言)是用于创建网页和Web应用的标准标记语言。它定义了网页的结构和内容。HTML文件由一系列元素组成,这些元素通过标签(tags)来表示,如`<p>`代表段落,`<a>`代表链接,`<img>`代表图像等。HTML5是当前使用的最新版本,支持更多的特性,如离线存储、多媒体和图形等。 ### 压缩包子文件的文件名称列表知识点 **IMproblem-using-AHP-and-centralisation-of-nodes-master** 这里的文件名称“IMproblem-using-AHP-and-centralisation-of-nodes-master”表明了一个GitHub仓库的名称,其中包含了源代码以及与项目相关的所有文件。从名称中可以看出,该仓库是关于如何结合AHP和节点集中度分析来解决IM问题的Flask应用程序。文件名中的“master”表明这是仓库的主分支(现在叫做main分支),它是项目最新的、可部署的代码版本。 综合来看,本段信息为我们提供了构建和部署一个使用Flask框架、针对IM问题使用AHP和节点集中度分析的Web应用的方法和步骤。同时,介绍了应用在不同环节中所需技术和组件的详细知识点。
recommend-type

视频内容自动生成算法:突破性的8大最新进展

# 1. 视频内容自动生成算法概述 ## 算法发展背景 随着人工智能技术的迅速发展,视频内容自动生成算法已经成为媒体和娱乐行业的重要工具。这些算法能够自动编辑和合成视频内容,使内容创作者能够以较低的成本和时间生成高质量的视频。从社交媒体动态到在线教育内容,视频内容自动生成的应用场景正在不断扩大。 ## 核心技术简述 视