gitcad-core 0.7.7__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.
- gitcad/_version.py +1 -0
- gitcad/canonical.py +56 -0
- gitcad/errors.py +84 -0
- gitcad/expr.py +148 -0
- gitcad/identity.py +134 -0
- gitcad/importers/report.py +31 -0
- gitcad/part/__init__.py +29 -0
- gitcad/part/assembly.py +229 -0
- gitcad/part/bought.py +62 -0
- gitcad/part/exploded.py +106 -0
- gitcad/part/interface.py +182 -0
- gitcad/part/interference.py +71 -0
- gitcad/part/lockfile.py +133 -0
- gitcad/part/manifest.py +77 -0
- gitcad/part/matesolve.py +88 -0
- gitcad/part/semver.py +81 -0
- gitcad/report/__init__.py +20 -0
- gitcad/report/fingerprint.py +22 -0
- gitcad/report/reduce.py +88 -0
- gitcad/report/scrub.py +116 -0
- gitcad/seams.py +249 -0
- gitcad/strokefont.py +84 -0
- gitcad/waivers.py +59 -0
- gitcad_core-0.7.7.dist-info/METADATA +40 -0
- gitcad_core-0.7.7.dist-info/RECORD +26 -0
- gitcad_core-0.7.7.dist-info/WHEEL +4 -0
gitcad/seams.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""The six seams — the only load-bearing interfaces in gitcad.
|
|
2
|
+
|
|
3
|
+
Rule (see ADR-0002): everything swappable lives behind one of these Protocols.
|
|
4
|
+
Agents work *inside* a seam, never across one. A "major architecture change"
|
|
5
|
+
should mean writing a new backend for one seam, not a rewrite. Keeping the seam
|
|
6
|
+
count small and the interfaces narrow is what preserves that property, so adding
|
|
7
|
+
a seam or widening one is a human-sign-off change (see ``CODEOWNERS``).
|
|
8
|
+
|
|
9
|
+
These are :class:`typing.Protocol` classes: backends satisfy them structurally,
|
|
10
|
+
so a backend package need not import gitcad-core to implement one.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any, Protocol, runtime_checkable
|
|
16
|
+
|
|
17
|
+
from gitcad.errors import ValidationReport
|
|
18
|
+
|
|
19
|
+
# A Shape is an opaque, backend-owned handle to geometry. Core code never
|
|
20
|
+
# inspects its internals — it only passes it back to the same Kernel. This is
|
|
21
|
+
# what lets OCCT, a mesh kernel, or a future Rust kernel all satisfy `Kernel`.
|
|
22
|
+
Shape = Any
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@runtime_checkable
|
|
26
|
+
class Kernel(Protocol):
|
|
27
|
+
"""Geometry backend. The one seam that talks b-rep math.
|
|
28
|
+
|
|
29
|
+
Intent-level, not control-point level: callers express *what must be true*
|
|
30
|
+
(through these points, tangent to that face, curvature-continuous) and the
|
|
31
|
+
kernel solves the geometry. See ADR-0002 for why the API is intent-based.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
"""Backend identity for fingerprints, e.g. ``"occt-7.8.1"`` or ``"null"``."""
|
|
36
|
+
|
|
37
|
+
def box(self, dx: float, dy: float, dz: float) -> Shape: ...
|
|
38
|
+
|
|
39
|
+
def cylinder(self, radius: float, height: float) -> Shape: ...
|
|
40
|
+
|
|
41
|
+
def sphere(self, radius: float) -> Shape: ...
|
|
42
|
+
|
|
43
|
+
def cone(self, r1: float, r2: float, height: float) -> Shape: ...
|
|
44
|
+
|
|
45
|
+
def boolean(self, op: str, a: Shape, b: Shape) -> Shape:
|
|
46
|
+
"""``op`` in {"union", "cut", "intersect"}."""
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
def extrude(self, profile: dict, height: float) -> Shape:
|
|
50
|
+
"""Linear sweep of a closed XY profile (gitcad.sketch form) along +Z."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def revolve(self, profile: dict, angle_deg: float = 360.0) -> Shape:
|
|
54
|
+
"""Revolve a closed XY profile about the Y axis."""
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def loft(self, sections: list[tuple[dict, float]], *, ruled: bool = False) -> Shape:
|
|
58
|
+
"""Solid through closed XY profiles stacked at their z heights."""
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
def sweep(self, profile: dict, path: list[tuple[float, float, float]]) -> Shape:
|
|
62
|
+
"""Sweep a closed XY profile along a 3D polyline path from (0,0,0)."""
|
|
63
|
+
...
|
|
64
|
+
|
|
65
|
+
def mirror(self, shape: Shape, plane: str) -> Shape:
|
|
66
|
+
"""Mirrored copy across a principal plane ("xy"|"yz"|"zx") at origin."""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
def mass_props(self, shape: Shape) -> dict[str, float]:
|
|
70
|
+
"""Unit-density volume, center of mass, inertia tensor about the COM."""
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
def chamfer(self, shape: Shape, edges: list[int] | None, distance: float) -> Shape:
|
|
74
|
+
"""Chamfer by edge enumeration index (same contract as fillet)."""
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
def shell(self, shape: Shape, remove_faces: list[int], thickness: float) -> Shape:
|
|
78
|
+
"""Hollow to a wall thickness, removing listed faces as openings."""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
def fillet(self, shape: Shape, edges: list[int] | None, radius: float) -> Shape:
|
|
82
|
+
"""Fillet by enumeration index into ``entities(shape, "edge")`` order
|
|
83
|
+
(``None`` = all edges). Index resolution from stable entity ids happens
|
|
84
|
+
one level up, in the document build (ADR-0003) — the kernel never sees
|
|
85
|
+
identity, only concrete indices valid for this exact shape."""
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
def entities(self, shape: Shape, kind: str) -> list[dict[str, Any]]:
|
|
89
|
+
"""Enumerate topological entities (``kind`` in {"face","edge","vertex"})
|
|
90
|
+
with a stable, order-independent semantic descriptor each. This is the
|
|
91
|
+
raw material the :class:`IdentityService` turns into durable IDs."""
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def transform(self, shape: Shape, *, translate: tuple[float, float, float] = (0, 0, 0),
|
|
95
|
+
rotate_axis: tuple[float, float, float] = (0, 0, 1), rotate_deg: float = 0.0) -> Shape:
|
|
96
|
+
"""Rigid placement: rotate about an axis through the origin, then
|
|
97
|
+
translate. The primitive every positioned feature builds on."""
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
def scale(self, shape: Shape, fx: float, fy: float | None = None,
|
|
101
|
+
fz: float | None = None) -> Shape:
|
|
102
|
+
"""Uniform (one factor) or anisotropic (three) scale about the origin."""
|
|
103
|
+
...
|
|
104
|
+
|
|
105
|
+
def draft(self, shape: Shape, faces: list[int], angle_deg: float,
|
|
106
|
+
pull: tuple[float, float, float] = (0, 0, 1),
|
|
107
|
+
neutral_z: float = 0.0) -> Shape:
|
|
108
|
+
"""Molding draft: tilt the listed faces (empty = all faces normal to
|
|
109
|
+
pull) by ``angle_deg`` about the neutral plane z=``neutral_z``."""
|
|
110
|
+
...
|
|
111
|
+
|
|
112
|
+
def helix(self, radius: float, pitch: float, turns: float,
|
|
113
|
+
ccw: bool = True) -> Shape:
|
|
114
|
+
"""A 3D helical wire about +z — the spine of springs and threads."""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
def pipe(self, spine: Shape, profile_diameter: float) -> Shape:
|
|
118
|
+
"""Sweep a circular section along a wire spine (springs, wire runs)."""
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
def validate(self, shape: Shape) -> ValidationReport:
|
|
122
|
+
"""Machine-readable geometric checks (watertight, self-intersection,
|
|
123
|
+
continuity). The core of the agent verification loop."""
|
|
124
|
+
...
|
|
125
|
+
|
|
126
|
+
def measure(self, shape: Shape) -> dict[str, float]:
|
|
127
|
+
"""Mass properties: volume, area, centroid — deterministic oracle used
|
|
128
|
+
by golden tests and by geometric-diff in PRs."""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def bbox(self, shape: Shape) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
|
132
|
+
"""Axis-aligned bounding box ((minx,miny,minz),(maxx,maxy,maxz)) —
|
|
133
|
+
the source of a derived part envelope (ADR-0008)."""
|
|
134
|
+
...
|
|
135
|
+
|
|
136
|
+
def export_step(self, shape: Shape, path: str) -> None:
|
|
137
|
+
"""Write ISO 10303-21 STEP — the mechanical interchange deliverable."""
|
|
138
|
+
...
|
|
139
|
+
|
|
140
|
+
def export_stl(self, shape: Shape, path: str, *, deflection: float = 0.1) -> None:
|
|
141
|
+
"""Tessellate and write STL for 3D printing."""
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
def import_step(self, path: str) -> Shape:
|
|
145
|
+
"""Read STEP — the onboarding path for existing mechanical work."""
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
def import_brep(self, path: str) -> Shape:
|
|
149
|
+
"""Read OCCT-native .brep (what .FCStd embeds per object)."""
|
|
150
|
+
...
|
|
151
|
+
|
|
152
|
+
def export_brep(self, shape: Shape, path: str) -> None:
|
|
153
|
+
"""Write OCCT-native .brep — the content-addressed import artifact."""
|
|
154
|
+
...
|
|
155
|
+
|
|
156
|
+
def compound(self, shapes: list[Shape]) -> Shape:
|
|
157
|
+
"""Combine shapes into one compound (multi-body import container)."""
|
|
158
|
+
...
|
|
159
|
+
|
|
160
|
+
def hlr_project(self, shape: Shape, direction: tuple[float, float, float],
|
|
161
|
+
xdir: tuple[float, float, float], *,
|
|
162
|
+
deflection: float = 0.05) -> dict[str, list[list[tuple[float, float]]]]:
|
|
163
|
+
"""Hidden-line-removal projection to 2D polylines (visible/hidden) —
|
|
164
|
+
the geometry backend of the DrawingEngine (ADR-0002)."""
|
|
165
|
+
...
|
|
166
|
+
|
|
167
|
+
def tessellate(self, shape: Shape, *, deflection: float = 0.2) -> dict[str, list]:
|
|
168
|
+
"""Triangulate for display: {"positions": [x,y,z,...], "indices":
|
|
169
|
+
[a,b,c,...]} — the viewer/Renderer geometry source."""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@runtime_checkable
|
|
174
|
+
class IdentityService(Protocol):
|
|
175
|
+
"""Assigns stable IDs to topological entities (the topological-naming fix).
|
|
176
|
+
|
|
177
|
+
An ID must survive upstream edits, reordering, and rebuilds. Identity is
|
|
178
|
+
derived from *construction lineage + geometric fingerprint*, never from an
|
|
179
|
+
ordinal index. See ADR-0003 — this is the decision that makes git workflows
|
|
180
|
+
(merge, rebase) survivable for CAD.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
def assign(self, descriptor: dict[str, Any], lineage: tuple[str, ...]) -> str: ...
|
|
184
|
+
|
|
185
|
+
def resolve(self, entity_id: str, candidates: list[dict[str, Any]]) -> dict[str, Any] | None:
|
|
186
|
+
"""Re-bind a stored ID to the best-matching current entity after a
|
|
187
|
+
rebuild, or ``None`` if it genuinely no longer exists."""
|
|
188
|
+
...
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@runtime_checkable
|
|
192
|
+
class DocumentModel(Protocol):
|
|
193
|
+
"""The feature tree and its canonical text (de)serialization.
|
|
194
|
+
|
|
195
|
+
Source of truth is text (ADR-0004): this seam owns the mapping between the
|
|
196
|
+
editable, git-diffable text form and the in-memory feature graph.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def dumps(self) -> str: ...
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def loads(cls, text: str) -> "DocumentModel": ...
|
|
203
|
+
|
|
204
|
+
def content_hash(self) -> str: ...
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@runtime_checkable
|
|
208
|
+
class Renderer(Protocol):
|
|
209
|
+
"""Headless tessellation → images / glTF. The agent's eyes (stub for now)."""
|
|
210
|
+
|
|
211
|
+
def render(self, shape: Shape, *, views: list[str], overlay: str | None = None) -> dict[str, bytes]: ...
|
|
212
|
+
|
|
213
|
+
def to_gltf(self, shape: Shape) -> bytes: ...
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@runtime_checkable
|
|
217
|
+
class DrawingEngine(Protocol):
|
|
218
|
+
"""3D → 2D drafting: HLR projection, associative dimensions, GD&T, sheets.
|
|
219
|
+
|
|
220
|
+
The mechanical-engineering deliverable. Projection is delegated to the
|
|
221
|
+
Kernel's HLR; this seam owns the 2D document model on top. Stub for now.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def project(self, shape: Shape, direction: str) -> dict[str, Any]:
|
|
225
|
+
"""Return 2D edges tagged visible/hidden for one orthographic view."""
|
|
226
|
+
...
|
|
227
|
+
|
|
228
|
+
def export(self, sheet: dict[str, Any], fmt: str) -> bytes:
|
|
229
|
+
"""``fmt`` in {"svg", "pdf", "dxf"}."""
|
|
230
|
+
...
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@runtime_checkable
|
|
234
|
+
class Storage(Protocol):
|
|
235
|
+
"""Git-backed store: text models are source, geometry is a build artifact.
|
|
236
|
+
|
|
237
|
+
ADR-0004. Models are committed as text; ``.brep``/STEP/PDF are generated,
|
|
238
|
+
content-addressed artifacts — never source. Stub for now.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def read_model(self, ref: str, path: str) -> str: ...
|
|
242
|
+
|
|
243
|
+
def write_model(self, path: str, text: str, message: str) -> str:
|
|
244
|
+
"""Commit a model revision; return the commit id."""
|
|
245
|
+
...
|
|
246
|
+
|
|
247
|
+
def put_artifact(self, data: bytes) -> str:
|
|
248
|
+
"""Store a build artifact content-addressed; return its digest."""
|
|
249
|
+
...
|
gitcad/strokefont.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""A minimal single-stroke vector font for fab outputs (KiCad-map P3).
|
|
2
|
+
|
|
3
|
+
Gerber legend layers carry strokes, not text — every silkscreen ref is
|
|
4
|
+
drawn. Glyphs live in a unit cell (x 0..0.6, y 0..1, y-up), each a list
|
|
5
|
+
of polylines; ``text_strokes`` scales and places them. Deliberately
|
|
6
|
+
spartan: uppercase + digits + the punctuation that appears in refs and
|
|
7
|
+
values (text is uppercased on the way in — silkscreen is not typography).
|
|
8
|
+
Unknown characters render as a hollow box, visibly wrong rather than
|
|
9
|
+
silently absent.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
W = 0.6 # glyph width in cell units (height is 1.0)
|
|
15
|
+
ADVANCE = 0.85 # horizontal advance per character
|
|
16
|
+
|
|
17
|
+
_G: dict[str, list[list[tuple[float, float]]]] = {
|
|
18
|
+
"0": [[(0, 0), (0.6, 0), (0.6, 1), (0, 1), (0, 0)], [(0, 0.15), (0.6, 0.85)]],
|
|
19
|
+
"1": [[(0.15, 0.8), (0.35, 1), (0.35, 0)], [(0.15, 0), (0.55, 0)]],
|
|
20
|
+
"2": [[(0, 0.85), (0.15, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.6), (0, 0), (0.6, 0)]],
|
|
21
|
+
"3": [[(0, 1), (0.6, 1), (0.3, 0.6), (0.6, 0.35), (0.6, 0.15), (0.45, 0), (0.1, 0), (0, 0.1)]],
|
|
22
|
+
"4": [[(0.45, 0), (0.45, 1), (0, 0.35), (0.6, 0.35)]],
|
|
23
|
+
"5": [[(0.6, 1), (0, 1), (0, 0.55), (0.45, 0.55), (0.6, 0.4), (0.6, 0.15), (0.45, 0), (0, 0)]],
|
|
24
|
+
"6": [[(0.5, 1), (0.1, 1), (0, 0.85), (0, 0.15), (0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 0.4), (0.45, 0.5), (0, 0.5)]],
|
|
25
|
+
"7": [[(0, 1), (0.6, 1), (0.2, 0)]],
|
|
26
|
+
"8": [[(0.1, 0.55), (0, 0.7), (0, 0.85), (0.15, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.7), (0.5, 0.55), (0.1, 0.55), (0, 0.4), (0, 0.15), (0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 0.4), (0.5, 0.55)]],
|
|
27
|
+
"9": [[(0.6, 0.5), (0.15, 0.5), (0, 0.6), (0, 0.85), (0.15, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.15), (0.5, 0), (0.1, 0)]],
|
|
28
|
+
"A": [[(0, 0), (0.3, 1), (0.6, 0)], [(0.12, 0.4), (0.48, 0.4)]],
|
|
29
|
+
"B": [[(0, 0), (0, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.65), (0.45, 0.55), (0, 0.55)], [(0.45, 0.55), (0.6, 0.4), (0.6, 0.15), (0.45, 0), (0, 0)]],
|
|
30
|
+
"C": [[(0.6, 0.85), (0.45, 1), (0.15, 1), (0, 0.85), (0, 0.15), (0.15, 0), (0.45, 0), (0.6, 0.15)]],
|
|
31
|
+
"D": [[(0, 0), (0, 1), (0.4, 1), (0.6, 0.8), (0.6, 0.2), (0.4, 0), (0, 0)]],
|
|
32
|
+
"E": [[(0.6, 1), (0, 1), (0, 0), (0.6, 0)], [(0, 0.55), (0.45, 0.55)]],
|
|
33
|
+
"F": [[(0.6, 1), (0, 1), (0, 0)], [(0, 0.55), (0.45, 0.55)]],
|
|
34
|
+
"G": [[(0.6, 0.85), (0.45, 1), (0.15, 1), (0, 0.85), (0, 0.15), (0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 0.45), (0.35, 0.45)]],
|
|
35
|
+
"H": [[(0, 0), (0, 1)], [(0.6, 0), (0.6, 1)], [(0, 0.55), (0.6, 0.55)]],
|
|
36
|
+
"I": [[(0.1, 0), (0.5, 0)], [(0.3, 0), (0.3, 1)], [(0.1, 1), (0.5, 1)]],
|
|
37
|
+
"J": [[(0.6, 1), (0.6, 0.15), (0.45, 0), (0.15, 0), (0, 0.15)]],
|
|
38
|
+
"K": [[(0, 0), (0, 1)], [(0.6, 1), (0, 0.5), (0.6, 0)]],
|
|
39
|
+
"L": [[(0, 1), (0, 0), (0.6, 0)]],
|
|
40
|
+
"M": [[(0, 0), (0, 1), (0.3, 0.5), (0.6, 1), (0.6, 0)]],
|
|
41
|
+
"N": [[(0, 0), (0, 1), (0.6, 0), (0.6, 1)]],
|
|
42
|
+
"O": [[(0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 0.85), (0.45, 1), (0.15, 1), (0, 0.85), (0, 0.15), (0.15, 0)]],
|
|
43
|
+
"P": [[(0, 0), (0, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.6), (0.45, 0.5), (0, 0.5)]],
|
|
44
|
+
"Q": [[(0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 0.85), (0.45, 1), (0.15, 1), (0, 0.85), (0, 0.15), (0.15, 0)], [(0.35, 0.25), (0.6, 0)]],
|
|
45
|
+
"R": [[(0, 0), (0, 1), (0.45, 1), (0.6, 0.85), (0.6, 0.6), (0.45, 0.5), (0, 0.5)], [(0.3, 0.5), (0.6, 0)]],
|
|
46
|
+
"S": [[(0.6, 0.85), (0.45, 1), (0.15, 1), (0, 0.85), (0, 0.65), (0.6, 0.35), (0.6, 0.15), (0.45, 0), (0.15, 0), (0, 0.15)]],
|
|
47
|
+
"T": [[(0, 1), (0.6, 1)], [(0.3, 1), (0.3, 0)]],
|
|
48
|
+
"U": [[(0, 1), (0, 0.15), (0.15, 0), (0.45, 0), (0.6, 0.15), (0.6, 1)]],
|
|
49
|
+
"V": [[(0, 1), (0.3, 0), (0.6, 1)]],
|
|
50
|
+
"W": [[(0, 1), (0.15, 0), (0.3, 0.5), (0.45, 0), (0.6, 1)]],
|
|
51
|
+
"X": [[(0, 0), (0.6, 1)], [(0, 1), (0.6, 0)]],
|
|
52
|
+
"Y": [[(0, 1), (0.3, 0.5), (0.6, 1)], [(0.3, 0.5), (0.3, 0)]],
|
|
53
|
+
"Z": [[(0, 1), (0.6, 1), (0, 0), (0.6, 0)]],
|
|
54
|
+
"-": [[(0.1, 0.5), (0.5, 0.5)]],
|
|
55
|
+
"+": [[(0.1, 0.5), (0.5, 0.5)], [(0.3, 0.3), (0.3, 0.7)]],
|
|
56
|
+
".": [[(0.25, 0), (0.35, 0), (0.35, 0.1), (0.25, 0.1), (0.25, 0)]],
|
|
57
|
+
"_": [[(0, 0), (0.6, 0)]],
|
|
58
|
+
"/": [[(0, 0), (0.6, 1)]],
|
|
59
|
+
"%": [[(0, 0), (0.6, 1)], [(0.05, 0.8), (0.15, 0.9)], [(0.45, 0.1), (0.55, 0.2)]],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_UNKNOWN = [[(0, 0), (0.6, 0), (0.6, 1), (0, 1), (0, 0)]] # hollow box
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def text_strokes(text: str, x: float, y: float, height: float,
|
|
66
|
+
) -> list[tuple[float, float, float, float]]:
|
|
67
|
+
"""Stroke segments for ``text`` with its lower-left corner at (x, y),
|
|
68
|
+
y-up, glyph height ``height`` mm. Returns (x1, y1, x2, y2) segments."""
|
|
69
|
+
segs: list[tuple[float, float, float, float]] = []
|
|
70
|
+
cx = x
|
|
71
|
+
for ch in text.upper():
|
|
72
|
+
if ch == " ":
|
|
73
|
+
cx += ADVANCE * height
|
|
74
|
+
continue
|
|
75
|
+
for poly in _G.get(ch, _UNKNOWN):
|
|
76
|
+
for (ax, ay), (bx, by) in zip(poly, poly[1:]):
|
|
77
|
+
segs.append((cx + ax * height, y + ay * height,
|
|
78
|
+
cx + bx * height, y + by * height))
|
|
79
|
+
cx += ADVANCE * height
|
|
80
|
+
return segs
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def text_width(text: str, height: float) -> float:
|
|
84
|
+
return len(text) * ADVANCE * height
|
gitcad/waivers.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Check waivers — a silenced check must leave a trace (KiCad-map tier 2).
|
|
2
|
+
|
|
3
|
+
KiCad has per-violation exclusions buried in project files; here a waiver
|
|
4
|
+
is reviewable canonical text next to the design:
|
|
5
|
+
|
|
6
|
+
{"schema": "gitcad/waivers@1", "waivers": [
|
|
7
|
+
{"match": "erc:net-single-pin:TP*", "reason": "test points", "author": "dan"}]}
|
|
8
|
+
|
|
9
|
+
Rules with teeth:
|
|
10
|
+
- every waiver REQUIRES a reason — suppression without rationale refused;
|
|
11
|
+
- ``match`` is an fnmatch glob over the violation string, so one waiver
|
|
12
|
+
can cover a family without hiding unrelated failures;
|
|
13
|
+
- waived violations stay VISIBLE (moved to ``waived``, never deleted);
|
|
14
|
+
- a waiver that matches nothing is reported ``unused`` — stale
|
|
15
|
+
suppressions are debt, and debt shows.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from fnmatch import fnmatchcase
|
|
22
|
+
|
|
23
|
+
from gitcad.errors import GitcadError
|
|
24
|
+
|
|
25
|
+
SCHEMA = "gitcad/waivers@1"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_waivers(text: str) -> list[dict]:
|
|
29
|
+
doc = json.loads(text)
|
|
30
|
+
if doc.get("schema") != SCHEMA:
|
|
31
|
+
raise GitcadError(f"unsupported waivers schema {doc.get('schema')!r}")
|
|
32
|
+
waivers = doc.get("waivers", [])
|
|
33
|
+
for w in waivers:
|
|
34
|
+
if not w.get("match"):
|
|
35
|
+
raise GitcadError("every waiver needs a match pattern")
|
|
36
|
+
if not (w.get("reason") or "").strip():
|
|
37
|
+
raise GitcadError(
|
|
38
|
+
f"waiver {w['match']!r} has no reason — suppression without "
|
|
39
|
+
"rationale is refused")
|
|
40
|
+
return waivers
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def waive(violations: list[str], waivers: list[dict]
|
|
44
|
+
) -> tuple[list[str], list[dict], list[str]]:
|
|
45
|
+
"""(kept, waived [{violation, match, reason}], unused_matches)."""
|
|
46
|
+
kept: list[str] = []
|
|
47
|
+
waived: list[dict] = []
|
|
48
|
+
hits = {w["match"]: 0 for w in waivers}
|
|
49
|
+
for v in violations:
|
|
50
|
+
for w in waivers:
|
|
51
|
+
if fnmatchcase(v, w["match"]):
|
|
52
|
+
hits[w["match"]] += 1
|
|
53
|
+
waived.append({"violation": v, "match": w["match"],
|
|
54
|
+
"reason": w["reason"]})
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
kept.append(v)
|
|
58
|
+
unused = [m for m, n in hits.items() if n == 0]
|
|
59
|
+
return kept, waived, unused
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitcad-core
|
|
3
|
+
Version: 0.7.7
|
|
4
|
+
Summary: gitcad core: canonical text, stable identity, the Part standard, report pipeline
|
|
5
|
+
Project-URL: Homepage, https://gitcad.xyz
|
|
6
|
+
Project-URL: Repository, https://github.com/gitcad-xyz/gitcad
|
|
7
|
+
Project-URL: Issues, https://github.com/gitcad-xyz/gitcad/issues
|
|
8
|
+
Author-email: Dan Willis <danielcwillis@gmail.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: agent,brep,cad,git,headless,identity,part
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Manufacturing
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# gitcad-core
|
|
23
|
+
|
|
24
|
+
Core of [**gitcad**](https://gitcad.xyz) — the agent-first, headless, git-native
|
|
25
|
+
CAD substrate. This package is the dependency-free foundation the mechanical and
|
|
26
|
+
electrical domains build on:
|
|
27
|
+
|
|
28
|
+
- **Canonical text** — every model serializes to byte-stable, diff-friendly
|
|
29
|
+
source; geometry is a build artifact, never committed.
|
|
30
|
+
- **Stable identity** — entity and feature ids derive from construction lineage
|
|
31
|
+
and geometric fingerprint, never from ordinal position.
|
|
32
|
+
- **The Part standard** — one manifest/interface/semver/lockfile model that lets
|
|
33
|
+
mechanical parts, boards, and assemblies compose across domains.
|
|
34
|
+
- **Report pipeline** — the verification/render loop that turns a build into a
|
|
35
|
+
reviewable artifact.
|
|
36
|
+
|
|
37
|
+
Pure Python, no third-party runtime dependencies. Install the full system with
|
|
38
|
+
`pip install gitcad`.
|
|
39
|
+
|
|
40
|
+
Apache-2.0 · https://github.com/gitcad-xyz/gitcad
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
gitcad/_version.py,sha256=wu65dmVM9fKR1rBHH263ls8Ca2FZzb0ejYcrP_Ld0iY,22
|
|
2
|
+
gitcad/canonical.py,sha256=K3QStnwcn6nAAWBbmllDMHaaKzFsgB_5ihCvrthvDb8,2352
|
|
3
|
+
gitcad/errors.py,sha256=SAbWf0tJUDlFKgmmiBz-mmSdZn08zBV8hMWqcB6BT9g,2999
|
|
4
|
+
gitcad/expr.py,sha256=AwJ8WKz2BRGGd8ijibwGPiqHmzRPc9w8SA-iq2dWbpA,5511
|
|
5
|
+
gitcad/identity.py,sha256=xSVyXq1mDLcHqwhiJoTNflbauudRfR0JCTD_5QJn2hU,5355
|
|
6
|
+
gitcad/seams.py,sha256=ZwR-ah2YG-Y_3hRq_RpxI4Vw78XaH8rEMIGn7asVIjo,9981
|
|
7
|
+
gitcad/strokefont.py,sha256=Z9Zg3I1EvL5Eb-duhzReXT_RHe7CtyX5e1CSIZn1-5E,5041
|
|
8
|
+
gitcad/waivers.py,sha256=PeM_30KVEPj38vRTrJx6JG4a5A72VH0l7se0f0_65oU,2133
|
|
9
|
+
gitcad/importers/report.py,sha256=Lshscg6IedTaO5IBF5-g-KUgocxBJ9CSVYTUchcwItE,1166
|
|
10
|
+
gitcad/part/__init__.py,sha256=JTR_NbKFYTTgs5X9AZlJ-HDL4GlWTI52f-2qUgNHAh8,1361
|
|
11
|
+
gitcad/part/assembly.py,sha256=A6aX8nnnz4pd6AaAJlR8iRBnNtbfRbLivzhMbuCurL0,10451
|
|
12
|
+
gitcad/part/bought.py,sha256=-PO9gk81mmjIhYClH1nk4KaMMtzt5dciUXYXGh5dSAQ,2807
|
|
13
|
+
gitcad/part/exploded.py,sha256=F44BzbJmoeHUchoIv9YXOU6BhzdiioUHSWDr7IbRaDk,4321
|
|
14
|
+
gitcad/part/interface.py,sha256=m8ugdgZt9tHLrHWHnKBYEpaEj2aPSHho985yp-LxOZY,7493
|
|
15
|
+
gitcad/part/interference.py,sha256=CIHKnzpACLrXdoItajDe89vSCIuarxQkkX0dmA1QU4A,2891
|
|
16
|
+
gitcad/part/lockfile.py,sha256=rdId_HGZf6A4jOOEGdBaf2Fk11Zfj-j92IylApgrtfM,5588
|
|
17
|
+
gitcad/part/manifest.py,sha256=p-zT_zWoczj9P02ZimCG8KSkerZ_63FKDS0j6QX_BMs,2622
|
|
18
|
+
gitcad/part/matesolve.py,sha256=zSvQ-11FMpZ4_I_43wycsk2jYLEUW0kdhZN7Z-v8qko,3489
|
|
19
|
+
gitcad/part/semver.py,sha256=w6svOd3DszrCLRJlsXEk1Anwp-ebd_YsFJnGT691GRs,2815
|
|
20
|
+
gitcad/report/__init__.py,sha256=R31rqGmbO2qT5jHx_CO8mc34jHPmg8MHIsoqfVr1C3k,918
|
|
21
|
+
gitcad/report/fingerprint.py,sha256=L_b6POelyW5UwYhOnyvpM_p6EE-vXfFHLaF7AdPOxBw,835
|
|
22
|
+
gitcad/report/reduce.py,sha256=1gBuPxYBRrcUQzLlOOlxLRLoPM6l2MeotPhad7gprYM,3243
|
|
23
|
+
gitcad/report/scrub.py,sha256=N6sZGdsddDqlXVbBQhKlMVYPfaMiFwn496Hf5HI05_o,5167
|
|
24
|
+
gitcad_core-0.7.7.dist-info/METADATA,sha256=jg1ztfH0bcyQNl-OvwC64uh2ElUWN2hi8rwZKj8y4mU,1800
|
|
25
|
+
gitcad_core-0.7.7.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
26
|
+
gitcad_core-0.7.7.dist-info/RECORD,,
|