solidworks-mcp-python 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. solidworks_mcp/__init__.py +57 -0
  2. solidworks_mcp/adapters/__init__.py +40 -0
  3. solidworks_mcp/adapters/base.py +1882 -0
  4. solidworks_mcp/adapters/circuit_breaker.py +2052 -0
  5. solidworks_mcp/adapters/com_executor.py +252 -0
  6. solidworks_mcp/adapters/complexity_analyzer.py +272 -0
  7. solidworks_mcp/adapters/connection_pool.py +1884 -0
  8. solidworks_mcp/adapters/factory.py +366 -0
  9. solidworks_mcp/adapters/intelligent_router.py +218 -0
  10. solidworks_mcp/adapters/mock_adapter.py +4016 -0
  11. solidworks_mcp/adapters/pywin32_adapter.py +2842 -0
  12. solidworks_mcp/adapters/solidworks/__init__.py +17 -0
  13. solidworks_mcp/adapters/solidworks/bitmap.py +180 -0
  14. solidworks_mcp/adapters/solidworks/com_utils.py +56 -0
  15. solidworks_mcp/adapters/solidworks/dimension_layout.py +753 -0
  16. solidworks_mcp/adapters/solidworks/documentation.py +1476 -0
  17. solidworks_mcp/adapters/solidworks/drawing_standard.py +1993 -0
  18. solidworks_mcp/adapters/solidworks/face_selection.py +392 -0
  19. solidworks_mcp/adapters/solidworks/features.py +3362 -0
  20. solidworks_mcp/adapters/solidworks/io.py +3649 -0
  21. solidworks_mcp/adapters/solidworks/rebuild_diagnostics.py +269 -0
  22. solidworks_mcp/adapters/solidworks/rebuild_inspector.py +146 -0
  23. solidworks_mcp/adapters/solidworks/selection.py +308 -0
  24. solidworks_mcp/adapters/solidworks/sketch.py +2545 -0
  25. solidworks_mcp/adapters/sw_feature_errors.py +66 -0
  26. solidworks_mcp/adapters/sw_type_info.py +317 -0
  27. solidworks_mcp/adapters/vba_adapter.py +318 -0
  28. solidworks_mcp/adapters/vba_macro_executor.py +215 -0
  29. solidworks_mcp/agents/__init__.py +68 -0
  30. solidworks_mcp/agents/design_knowledge/assembly_patterns.md +127 -0
  31. solidworks_mcp/agents/design_knowledge/hinges.md +118 -0
  32. solidworks_mcp/agents/design_knowledge/snap_fits.md +93 -0
  33. solidworks_mcp/agents/design_knowledge/solidworks_basics.md +3 -0
  34. solidworks_mcp/agents/design_knowledge/tolerancing.md +128 -0
  35. solidworks_mcp/agents/harness.py +216 -0
  36. solidworks_mcp/agents/history_db.py +1508 -0
  37. solidworks_mcp/agents/retrieval_index.py +164 -0
  38. solidworks_mcp/agents/schemas.py +165 -0
  39. solidworks_mcp/agents/smoke_test.py +285 -0
  40. solidworks_mcp/agents/soc_exporter.py +650 -0
  41. solidworks_mcp/agents/soc_pickup.py +332 -0
  42. solidworks_mcp/agents/soc_rewind.py +221 -0
  43. solidworks_mcp/agents/vector_rag.py +665 -0
  44. solidworks_mcp/cache/__init__.py +5 -0
  45. solidworks_mcp/cache/response_cache.py +164 -0
  46. solidworks_mcp/config.py +729 -0
  47. solidworks_mcp/exceptions.py +37 -0
  48. solidworks_mcp/security/__init__.py +68 -0
  49. solidworks_mcp/security/auth.py +128 -0
  50. solidworks_mcp/security/cors.py +27 -0
  51. solidworks_mcp/security/rate_limiting.py +90 -0
  52. solidworks_mcp/security/runtime.py +155 -0
  53. solidworks_mcp/server.py +777 -0
  54. solidworks_mcp/server_cli_fixed.py +120 -0
  55. solidworks_mcp/setup/__init__.py +1 -0
  56. solidworks_mcp/setup/cli.py +399 -0
  57. solidworks_mcp/setup/clients.py +157 -0
  58. solidworks_mcp/setup/config_file.py +230 -0
  59. solidworks_mcp/setup/handshake.py +202 -0
  60. solidworks_mcp/setup/launcher.py +102 -0
  61. solidworks_mcp/setup/legacy.py +100 -0
  62. solidworks_mcp/setup/registration.py +193 -0
  63. solidworks_mcp/setup/solidworks.py +49 -0
  64. solidworks_mcp/tools/__init__.py +64 -0
  65. solidworks_mcp/tools/analysis.py +723 -0
  66. solidworks_mcp/tools/automation.py +578 -0
  67. solidworks_mcp/tools/docs_discovery.py +1476 -0
  68. solidworks_mcp/tools/drawing.py +1015 -0
  69. solidworks_mcp/tools/drawing_analysis.py +456 -0
  70. solidworks_mcp/tools/drawing_automation.py +627 -0
  71. solidworks_mcp/tools/engineering_data.py +466 -0
  72. solidworks_mcp/tools/error_classifier.py +232 -0
  73. solidworks_mcp/tools/export.py +833 -0
  74. solidworks_mcp/tools/file_facts.py +40 -0
  75. solidworks_mcp/tools/file_management.py +1473 -0
  76. solidworks_mcp/tools/input_compat.py +17 -0
  77. solidworks_mcp/tools/macro_recording.py +464 -0
  78. solidworks_mcp/tools/modeling.py +2360 -0
  79. solidworks_mcp/tools/sketching.py +1422 -0
  80. solidworks_mcp/tools/template_management.py +367 -0
  81. solidworks_mcp/tools/tool_boundary.py +280 -0
  82. solidworks_mcp/tools/vba_generation.py +1145 -0
  83. solidworks_mcp/ui/__init__.py +9 -0
  84. solidworks_mcp/ui/local_llm.py +725 -0
  85. solidworks_mcp/ui/routers/__init__.py +10 -0
  86. solidworks_mcp/ui/routers/checkpoint.py +24 -0
  87. solidworks_mcp/ui/routers/docs.py +47 -0
  88. solidworks_mcp/ui/routers/llm.py +83 -0
  89. solidworks_mcp/ui/routers/local_model.py +69 -0
  90. solidworks_mcp/ui/routers/model.py +75 -0
  91. solidworks_mcp/ui/routers/preview.py +38 -0
  92. solidworks_mcp/ui/routers/session.py +161 -0
  93. solidworks_mcp/ui/routers/viewer.py +154 -0
  94. solidworks_mcp/ui/schemas.py +502 -0
  95. solidworks_mcp/ui/server.py +298 -0
  96. solidworks_mcp/ui/service.py +481 -0
  97. solidworks_mcp/ui/services/__init__.py +136 -0
  98. solidworks_mcp/ui/services/_utils.py +958 -0
  99. solidworks_mcp/ui/services/checkpoint_service.py +1110 -0
  100. solidworks_mcp/ui/services/docs_service.py +282 -0
  101. solidworks_mcp/ui/services/llm_service.py +956 -0
  102. solidworks_mcp/ui/services/model_service.py +459 -0
  103. solidworks_mcp/ui/services/parameter_repair_service.py +314 -0
  104. solidworks_mcp/ui/services/preview_service.py +489 -0
  105. solidworks_mcp/ui/services/session_service.py +1389 -0
  106. solidworks_mcp/utils/__init__.py +9 -0
  107. solidworks_mcp/utils/feature_tree_classifier.py +342 -0
  108. solidworks_mcp/utils/logging.py +76 -0
  109. solidworks_mcp/utils/validation.py +77 -0
  110. solidworks_mcp/version.py +8 -0
  111. solidworks_mcp_python-1.5.0.dist-info/METADATA +410 -0
  112. solidworks_mcp_python-1.5.0.dist-info/RECORD +115 -0
  113. solidworks_mcp_python-1.5.0.dist-info/WHEEL +4 -0
  114. solidworks_mcp_python-1.5.0.dist-info/entry_points.txt +3 -0
  115. solidworks_mcp_python-1.5.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,269 @@
