swdesigntables 0.1.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.
@@ -0,0 +1,172 @@
1
+ """Build SOLIDWORKS design tables from Python.
2
+
3
+ A design table is an Excel sheet embedded in a SOLIDWORKS model: each row makes
4
+ a configuration, each column drives a parameter. The file format has a few
5
+ load-bearing details that are easy to get wrong and fail silently, so this
6
+ package writes them for you:
7
+
8
+ * ``Design Table for: <model>`` in A1
9
+ * headers from **B2**, never A2
10
+ * the workbook-level defined name ``Family`` pointing at ``Sheet1!$A$2``
11
+ * equation values written as literal text, not as Excel formulas
12
+
13
+ Quickstart::
14
+
15
+ import swdesigntables as sw
16
+
17
+ length = sw.dimension("Length", "Boss-Extrude1")
18
+ holes = sw.state("HolePattern")
19
+
20
+ table = sw.DesignTable("BRK-MASTER", [length, holes])
21
+ table.add_configuration("BRK-025", {length: 25.0, holes: sw.SUPPRESSED})
22
+ table.add_configuration("BRK-040", {length: 40.0, holes: sw.UNSUPPRESSED})
23
+ table.save("brk_master_dt.xlsx")
24
+
25
+ Then insert it with Insert > Tables > Design Table > From file.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ __version__ = "0.1.0"
31
+
32
+ from swdesigntables.columns import (
33
+ Column,
34
+ color,
35
+ column,
36
+ comment,
37
+ component_config,
38
+ component_display_state,
39
+ component_fixed,
40
+ component_state,
41
+ component_visibility,
42
+ description,
43
+ dimension,
44
+ display_state,
45
+ global_variable,
46
+ never_expand_in_bom,
47
+ parent,
48
+ parse_header,
49
+ part_number,
50
+ prop,
51
+ raw,
52
+ state,
53
+ suppress_new_components,
54
+ suppress_new_features,
55
+ sw_property,
56
+ tolerance,
57
+ user_notes,
58
+ )
59
+ from swdesigntables.errors import (
60
+ DesignTableError,
61
+ DesignTableWarning,
62
+ DuplicateColumnError,
63
+ DuplicateConfigurationError,
64
+ InvalidNameError,
65
+ ReservedNameError,
66
+ UnknownColumnError,
67
+ UnknownParameterError,
68
+ ValidationError,
69
+ )
70
+ from swdesigntables.parameters import (
71
+ ParameterSpec,
72
+ Status,
73
+ ValueKind,
74
+ get_parameter,
75
+ list_parameters,
76
+ register_parameter,
77
+ )
78
+ from swdesigntables.table import (
79
+ Configuration,
80
+ DesignTable,
81
+ ExtraSheet,
82
+ sanitize_configuration_name,
83
+ )
84
+ from swdesigntables.template import TableTemplate, blank_table
85
+ from swdesigntables.validation import Issue, Severity, ValidationReport
86
+ from swdesigntables.values import (
87
+ SUPPRESSED,
88
+ UNSUPPRESSED,
89
+ CellValue,
90
+ ComponentState,
91
+ Expression,
92
+ MissingValue,
93
+ State,
94
+ StateFormat,
95
+ YesNo,
96
+ )
97
+ from swdesigntables.vocabulary import ENGLISH, Vocabulary
98
+ from swdesigntables.writer import FAMILY_NAME, TITLE_PREFIX
99
+
100
+ __all__ = [
101
+ "__version__",
102
+ # model
103
+ "DesignTable",
104
+ "Configuration",
105
+ "ExtraSheet",
106
+ "TableTemplate",
107
+ "blank_table",
108
+ "sanitize_configuration_name",
109
+ # columns
110
+ "Column",
111
+ "column",
112
+ "parse_header",
113
+ "dimension",
114
+ "global_variable",
115
+ "state",
116
+ "prop",
117
+ "description",
118
+ "parent",
119
+ "display_state",
120
+ "component_config",
121
+ "component_state",
122
+ "component_visibility",
123
+ "component_fixed",
124
+ "component_display_state",
125
+ "comment",
126
+ "color",
127
+ "part_number",
128
+ "user_notes",
129
+ "never_expand_in_bom",
130
+ "suppress_new_features",
131
+ "suppress_new_components",
132
+ "sw_property",
133
+ "tolerance",
134
+ "raw",
135
+ # parameters
136
+ "ParameterSpec",
137
+ "Status",
138
+ "ValueKind",
139
+ "register_parameter",
140
+ "get_parameter",
141
+ "list_parameters",
142
+ # values
143
+ "State",
144
+ "ComponentState",
145
+ "YesNo",
146
+ "StateFormat",
147
+ "Expression",
148
+ "MissingValue",
149
+ "CellValue",
150
+ "SUPPRESSED",
151
+ "UNSUPPRESSED",
152
+ # vocabulary
153
+ "Vocabulary",
154
+ "ENGLISH",
155
+ # validation
156
+ "ValidationReport",
157
+ "Issue",
158
+ "Severity",
159
+ # errors
160
+ "DesignTableError",
161
+ "ValidationError",
162
+ "DuplicateColumnError",
163
+ "DuplicateConfigurationError",
164
+ "UnknownColumnError",
165
+ "InvalidNameError",
166
+ "ReservedNameError",
167
+ "UnknownParameterError",
168
+ "DesignTableWarning",
169
+ # constants
170
+ "FAMILY_NAME",
171
+ "TITLE_PREFIX",
172
+ ]
@@ -0,0 +1,272 @@
1
+ """Typed columns and the factories that build them.
2
+
3
+ A column is a parameter spec plus the arguments that fill its template. It is
4
+ frozen and hashable, so a column object is the key you use when supplying row
5
+ values. That is the whole point: values are addressed by identity, never by
6
+ column number.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ from swdesigntables.parameters import (
14
+ RAW,
15
+ ParameterSpec,
16
+ Status,
17
+ ValueKind,
18
+ get_parameter,
19
+ list_parameters,
20
+ )
21
+ from swdesigntables.vocabulary import DEFAULT_VOCABULARY, Vocabulary
22
+
23
+ __all__ = [
24
+ "Column",
25
+ "column",
26
+ "parse_header",
27
+ "dimension",
28
+ "global_variable",
29
+ "state",
30
+ "prop",
31
+ "description",
32
+ "parent",
33
+ "display_state",
34
+ "component_config",
35
+ "component_state",
36
+ "component_visibility",
37
+ "component_fixed",
38
+ "component_display_state",
39
+ "comment",
40
+ "color",
41
+ "part_number",
42
+ "user_notes",
43
+ "never_expand_in_bom",
44
+ "suppress_new_features",
45
+ "suppress_new_components",
46
+ "sw_property",
47
+ "tolerance",
48
+ "raw",
49
+ ]
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class Column:
54
+ """One design table column.
55
+
56
+ Two columns are equal when they name the same parameter with the same
57
+ arguments, independent of the vocabulary used to render them.
58
+ """
59
+
60
+ spec: ParameterSpec
61
+ args: tuple[tuple[str, str], ...] = ()
62
+
63
+ def header(self, vocabulary: Vocabulary | None = None) -> str:
64
+ """Return the text that goes in row 2 for this column."""
65
+ return self.spec.render(dict(self.args), vocabulary or DEFAULT_VOCABULARY)
66
+
67
+ @property
68
+ def key(self) -> str:
69
+ """A stable identity for this column that does not depend on language."""
70
+ rendered = ",".join(f"{name}={value}" for name, value in self.args)
71
+ return f"{self.spec.name}({rendered})"
72
+
73
+ @property
74
+ def status(self) -> Status:
75
+ """How well confirmed this column's syntax is."""
76
+ return self.spec.status
77
+
78
+ @property
79
+ def value_kind(self) -> ValueKind:
80
+ """The kind of value this column accepts."""
81
+ return self.spec.value_kind
82
+
83
+ def __str__(self) -> str:
84
+ return self.header()
85
+
86
+
87
+ def column(parameter: str, **args: object) -> Column:
88
+ """Build a column for any registered parameter, including runtime ones."""
89
+ spec = get_parameter(parameter)
90
+ return _build(spec, **args)
91
+
92
+
93
+ def _build(spec: ParameterSpec, **args: object) -> Column:
94
+ normalized = tuple(sorted((name, str(value)) for name, value in args.items()))
95
+ column_ = Column(spec=spec, args=normalized)
96
+ # Render once so a missing or malformed argument fails at the call site.
97
+ column_.header()
98
+ return column_
99
+
100
+
101
+ def _component_ref(component: str, instance: int | None) -> str:
102
+ """Return a component reference, appending the instance number if given."""
103
+ if instance is None:
104
+ return component
105
+ if component.endswith(">"):
106
+ raise ValueError(
107
+ f"Component {component!r} already carries an instance number; "
108
+ "pass either the suffix or the instance argument, not both."
109
+ )
110
+ return f"{component}<{instance}>"
111
+
112
+
113
+ # --- verified parameters -------------------------------------------------
114
+
115
+
116
+ def dimension(name: str, feature: str) -> Column:
117
+ """Drive a dimension directly, as in ``Length@Boss-Extrude1``.
118
+
119
+ If an equation governs the dimension, this column is ignored on rebuild;
120
+ drive the global variable with :func:`global_variable` instead.
121
+ """
122
+ return _build(get_parameter("dimension"), dimension=name, feature=feature)
123
+
124
+
125
+ def global_variable(name: str) -> Column:
126
+ """Drive a global variable, as in ``$VALUE@width@Equations``."""
127
+ return _build(get_parameter("global_variable"), variable=name)
128
+
129
+
130
+ def state(feature: str) -> Column:
131
+ """Suppress or unsuppress a feature, as in ``$STATE@Draft2``."""
132
+ return _build(get_parameter("state"), feature=feature)
133
+
134
+
135
+ def prop(name: str) -> Column:
136
+ """Set a custom property, as in ``$PRP@Description``."""
137
+ return _build(get_parameter("prop"), name=name)
138
+
139
+
140
+ def description() -> Column:
141
+ """The ``$DESCRIPTION`` column."""
142
+ return _build(get_parameter("description"))
143
+
144
+
145
+ def parent() -> Column:
146
+ """The ``$PARENT`` column, which makes each row a derived configuration."""
147
+ return _build(get_parameter("parent"))
148
+
149
+
150
+ def display_state() -> Column:
151
+ """The ``$DISPLAYSTATE`` column."""
152
+ return _build(get_parameter("display_state"))
153
+
154
+
155
+ def component_config(component: str, instance: int | None = None) -> Column:
156
+ """Choose a component's configuration, as in ``$CONFIGURATION@Arm<1>``."""
157
+ return _build(
158
+ get_parameter("component_config"), component=_component_ref(component, instance)
159
+ )
160
+
161
+
162
+ # --- documented parameters ----------------------------------------------
163
+
164
+
165
+ def component_state(component: str, instance: int | None = None) -> Column:
166
+ """Suppress or resolve a component. Values are ``S``/``R``."""
167
+ return _build(
168
+ get_parameter("component_state"), component=_component_ref(component, instance)
169
+ )
170
+
171
+
172
+ def component_visibility(component: str, instance: int | None = None) -> Column:
173
+ """Show or hide a component, as in ``$SHOW@Arm<1>``."""
174
+ return _build(
175
+ get_parameter("component_visibility"), component=_component_ref(component, instance)
176
+ )
177
+
178
+
179
+ def component_fixed(component: str, instance: int | None = None) -> Column:
180
+ """Fix or float a component, as in ``$FIXED@Arm<1>``."""
181
+ return _build(
182
+ get_parameter("component_fixed"), component=_component_ref(component, instance)
183
+ )
184
+
185
+
186
+ def component_display_state(component: str, instance: int | None = None) -> Column:
187
+ """Choose a component's display state."""
188
+ return _build(
189
+ get_parameter("component_display_state"),
190
+ component=_component_ref(component, instance),
191
+ )
192
+
193
+
194
+ def comment() -> Column:
195
+ """A ``$COMMENT`` column, which SOLIDWORKS ignores."""
196
+ return _build(get_parameter("comment"))
197
+
198
+
199
+ def color() -> Column:
200
+ """The ``$COLOR`` column, taking a 32-bit RGB integer."""
201
+ return _build(get_parameter("color"))
202
+
203
+
204
+ def part_number() -> Column:
205
+ """The ``$PARTNUMBER`` column used by the bill of materials."""
206
+ return _build(get_parameter("part_number"))
207
+
208
+
209
+ def user_notes() -> Column:
210
+ """The ``$USER_NOTES`` column."""
211
+ return _build(get_parameter("user_notes"))
212
+
213
+
214
+ def never_expand_in_bom() -> Column:
215
+ """The ``$NEVER_EXPAND_IN_BOM`` column, taking ``Y``/``N``."""
216
+ return _build(get_parameter("never_expand_in_bom"))
217
+
218
+
219
+ def suppress_new_features() -> Column:
220
+ """The ``$SUPPRESS NEW FEATURES`` column, taking ``Y``/``N``."""
221
+ return _build(get_parameter("suppress_new_features"))
222
+
223
+
224
+ def suppress_new_components() -> Column:
225
+ """The ``$SUPPRESS NEW COMPONENTS`` column, taking ``Y``/``N``."""
226
+ return _build(get_parameter("suppress_new_components"))
227
+
228
+
229
+ def sw_property(name: str) -> Column:
230
+ """A SOLIDWORKS-computed property such as ``$SW-Mass``. Read only."""
231
+ return _build(get_parameter("sw_property"), name=name)
232
+
233
+
234
+ def tolerance(name: str, feature: str) -> Column:
235
+ """Set a dimension's tolerance, as in ``$TOLERANCE@D1@Sketch1``."""
236
+ return _build(get_parameter("tolerance"), dimension=name, feature=feature)
237
+
238
+
239
+ # --- escape hatch --------------------------------------------------------
240
+
241
+
242
+ def raw(text: str) -> Column:
243
+ """Write a header verbatim, with no syntax checking.
244
+
245
+ The floor under this library's coverage: whatever SOLIDWORKS accepts, you
246
+ can write, even if the parameter is not in the catalogue.
247
+ """
248
+ if not text.strip():
249
+ raise ValueError("A raw header cannot be empty")
250
+ return Column(spec=RAW, args=(("text", text),))
251
+
252
+
253
+ def parse_header(text: str, vocabulary: Vocabulary | None = None) -> Column:
254
+ """Turn an existing header string into the typed column that renders it.
255
+
256
+ This is the migration path for scripts that already hold a list of header
257
+ strings: parse them and keep working, with validation and identity-based
258
+ row values from then on. Anything unrecognized becomes a raw column.
259
+ """
260
+ vocab = vocabulary or DEFAULT_VOCABULARY
261
+ stripped = text.strip()
262
+ if not stripped:
263
+ raise ValueError("Cannot parse an empty header")
264
+ for spec in list_parameters():
265
+ match = spec.pattern(vocab).match(stripped)
266
+ if match is None:
267
+ continue
268
+ args = {name: value for name, value in match.groupdict().items() if value is not None}
269
+ if set(args) != set(spec.fields):
270
+ continue
271
+ return _build(spec, **args)
272
+ return raw(stripped)
@@ -0,0 +1,67 @@
1
+ """Exception and warning hierarchy for swdesigntables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from swdesigntables.validation import ValidationReport
9
+
10
+ __all__ = [
11
+ "DesignTableError",
12
+ "ValidationError",
13
+ "DuplicateColumnError",
14
+ "DuplicateConfigurationError",
15
+ "UnknownColumnError",
16
+ "InvalidNameError",
17
+ "ReservedNameError",
18
+ "UnknownParameterError",
19
+ "DesignTableWarning",
20
+ ]
21
+
22
+
23
+ class DesignTableError(Exception):
24
+ """Base class for every error raised by this package."""
25
+
26
+
27
+ class ValidationError(DesignTableError):
28
+ """Raised when a table fails validation.
29
+
30
+ The full report is available on the ``report`` attribute, so a caller can
31
+ inspect every issue instead of parsing the message.
32
+ """
33
+
34
+ def __init__(self, message: str, report: ValidationReport | None = None) -> None:
35
+ super().__init__(message)
36
+ self.report = report
37
+
38
+
39
+ class DuplicateColumnError(ValidationError):
40
+ """Two columns render the same header."""
41
+
42
+
43
+ class DuplicateConfigurationError(ValidationError):
44
+ """Two configurations share a name."""
45
+
46
+
47
+ class UnknownColumnError(ValidationError):
48
+ """A configuration supplies a value for a column that was never declared."""
49
+
50
+
51
+ class InvalidNameError(ValidationError):
52
+ """A configuration, sheet or defined name is not usable in SOLIDWORKS."""
53
+
54
+
55
+ class ReservedNameError(InvalidNameError):
56
+ """A name reserved by SOLIDWORKS was used (for example ``_SWX``)."""
57
+
58
+
59
+ class UnknownParameterError(DesignTableError):
60
+ """A parameter name was requested that is not in the registry."""
61
+
62
+
63
+ class DesignTableWarning(UserWarning):
64
+ """Something is probably a mistake, but the file is still written.
65
+
66
+ Promote these to errors with ``-W error::swdesigntables.DesignTableWarning``.
67
+ """