news 2026/9/7 2:26:43

graphify `--update` 与 `--cluster-only`:知识图谱增量重建与社区重聚类的完整操作手册

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
graphify `--update` 与 `--cluster-only`:知识图谱增量重建与社区重聚类的完整操作手册

graphify--update--cluster-only:知识图谱增量重建与社区重聚类的完整操作手册

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

这篇指南面向 Trae 等 AI 编码 Agent 的 graphify 技能使用者,围绕 Trae 平台的 update 参考文档 展开,完整讲解两条"非全量重建"的执行路径:--update(增量重抽取,只处理新增/变更文件,省 token 省时间)与--cluster-only(在既有图之上仅重跑社区聚类)。读完你会掌握:如何在已有graphify-out/产物的项目上只重抽变更文件、如何区分"纯代码变更快路径"与"文档/音视频语义路径"、如何安全地把增量抽取结果合并回既有graph.json(含删除剪枝、重抽取替换、超边保留与有向图保真),以及只重聚类时哪些步骤绝不能重跑。

何时才需要这份参考

update.md在技能体系中的定位非常明确——只有用户显式传入--update--cluster-only时才加载它;首次全量构建永远不会读取该文件。原因在于首次构建由 graphify/skill-trae.md 主流程的 Steps 1–5 完成:先检测语料,再做 AST 结构抽取(Step 3A)与语义抽取(Step 3B),再构建、聚类、导出。而增量更新与仅重聚类都建立在"上一次运行已经生成graph.jsonGRAPH_REPORT.md、manifest 等中间产物"的前提上。

因此,阅读本手册前请先确认工作目录中存在完整的上一轮产物,尤其是:

  • graphify-out/graph.json—— 既有知识图谱本体(节点的source_file通常是相对扫描根目录的路径);
  • graphify-out/.graphify_python—— 上一轮解析出的可用 Python 解释器路径(uv tool / pipx / venv / 系统 Python 均可,所有命令均用$(cat graphify-out/.graphify_python)调用以保证解释器一致);
  • .graphify_detect.json.graphify_extract.json.graphify_*中间状态文件(注意:全量构建的收尾步骤会清理它们,这正是--cluster-only后不能重跑 Steps 5–9 的原因,详见后文)。

--update:只重抽新增/变更文件的增量流程

当用户自上次运行以来新增或修改了文件时使用--update。核心思想是只重新抽取变更过的文件,从而节省 token 与时间。整个流程在逻辑上分七个阶段,下面逐一展开。

阶段 1:调用detect_incremental得到变更集合

首先用 Python 调用 graphify/detect.py 中的detect_incremental,把结果同时打印出来并落盘为graphify-out/.graphify_incremental.json,供后续所有步骤读取:

$(cat graphify-out/.graphify_python) -c " import sys, json from graphify.detect import detect_incremental, save_manifest from pathlib import Path result = detect_incremental(Path('INPUT_PATH')) new_total = result.get('new_total', 0) print(json.dumps(result, indent=2, ensure_ascii=False)) Path('graphify-out/.graphify_incremental.json').write_text(json.dumps(result, ensure_ascii=False), encoding=\"utf-8\") deleted = list(result.get('deleted_files', [])) if new_total == 0 and not deleted: print('No files changed since last run. Nothing to update.') raise SystemExit(0) if deleted: print(f'{len(deleted)} deleted file(s) to prune.') if new_total > 0: print(f'{new_total} new/changed file(s) to re-extract.') "