1
+ """Turn SolidWorks rebuild error codes into descriptions and fix hints.
2
+
3
+ Pure functions, no COM: the adapter collects ``(feature, type, code,
4
+ is_warning, parents)`` from ``IModelDocExtension::GetWhatsWrong`` and this
5
+ module explains each one so an LLM caller can repair the model rather than
6
+ just learning that the rebuild "didn't work".
7
+
8
+ Hints are chosen most-specific first: an exact ``swFeatureError_e`` code, then
9
+ the feature type (``swFeatureErrorUnknown`` - code 1 - is what SolidWorks
10
+ reports for many common failures, e.g. an extrude whose sketch was emptied,
11
+ so the type is often the only useful signal), then a parent sketch that has
12
+ no geometry, then a generic fallback.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from typing import Any
19
+
20
+ from ..sw_feature_errors import SW_FEATURE_ERRORS
21
+
22
+ GENERIC_HINT = (
23
+ "Inspect the parents listed for this feature: re-target any missing "
24
+ "edge/face/sketch reference, fix the parent sketch, or suppress/delete the "
25
+ "feature and recreate it."
26
+ )
27
+
28
+ # Exact-code hints. Keys MUST exist in SW_FEATURE_ERRORS (enforced by tests).
29
+ _CODE_HINTS: dict[int, tuple[str, str]] = {
30
+ 10: (
31
+ "The fillet could not find a closed loop of edges.",
32
+ "Re-select the fillet edges (e.g. select_face_edges then add_fillet "
33
+ "with empty edge_names) or delete and recreate the fillet.",
34
+ ),
35
+ 11: (
36
+ "A face the fillet referenced no longer exists.",
37
+ "Re-select the fillet's faces/edges with select_face_edges, or delete "
38
+ "and recreate the fillet.",
39
+ ),
40
+ 12: (
41
+ "The fillet radius is invalid.",
42
+ "Set a positive radius smaller than the adjacent faces allow.",
43
+ ),
44
+ 13: (
45
+ "An edge the fillet referenced no longer exists (often after an "
46
+ "earlier dimension or feature change).",
47
+ "Re-select the edges with select_face_edges (prefer whole-face "
48
+ "selection) and recreate the fillet.",
49
+ ),
50
+ 15: (
51
+ "The fillet radius is too small for the geometry.",
52
+ "Increase the fillet radius.",
53
+ ),
54
+ 18: (
55
+ "The fillet radius is too big for the adjacent faces.",
56
+ "Reduce the fillet radius below the thickness of the faces it joins.",
57
+ ),
58
+ 19: (
59
+ "The fillet radius is too big for the adjacent faces.",
60
+ "Reduce the fillet radius below the thickness of the faces it joins.",
61
+ ),
62
+ 30: (
63
+ "The extrusion would create disjoint (disconnected) bodies.",
64
+ "Change the extrude depth/end condition so the result touches the "
65
+ "existing body, or enable merge-off / multibody intentionally.",
66
+ ),
67
+ 31: (
68
+ "The extrusion's end condition found no end (e.g. Up To Surface target "
69
+ "missing).",
70
+ "Re-select the up-to reference or switch the end condition to Blind "
71
+ "with an explicit depth.",
72
+ ),
73
+ 33: (
74
+ "The cut profile mixes open and closed contours.",
75
+ "Edit the cut's sketch so it contains only closed contours.",
76
+ ),
77
+ 34: (
78
+ "The cut profile is invalid (open, overlapping or self-intersecting).",
79
+ "Edit the cut's sketch: close every contour and remove overlaps.",
80
+ ),
81
+ 36: (
82
+ "The boss profile mixes open and closed contours.",
83
+ "Edit the boss sketch so it contains only closed contours.",
84
+ ),
85
+ 37: (
86
+ "The boss profile is invalid (open, overlapping or self-intersecting).",
87
+ "Edit the boss sketch: close every contour and remove overlaps.",
88
+ ),
89
+ 43: (
90
+ "A mate references geometry that no longer exists (dangling).",
91
+ "Delete the mate and re-mate the components to existing faces/edges.",
92
+ ),
93
+ 46: (
94
+ "The assembly is over-defined by mates.",
95
+ "Delete or suppress the conflicting mate(s).",
96
+ ),
97
+ 48: ("A mate is broken.", "Delete the mate and recreate it."),
98
+ 51: (
99
+ "The sketch's external reference could not be resolved.",
100
+ "Edit the sketch and repair or delete its dangling relations.",
101
+ ),
102
+ 71: (
103
+ "The cut does not intersect the model.",
104
+ "Move the cut sketch onto/inside the body, flip the cut direction, or "
105
+ "increase its depth.",
106
+ ),
107
+ 72: (
108
+ "The sketch contains a self-intersecting contour.",
109
+ "Edit the sketch and remove the crossing segments.",
110
+ ),
111
+ 73: (
112
+ "Items the feature needs are missing.",
113
+ "Re-select the feature's missing references or recreate the feature.",
114
+ ),
115
+ }
116
+
117
+ # Feature-type hints, used when the code alone is not specific
118
+ # (``swFeatureErrorUnknown``) or has no curated hint. Keys are
119
+ # ``IFeature::GetTypeName2`` values.
120
+ _TYPE_HINTS: dict[str, str] = {
121
+ "Extrusion": (
122
+ "Check the boss's parent sketch: it must contain at least one closed "
123
+ "contour, and the depth/end condition must reach geometry."
124
+ ),
125
+ "Cut": (
126
+ "Check the cut's parent sketch has a closed contour that overlaps the "
127
+ "body, and that the cut direction/depth actually removes material."
128
+ ),
129
+ # "ICE" is what GetTypeName2 reports for extrudes created through the
130
+ # API - boss or cut (observed live on R2026x for a boss extrude).
131
+ "ICE": (
132
+ "Check the extrude's parent sketch: it must contain a closed contour, "
133
+ "and the depth/end condition must reach (or, for a cut, overlap) the body."
134
+ ),
135
+ "Fillet": (
136
+ "The fillet's edges/faces probably changed. Re-select them with "
137
+ "select_face_edges and recreate the fillet, or reduce the radius."
138
+ ),
139
+ "Chamfer": (
140
+ "The chamfer's edges probably changed. Re-select them with "
141
+ "select_face_edges and recreate the chamfer, or reduce the distance."
142
+ ),
143
+ "ProfileFeature": (
144
+ "The sketch itself has a problem (dangling or conflicting relations, "
145
+ "or a reference to deleted geometry). Edit the sketch to repair it."
146
+ ),
147
+ "RefPlane": (
148
+ "The reference plane lost the face/plane it was defined from. Redefine "
149
+ "it from existing geometry."
150
+ ),
151
+ }
152
+
153
+ #: Readable names for GetTypeName2 values used in descriptions.
154
+ _TYPE_LABELS: dict[str, str] = {
155
+ "ICE": "Extrude",
156
+ "Extrusion": "Boss-extrude",
157
+ "Cut": "Cut-extrude",
158
+ "ProfileFeature": "Sketch",
159
+ "RefPlane": "Reference plane",
160
+ }
161
+
162
+ _EMPTY_SKETCH_HINT = (
163
+ "Parent sketch '{name}' has no geometry. Add a closed profile to it (or "
164
+ "delete this feature)."
165
+ )
166
+
167
+
168
+ @dataclass(frozen=True)
169
+ class ParentInfo:
170
+ """A feature the failing feature depends on.
171
+
172
+ Attributes:
173
+ name: The parent feature or sketch name.
174
+ type: Its ``GetTypeName2`` value (``""`` when unknown).
175
+ segment_count: For sketches, how many segments it has; ``None`` when
176
+ not a sketch or not readable.
177
+ """
178
+
179
+ name: str
180
+ type: str = ""
181
+ segment_count: int | None = None
182
+
183
+
184
+ @dataclass(frozen=True)
185
+ class RawRebuildIssue:
186
+ """One raw ``GetWhatsWrong`` entry as collected from COM."""
187
+
188
+ feature: str
189
+ type: str
190
+ code: int
191
+ is_warning: bool
192
+ parents: list[ParentInfo] = field(default_factory=list)
193
+
194
+
195
+ def code_name(code: int) -> str:
196
+ """Return the ``swFeatureError_e`` member name, or ``"code <n>"``."""
197
+ return SW_FEATURE_ERRORS.get(code, f"code {code}")
198
+
199
+
200
+ def explain(issue: RawRebuildIssue) -> dict[str, Any]:
201
+ """Build the caller-facing diagnostic entry for one rebuild issue.
202
+
203
+ Args:
204
+ issue: The raw entry collected from SolidWorks.
205
+
206
+ Returns:
207
+ dict[str, Any]: ``feature``, ``type``, ``code``, ``code_name``,
208
+ ``severity``, ``description``, ``parents`` and ``hint``.
209
+ """
210
+ description, hint = _CODE_HINTS.get(issue.code, ("", ""))
211
+ empty_parent = next(
212
+ (
213
+ p
214
+ for p in issue.parents
215
+ if p.type == "ProfileFeature" and p.segment_count == 0
216
+ ),
217
+ None,
218
+ )
219
+ if empty_parent is not None:
220
+ hint = _EMPTY_SKETCH_HINT.format(name=empty_parent.name)
221
+ elif not hint:
222
+ hint = _TYPE_HINTS.get(issue.type, GENERIC_HINT)
223
+ if not description:
224
+ description = (
225
+ f"{_TYPE_LABELS.get(issue.type, issue.type or 'Feature')} "
226
+ f"'{issue.feature}' failed to rebuild "
227
+ f"({code_name(issue.code)})."
228
+ )
229
+ return {
230
+ "feature": issue.feature,
231
+ "type": issue.type,
232
+ "code": issue.code,
233
+ "code_name": code_name(issue.code),
234
+ "severity": "warning" if issue.is_warning else "error",
235
+ "description": description,
236
+ "parents": [
237
+ {"name": p.name, "type": p.type}
238
+ | (
239
+ {"segment_count": p.segment_count}
240
+ if p.segment_count is not None
241
+ else {}
242
+ )
243
+ for p in issue.parents
244
+ ],
245
+ "hint": hint,
246
+ }
247
+
248
+
249
+ def summarize(entries: list[dict[str, Any]]) -> tuple[int, int, str]:
250
+ """Count errors/warnings and build the one-line status message.
251
+
252
+ Args:
253
+ entries: Output of :func:`explain` for every issue.
254
+
255
+ Returns:
256
+ tuple[int, int, str]: ``(error_count, warning_count, message)``.
257
+ """
258
+ errors = [e["feature"] for e in entries if e["severity"] == "error"]
259
+ warnings = [e["feature"] for e in entries if e["severity"] == "warning"]
260
+ if errors:
261
+ message = (
262
+ f"Rebuild left {len(errors)} feature error(s): {', '.join(errors)}. "
263
+ "See data.errors for each cause and hint."
264
+ )
265
+ elif warnings:
266
+ message = f"Rebuilt with {len(warnings)} warning(s): {', '.join(warnings)}."
267
+ else:
268
+ message = "Rebuilt cleanly; no rebuild errors."
269
+ return len(errors), len(warnings), message
@@ -0,0 +1,146 @@
1
+ """Collect rebuild errors/warnings from the active SolidWorks model (COM side).
2
+
3
+ Feeds :mod:`rebuild_diagnostics`, which turns each raw issue into a
4
+ description and fix hint. Two sources, in the order chosen from the live probe
5
+ (R2026x, 2026-10-13, see the #106 change's design.md):
6
+
7
+ 1. ``IModelDocExtension::GetWhatsWrong`` with three byref VARIANT out-params
8
+ - one call returns every flagged feature, its code and warning flag.
9
+ 2. Fallback: walk the feature tree calling
10
+ ``IFeature::GetErrorCode2(byref IsWarning)`` on each unsuppressed feature.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ from .com_utils import as_list as _as_list
18
+ from .com_utils import attempt as _attempt
19
+ from .com_utils import flag as _flag
20
+ from .io import _byref_bool, _byref_variant
21
+ from .rebuild_diagnostics import ParentInfo, RawRebuildIssue
22
+
23
+ _FEATURE_MEMBERS = (
24
+ "GetTypeName2",
25
+ "GetParents",
26
+ "GetNextFeature",
27
+ "GetFirstSubFeature",
28
+ "GetNextSubFeature",
29
+ "GetErrorCode2",
30
+ "IsSuppressed",
31
+ "GetSpecificFeature2",
32
+ )
33
+ _MAX_FEATURES = 10000 # runaway guard, same as the selector's tree walk
34
+
35
+
36
+ def _sketch_segment_count(adapter: Any, feature: Any) -> int | None:
37
+ """Segment count of a sketch feature, or None when not readable."""
38
+ sketch = _attempt(adapter, lambda: feature.GetSpecificFeature2())
39
+ if sketch is None:
40
+ return None
41
+ _flag(adapter, sketch, "GetSketchSegments")
42
+ segments = _attempt(adapter, lambda: sketch.GetSketchSegments(), default=False)
43
+ if segments is False:
44
+ return None
45
+ return len(_as_list(segments))
46
+
47
+
48
+ def _parents(adapter: Any, feature: Any) -> list[ParentInfo]:
49
+ """Describe the features/sketches ``feature`` depends on."""
50
+ out = []
51
+ for parent in _as_list(_attempt(adapter, lambda: feature.GetParents())):
52
+ _flag(adapter, parent, *_FEATURE_MEMBERS)
53
+ name = str(_attempt(adapter, lambda p=parent: p.Name, "") or "")
54
+ kind = str(_attempt(adapter, lambda p=parent: p.GetTypeName2(), "") or "")
55
+ segments = (
56
+ _sketch_segment_count(adapter, parent) if kind == "ProfileFeature" else None
57
+ )
58
+ out.append(ParentInfo(name=name, type=kind, segment_count=segments))
59
+ return out
60
+
61
+
62
+ def _issue(adapter: Any, feature: Any, code: int, is_warning: bool) -> RawRebuildIssue:
63
+ _flag(adapter, feature, *_FEATURE_MEMBERS)
64
+ return RawRebuildIssue(
65
+ feature=str(_attempt(adapter, lambda: feature.Name, "") or ""),
66
+ type=str(_attempt(adapter, lambda: feature.GetTypeName2(), "") or ""),
67
+ code=int(code),
68
+ is_warning=bool(is_warning),
69
+ parents=_parents(adapter, feature),
70
+ )
71
+
72
+
73
+ def _from_whats_wrong(adapter: Any, model: Any) -> list[RawRebuildIssue] | None:
74
+ """Source 1: one GetWhatsWrong call. None when the call is unusable."""
75
+ ext = _attempt(adapter, lambda: model.Extension)
76
+ if ext is None:
77
+ return None
78
+ _flag(adapter, ext, "GetWhatsWrongCount", "GetWhatsWrong")
79
+ count = _attempt(adapter, lambda: ext.GetWhatsWrongCount())
80
+ if isinstance(count, (int, float)) and int(count) == 0:
81
+ return []
82
+ features, codes, warnings = _byref_variant(), _byref_variant(), _byref_variant()
83
+ if _attempt(adapter, lambda: ext.GetWhatsWrong(features, codes, warnings)) is None:
84
+ return None
85
+ feats = _as_list(features.value)
86
+ errs = _as_list(codes.value)
87
+ warns = _as_list(warnings.value)
88
+ if not feats or len(errs) != len(feats):
89
+ return None
90
+ warns = (warns + [False] * len(feats))[: len(feats)]
91
+ return [
92
+ _issue(adapter, f, int(c), bool(w))
93
+ for f, c, w in zip(feats, errs, warns, strict=True)
94
+ if f is not None
95
+ ]
96
+
97
+
98
+ def _check(adapter: Any, feature: Any, issues: list[RawRebuildIssue]) -> None:
99
+ """Append ``feature``'s rebuild issue (if any) to ``issues``."""
100
+ if _attempt(adapter, lambda: feature.IsSuppressed(), False):
101
+ return
102
+ is_warning = _byref_bool()
103
+ code = _attempt(adapter, lambda: feature.GetErrorCode2(is_warning), 0)
104
+ if isinstance(code, (int, float)) and int(code) != 0:
105
+ issues.append(_issue(adapter, feature, int(code), bool(is_warning.value)))
106
+
107
+
108
+ def _from_tree_walk(adapter: Any, model: Any) -> list[RawRebuildIssue]:
109
+ """Source 2: per-feature GetErrorCode2 over the unsuppressed tree.
110
+
111
+ Visits each top-level feature and its sub-features (e.g. the sketch an
112
+ extrude or cut absorbed), since a sketch error lives on the sketch.
113
+ """
114
+ issues: list[RawRebuildIssue] = []
115
+ feature = _attempt(adapter, lambda: model.FirstFeature())
116
+ guard = 0
117
+ while feature is not None and guard < _MAX_FEATURES:
118
+ guard += 1
119
+ _flag(adapter, feature, *_FEATURE_MEMBERS)
120
+ current = feature
121
+ _check(adapter, current, issues)
122
+ sub = _attempt(adapter, lambda f=current: f.GetFirstSubFeature())
123
+ while sub is not None and guard < _MAX_FEATURES:
124
+ guard += 1
125
+ _flag(adapter, sub, *_FEATURE_MEMBERS)
126
+ sub_current = sub
127
+ _check(adapter, sub_current, issues)
128
+ sub = _attempt(adapter, lambda s=sub_current: s.GetNextSubFeature())
129
+ feature = _attempt(adapter, lambda f=current: f.GetNextFeature())
130
+ return issues
131
+
132
+
133
+ def collect_rebuild_issues(adapter: Any, model: Any) -> list[RawRebuildIssue]:
134
+ """Return every rebuild error/warning on ``model`` after a rebuild.
135
+
136
+ Args:
137
+ adapter: A connected adapter (for ``_attempt``).
138
+ model: The active ``IModelDoc2``.
139
+
140
+ Returns:
141
+ list[RawRebuildIssue]: Empty when the model is clean.
142
+ """
143
+ issues = _from_whats_wrong(adapter, model)
144
+ if issues is None:
145
+ issues = _from_tree_walk(adapter, model)
146
+ return issues