从源码看,detect_incremental的判定逻辑很有讲究(见 detect.py#L2366-L2418):它先跑一次完整detect()扫描,再与上次保存的 manifest 比对。判断"是否变更"分两条 hash 轨道——kind="ast"比较ast_hashgraphify update用),kind="semantic"比较semantic_hashgraphify extract用)。为了不每次全量做磁盘 IO,它还带快路径优化:mtime 未变 + hash 匹配即视为未变更(仅一次 stat,零磁盘 IO);mtime 变动才走慢路径比对 MD5(detect.py#L2386-L2388)。

detect_incremental返回的关键字段即上述脚本所消费的:

字段含义
new_files按文件类型分组(如documentvideoimage、代码各类)的新增/变更文件清单
new_total需要重抽取的文件总数
deleted_files磁盘上已消失、需要从图中剪枝的真删除文件
excluded_files仍存活于磁盘但已被 ignore 规则 /--exclude排除、不得当作删除的文件
files/unchanged_files完整语料 / 未变更文件,供需要全语料上下文的步骤使用

脚本开头的提前退出很重要:若new_total == 0且无删除文件,直接打印 "Nothing to update" 并以SystemExit(0)干净退出——这一步为零成本,不会触发任何重抽取或合并。

值得注意的健壮性细节(源码注释可印证):manifest 旧格式只存浮点 mtime 时,用!=而非>比较,因此git checkout旧提交、tarball 还原、rsync --times造成的 mtime 倒退仍会触发重抽取,避免图与磁盘内容漂移(detect.py#L2432-L2438);manifest 键按 NFC 归一化,防止 macOS NFD 文件名漏检。

阶段 2:回填.graphify_detect.json让下游步骤"看到"增量状态

Steps 3A–6(结构抽取、语义抽取、合并、聚类、分析、导出)会无条件读取.graphify_detect.json,因此必须把增量结果重写为它们期望的形态。这里的双字段设计是精髓:

  • files—— 只装变更子集,驱动 Step 3A(AST 结构抽取)与 Step 3B0(缓存检查)只处理变更过的文件;
  • all_files—— 装完整语料,供任何需要全语料上下文(如语义子代理互读、图级统计)的步骤使用。
$(cat graphify-out/.graphify_python) -c " import json from pathlib import Path r = json.loads(Path('graphify-out/.graphify_incremental.json').read_text(encoding=\"utf-8\")) Path('graphify-out/.graphify_detect.json').write_text(json.dumps({ 'files': r.get('new_files', {}), 'all_files': r.get('files', {}), 'total_files': r.get('new_total', 0), 'total_words': r.get('total_words', 0), 'skipped_sensitive': r.get('skipped_sensitive', []), 'needs_graph': True, }, ensure_ascii=False), encoding=\"utf-8\") "

阶段 3:判断"纯代码变更"并走零 LLM 快路径

在跑任何语义抽取之前,先检查所有变更文件是否都是代码文件。这一步决定了能否跳过最昂贵的语义子代理环节。脚本内维护了一份硬编码的代码扩展名集合,涵盖 30 余种语言:

$(cat graphify-out/.graphify_python) -c " import json from pathlib import Path result = json.loads(open('graphify-out/.graphify_incremental.json', encoding='utf-8').read()) if Path('graphify-out/.graphify_incremental.json').exists() else {} code_exts = {'.py','.ts','.js','.go','.rs','.java','.cpp','.c','.rb','.swift','.kt','.cs','.scala','.php','.cc','.cxx','.hpp','.h','.kts','.lua','.toc','.f','.F','.f90','.F90','.f95','.F95','.f03','.F03','.f08','.F08'} new_files = result.get('new_files', {}) all_changed = [f for files in new_files.values() for f in files] code_only = all(Path(f).suffix.lower() in code_exts for f in all_changed) print('code_only:', code_only) "

分流的操作指令如下:

  • code_only为 True:打印[graphify update] Code-only changes detected - skipping semantic extraction (no LLM needed),只对变更文件跑 Step 3A(AST 结构抽取),完全跳过 Step 3B(语义子代理,不需要 LLM),随后直接进入合并与 Steps 4–8。这正是"代码改动零 token 成本刷图"的机制:AST 抽取是确定性的本地解析,天然不需要大模型。
  • code_only为 False(任一变更文件是文档 / 论文 / 图片 / 视频):进入完整 Steps 3A–3C 管线。

在代码路径下还可对照 CLI:graphify update(见 cli.py#L2251-L2309)无参数时会从graphify-out/.graphify_root恢复上次全量构建保存的扫描根,找不到才回退到.,然后调用graphify.watch._rebuild_code做无 LLM 的代码重抽。CLI 打印的提示语与技能手册互为印证:"Code graph updated. For doc/paper/image changes run /graphify --update in your AI assistant."——即命令行只能覆盖代码文件,文档/论文/图片的语义重抽必须回到 Agent 技能流程。

阶段 4:处理音视频变更(Step 2.5 转录),避免把媒体文件喂给语义子代理

code_only为 False,且变更文件中存在new_files['video'],则必须先对这些文件执行 transcribe 参考文档 中的 Step 2.5(视频/音频转录),然后重写.graphify_detect.json:把转录产物路径并入files['document'],并删除files['video']。文档注释明确给出了不这么做的后果:否则原始.mp4/.mp3路径会被当作"文档"直接喂给语义子代理,而子代理读不了媒体文件(对应 issue 上下文 #1392)。这保证了语义层永远只接触可读文本。

阶段 5:仅删除场景——构造空抽取让合并步骤执行剪枝

如果没有任何新文件(只有删除),就不存在新抽取内容。此时合并步骤仍需一个可用的抽取文件才能执行删除剪枝,因此需要显式构造一个空的 extraction:

if [ ! -f graphify-out/.graphify_extract.json ]; then echo '[graphify update] Only deletions -- creating empty extraction for merge.' $(cat graphify-out/.graphify_python) -c " import json from pathlib import Path Path('graphify-out/.graphify_extract.json').write_text(json.dumps({'nodes':[],'edges':[],'hyperedges':[],'input_tokens':0,'output_tokens':0}), encoding='utf-8') " fi

注意这里用if [ ! -f ... ]守卫:只有当抽取文件不存在时才创建,避免覆盖真实(例如来自仅删除前的小规模重抽)内容。

阶段 6:build_merge合并——剪枝只针对真删除、重抽取走替换、方向必须显式

这是整个增量更新的核心。它读取新的抽取结果与增量状态,调用 graphify/build.py 的build_merge把新内容并入既有graph.json,并把合并结果写回.graphify_extract.json,使 Step 4 能看到"完整图":

$(cat graphify-out/.graphify_python) -c " import json from pathlib import Path from graphify.build import build_merge from graphify.detect import save_manifest # Load new extraction and incremental state new_extraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) incremental = json.loads(Path('graphify-out/.graphify_incremental.json').read_text(encoding=\"utf-8\")) deleted = list(incremental.get('deleted_files', [])) # prune_sources is ONLY for genuinely DELETED files. Changed/re-extracted files are # handled by build_merge's replace-on-re-extract (#1344): every source_file in # new_chunks is dropped from the base before merge, so old/stale nodes don't survive. # Do NOT add `changed` here: with root= passed, prune_set relativizes to the same base # as the freshly merged nodes and would DELETE the re-extracted content (#1178 is moot # now that replace — not the dedup pass — reconciles changed files). prune = list(deleted) or None # Use build_merge() — reads graph.json directly without NetworkX round-trip # so edge direction (calls, implements, imports) is always preserved (#801). # Pass root= so prune_sources (absolute paths from detect_incremental) are # relativized to match the graph's relative source_file values; without it # nothing is pruned and stale nodes accumulate on every update (#1361). # directed=IS_DIRECTED: replace IS_DIRECTED with True if --directed was given, else # False. Without it a --directed --update silently rebuilds undirected and collapses # reciprocal A<->B edges (#1392). G = build_merge( [new_extraction], graph_path='graphify-out/graph.json', prune_sources=prune, root='INPUT_PATH', directed=IS_DIRECTED, ) print(f'[graphify update] Merged: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges') # Write merged result back to .graphify_extract.json so Step 4 sees the full graph merged_out = { 'nodes': [{'id': n, **d} for n, d in G.nodes(data=True)], 'edges': [ # Explicit source/target last so they win over any stale attrs in d. {**{k: val for k, val in d.items() if k not in ('_src', '_tgt', 'source', 'target')}, 'source': d.get('_src', u), 'target': d.get('_tgt', v)} for u, v, d in G.edges(data=True) ], # G.graph[\"hyperedges\"] holds hyperedges from both existing graph.json # and new_extraction (build_merge combines them). Falling back to # new_extraction only would silently drop prior-run hyperedges (#801). 'hyperedges': list(G.graph.get('hyperedges', [])), 'input_tokens': new_extraction.get('input_tokens', 0), 'output_tokens': new_extraction.get('output_tokens', 0), } Path('graphify-out/.graphify_extract.json').write_text(json.dumps(merged_out, ensure_ascii=False), encoding=\"utf-8\") print(f'[graphify update] Merged extraction written ({len(merged_out[\"nodes\"])} nodes, {len(merged_out[\"edges\"])} edges)') # Save manifest so next --update diffs against today's state, not the # prior run's baseline (prevents ghost-node reports on subsequent updates). # root= matches the build_merge call above so the manifest keys stay relative to # the scan root — portable across clones/machines, so --update keeps matching # cached files instead of missing every one after a move (#1417). # # Only stamp semantic files (docs/papers/images) that ACTUALLY produced output # THIS run (new_extraction is this run's fresh extraction, read above before the # merge overwrote the file): a changed doc whose chunk failed must stay unstamped # so the next --update re-queues it, otherwise it is marked done and its content # is lost forever (#2015). Mirrors the library extract path # (cli._stamped_manifest_files + clear_semantic + scan_corpus). from graphify.cli import _stamped_manifest_files _manifest_files = _stamped_manifest_files(incremental['files'], new_extraction, Path('INPUT_PATH')) # Changed semantic files dispatched this run but NOT stamped had their chunk fail # or be omitted; clear any stale semantic_hash so they are re-queued (#1948). _sem_types = ('document', 'paper', 'image') _dispatched = {f for t, fl in incremental.get('new_files', {}).items() if t in _sem_types for f in fl} _stamped = {f for fl in _manifest_files.values() for f in fl} _cleared = _dispatched - _stamped # scan_corpus = the RAW full corpus so in-root files newly excluded since last run # are dropped rather than masquerading as deletions; untouched rows preserved (#1908). _scan = {f for fl in incremental['files'].values() for f in fl} save_manifest(_manifest_files, root='INPUT_PATH', scan_corpus=_scan, clear_semantic=_cleared or None) print('[graphify update] Manifest saved.') "

这段脚本浓缩了增量合并最容易被忽视的四个语义陷阱,值得逐个吃透:

陷阱一:prune_sources只能装"真删除"文件,绝不能装变更文件。变更(重抽取)文件由build_merge的 "replace-on-re-extract" 机制负责:new_chunks中出现的每个source_file,在合并前会先被从基线图里整体剔除(build.py#L1685-L1734),旧节点/旧边不会残留,也不依赖 dedup 去兜底。若把changed混进prune,在传入root=的情况下剪枝集合与刚合并的新节点同基,会把刚重抽出来的新内容也剪掉。源码里更细致:替换是按 tier 拆分的(AST tier 与 semantic tier 各自独立),一次只重抽一个 tier 时,另一个 tier 的既有贡献会被原样保留(build.py#L1694-L1714),避免"仅语义重抽却把该文件的 AST 节点删光"。

陷阱二:必须传root='INPUT_PATH'detect_incremental返回的删除路径是绝对路径,而图内节点的source_file是相对扫描根的值;root=让剪枝路径与存储键同基。注释直白地警示:不传root,"nothing is pruned and stale nodes accumulate on every update"(剪枝全部落空,陈旧节点每次更新都在堆积,#1361)。即使调用方省略rootbuild_merge也会回退用图上记录的 scan root 推断有效根,绝对路径与相对键仍能对齐(build.py#L1673-L1683);当任何剪枝条目在存储键中零命中时,还会尝试通过后缀匹配重新推导根(build.py#L1776-L1796)。

陷阱三:directed=IS_DIRECTED必须与当初构建的图一致。如果不显式传入,一个原本--directed的图在--update时会"静默"退化为无向重建,A→BB→A的互反边会被折叠丢失。源码的默认策略更稳妥:directed=None时继承磁盘上既有图的 directed 标志(build.py#L1650-L1671)。脚本里要求在跑--update时手动替换IS_DIRECTED占位符,本质上就是让 Agent 按用户本次是否给了--directed显式表态。

陷阱四:超边(hyperedges)必须从合并后的图对象上取。build_merge会同时保留既有graph.json与新抽取里的超边并做 id 去重;若回退只取new_extraction的超边,上一次运行沉淀下来的超边会整体消失(#801)。源码中未重抽未删除文件的超边会被显式"carry"进新图(build.py#L1798-L1825),重抽文件的旧超边随 replace 丢弃、其新版本已含于新块。

随后是 manifest 落盘。关键点是只给"本轮确实产出了输出"的语义文件盖章

  • _stamped_manifest_files(见 cli.py#L88-L157)只会把sem_result中出现过source_file的文档/论文/图片文件算作已提取(节点与超边都算有效输出,纯边结果不算),AST 失败或零节点文件也排除——这些文件保持"未盖章",下一次--update才会重新入队,否则一次失败的内容会被永久标记为 done 而丢失(#2015/#2543)。
  • clear_semantic处理"本轮已派发但未盖章"的文件(#1948):这类文件上一轮残留的semantic_hash会被清空,避免detect_incremental误判为未变更。
  • scan_corpus传的是原始完整语料(不是盖章过滤后的子集):自上次运行以来新被 exclude 的 in-root 文件会从 manifest 中删除,而不是永远伪装成"删除文件"(#1908);未触碰文件的旧行则完整保留。

阶段 7:在合并图上继续 Steps 4–8,并展示图差异

合并完成后,按主流程在合并后的全量图上继续跑 Steps 4–8(构建/聚类/分析/导出/report)。Step 4 之后建议向用户展示一次图差异摘要。做法是:在合并之前先备份旧图,合并后再用graph_diff对比新旧快照:

$(cat graphify-out/.graphify_python) -c " import json from graphify.analyze import graph_diff from graphify.build import build_from_json from networkx.readwrite import json_graph import networkx as nx from pathlib import Path # Load old graph (before update) from backup written before merge old_data = json.loads(Path('graphify-out/.graphify_old.json').read_text(encoding=\"utf-8\")) if Path('graphify-out/.graphify_old.json').exists() else None new_extract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\"utf-8\")) G_new = build_from_json(new_extract, directed=IS_DIRECTED) if old_data: G_old = json_graph.node_link_graph(old_data, edges='links') diff = graph_diff(G_old, G_new) print(diff['summary']) if diff['new_nodes']: print('New nodes:', ', '.join(n['label'] for n in diff['new_nodes'][:5])) if diff['new_edges']: print('New edges:', len(diff['new_edges'])) "

配套的两条 shell 指令必须按序执行:合并步骤前先cp graphify-out/graph.json graphify-out/.graphify_old.json保存旧图;用完后清理rm -f graphify-out/.graphify_old.json,避免残留下一次把旧快照误读为"本次变更前状态"。

graph_diff的实现位于 analyze.py#L556-L637,返回结构化差异:new_nodes/removed_nodes(含 label)、new_edges/removed_edges(含 relation 与 confidence),以及人类可读的summary字符串(如 "3 new nodes, 5 new edges, 1 node removed";无变化时为 "no changes")。注意它用带 relation 的键(有向图用(u, v, relation),无向图对端点排序)做差集,因此语义上是"边关系级别"的差异,而非单纯端点差异。

--cluster-only:在既有图上仅重聚类

当用户只改了聚类相关诉求(如想看不同的社区划分结果),不需要动抽取层时,使用--cluster-only。它跳过 Steps 1–3(探测/抽取),直接在既有图上重跑聚类

graphify cluster-only .

该命令是完全自包含的:它会重新聚类、命名社区,并基于既有图重新生成GRAPH_REPORT.mdgraph.jsongraph.html。实现层面,cluster-onlylabel命令同源(cli.py#L1843 一带,label是"总是重新生成社区名"的cluster-only);从测试看,它覆盖了输出目录缺失时自动创建、graph.jsongraphify-out/时相邻写入、保留 analysis sidecar、把新社区 id 映射回上一轮的 cid、写被拒时报错、以及非 git 仓库 cwd 下保留built_at_commit等大量边界(见 test_cli_export.py 中test_cluster_only_*系列用例)。

务必遵守的禁令:不要重跑 Steps 5–9。文档措辞非常严厉——这些步骤读取的是.graphify_extract.json.graphify_detect.json.graphify_analysis.json这类中间文件,而上一轮全量构建的收尾(Step 9)已经把它们删掉了,因此重跑会直接抛FileNotFoundError(#1392)。cluster-only命令本身已经处理完聚类、命名与三件套输出,结束后只需像平时一样展示刷新后的GRAPH_REPORT.md摘要即可,不需要也不应该再叠加任何后续步骤。

与整份参考体系的关系

这份update.md是 Trae 技能参考集的一员。它的兄弟参考文档彼此咬合,构成了完整的图生命周期:

  • 首次全量构建:由 skill-trae.md 主流程(Steps 1–9)驱动,其## Usage一节列出了全部子命令(含--update--cluster-only--directed--watchquerypathexplain等);
  • 更新后的问答:读 query 参考,执行graphify query/path/explain,并把 Q&A 用graphify save-result写回图,形成"下一次--update会把它抽取为图节点"的自改进闭环;
  • 变更文件含音视频时:走 transcribe 参考 的 Step 2.5 转录。

从主流程(skill-trae.md 的--update分支同样指向 "Seereferences/update.md")可以看出:无论运行在 Trae 还是其他 Agent 平台,增量更新与仅重聚类这两条路径都遵循同一份运行手册——本文内容对任意平台的同构技能副本(如graphify/skills/claude/references/update.md)同样适用。

小结与自查清单

把两份参考与实现源码对照后,落地一条安全的--update可以浓缩为如下检查清单:

  1. detect_incremental无变更时提前退出(零成本);有变更则先判定code_only
  2. 纯代码 → 只跑 Step 3A(AST),跳过所有 LLM 语义子代理;
  3. 含文档/论文/图片/视频 → 视频先转录并改写files['document'],再走完整 Steps 3A–3C;
  4. 仅删除 → 造空抽取,交给合并步骤剪枝;
  5. 合并前cp备份旧图;build_mergeprune_sources只含真删除、务必传root=与正确的directed=,超边从合并后的G.graph取;
  6. manifest 只给本轮真正产出输出的语义文件盖章,派发未盖章的文件清semantic_hashscan_corpus传原始全语料;
  7. 继续 Steps 4–8 跑全量图,用graph_diff汇报差异,用毕删除.graphify_old.json

--cluster-only更简单也更挑剔:一条graphify cluster-only .自包含完成聚类-命名-输出三件套,此后不要再重跑任何依赖已被清理的中间文件的下游步骤

【免费下载链接】graphifyTurn any codebase, with its docs, SQL schemas, configs, and PDFs, into a queryable knowledge graph. A /graphify skill for Claude Code, Cursor, Codex, and Gemini CLI: local deterministic AST parsing, every edge explained, no vector store.项目地址: https://gitcode.com/GitHub_Trending/graph/graphify

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 2:25:18

PIC18指令系统实战解析:从51汇编到16位指令字的进阶之路

简介&#xff1a;PIC18系列单片机指令中文讲解是一份面向单片机初学者和嵌入式开发者的指令集学习资料&#xff0c;聚焦PIC18家族指令的快速入门与日常查阅。文档对算术运算、位元运算、程式流程控制、数据传输、逻辑运算及移位等常用指令进行了分类整理&#xff0c;并给出助记…

作者头像 李华
网站建设 2026/9/7 2:24:20

数据转表格技术全解析:从基础实现到高级优化方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:23:53

GitHub热榜爆款:qzonearchive教你如何备份QQ空间数据到本地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 2:23:41

ObjectARX+DockControlBar实现AutoCAD屏幕菜单:从原理到踩坑实践

简介&#xff1a;面向需要在AutoCAD 2010中定制屏幕菜单的ObjectArx开发者&#xff0c;这份示例工程演示了通过CAcUiDockControlBar派生自定义控制条&#xff0c;并完成注册、事件响应与布局管理的关键流程。工程源码包含DockControlBar实现、子对话框、入口函数与资源定义&…

作者头像 李华
网站建设 2026/9/7 2:23:25

环境工程CAD绘图入门:第三章核心命令与图层管理实战解析

简介&#xff1a;《环境工程CAD技术&#xff1a;第三章 绘图.pdf》是环境工程制图与CAD基础教学的配套讲义&#xff0c;面向环境工程专业学生、设计初学者及相关技术人员&#xff0c;帮助读者系统学习二维绘图的核心命令。内容以AutoCAD软件为载体&#xff0c;完整讲解了直线、…

作者头像 李华