structural-lib-is456 0.9.6__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.
- structural_lib/__init__.py +65 -0
- structural_lib/__main__.py +600 -0
- structural_lib/api.py +425 -0
- structural_lib/bbs.py +852 -0
- structural_lib/compliance.py +430 -0
- structural_lib/constants.py +17 -0
- structural_lib/detailing.py +579 -0
- structural_lib/ductile.py +97 -0
- structural_lib/dxf_export.py +1018 -0
- structural_lib/excel_integration.py +482 -0
- structural_lib/flexure.py +589 -0
- structural_lib/job_cli.py +38 -0
- structural_lib/job_runner.py +209 -0
- structural_lib/materials.py +106 -0
- structural_lib/py.typed +1 -0
- structural_lib/rebar_optimizer.py +282 -0
- structural_lib/serviceability.py +349 -0
- structural_lib/shear.py +128 -0
- structural_lib/tables.py +92 -0
- structural_lib/types.py +121 -0
- structural_lib/utilities.py +52 -0
- structural_lib_is456-0.9.6.dist-info/METADATA +101 -0
- structural_lib_is456-0.9.6.dist-info/RECORD +26 -0
- structural_lib_is456-0.9.6.dist-info/WHEEL +5 -0
- structural_lib_is456-0.9.6.dist-info/licenses/LICENSE +21 -0
- structural_lib_is456-0.9.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Package: structural_lib
|
|
3
|
+
Description: IS 456:2000 Structural Engineering Library
|
|
4
|
+
License: MIT
|
|
5
|
+
|
|
6
|
+
Version is read dynamically from pyproject.toml via importlib.metadata.
|
|
7
|
+
Use api.get_library_version() to get the current version.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version as _get_version
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
# Dynamic version from installed package metadata
|
|
18
|
+
try:
|
|
19
|
+
__version__ = _get_version("structural-lib-is456")
|
|
20
|
+
except PackageNotFoundError:
|
|
21
|
+
__version__ = "0.0.0-dev" # Not installed, development mode
|
|
22
|
+
|
|
23
|
+
# Expose key modules
|
|
24
|
+
from . import constants
|
|
25
|
+
from . import types
|
|
26
|
+
from . import tables
|
|
27
|
+
from . import utilities
|
|
28
|
+
from . import materials
|
|
29
|
+
from . import flexure
|
|
30
|
+
from . import shear
|
|
31
|
+
from . import ductile
|
|
32
|
+
from . import api
|
|
33
|
+
from . import detailing
|
|
34
|
+
from . import serviceability
|
|
35
|
+
from . import compliance
|
|
36
|
+
from . import bbs
|
|
37
|
+
|
|
38
|
+
# DXF export is optional (requires ezdxf)
|
|
39
|
+
dxf_export: Optional[ModuleType]
|
|
40
|
+
try:
|
|
41
|
+
dxf_export = importlib.import_module(f"{__name__}.dxf_export")
|
|
42
|
+
except ImportError:
|
|
43
|
+
dxf_export = None
|
|
44
|
+
|
|
45
|
+
# Excel integration module
|
|
46
|
+
from . import excel_integration
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"__version__",
|
|
50
|
+
"api",
|
|
51
|
+
"bbs",
|
|
52
|
+
"compliance",
|
|
53
|
+
"constants",
|
|
54
|
+
"detailing",
|
|
55
|
+
"ductile",
|
|
56
|
+
"dxf_export",
|
|
57
|
+
"excel_integration",
|
|
58
|
+
"flexure",
|
|
59
|
+
"materials",
|
|
60
|
+
"serviceability",
|
|
61
|
+
"shear",
|
|
62
|
+
"tables",
|
|
63
|
+
"types",
|
|
64
|
+
"utilities",
|
|
65
|
+
]
|
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified CLI entrypoint for structural_lib.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m structural_lib design input.csv -o results.json
|
|
6
|
+
python -m structural_lib bbs results.json -o bbs.csv
|
|
7
|
+
python -m structural_lib dxf results.json -o drawings.dxf
|
|
8
|
+
python -m structural_lib job job.json -o output/
|
|
9
|
+
|
|
10
|
+
This module provides a unified command-line interface with subcommands
|
|
11
|
+
for beam design, bar bending schedules, DXF generation, and job processing.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import List
|
|
21
|
+
|
|
22
|
+
from . import api
|
|
23
|
+
from . import bbs
|
|
24
|
+
from . import detailing
|
|
25
|
+
from . import dxf_export
|
|
26
|
+
from . import excel_integration
|
|
27
|
+
from . import job_runner
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cmd_design(args: argparse.Namespace) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Run beam design from CSV/JSON input file.
|
|
33
|
+
|
|
34
|
+
Reads beam parameters from CSV or JSON, performs IS456 design calculations,
|
|
35
|
+
and outputs design results in JSON format.
|
|
36
|
+
"""
|
|
37
|
+
input_path = Path(args.input)
|
|
38
|
+
|
|
39
|
+
if not input_path.exists():
|
|
40
|
+
print(f"Error: Input file not found: {input_path}", file=sys.stderr)
|
|
41
|
+
return 1
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
# Load beam data
|
|
45
|
+
print(f"Loading beam data from {input_path}...", file=sys.stderr)
|
|
46
|
+
|
|
47
|
+
if input_path.suffix.lower() == ".csv":
|
|
48
|
+
beams = excel_integration.load_beam_data_from_csv(str(input_path))
|
|
49
|
+
elif input_path.suffix.lower() == ".json":
|
|
50
|
+
beams = excel_integration.load_beam_data_from_json(str(input_path))
|
|
51
|
+
else:
|
|
52
|
+
print(
|
|
53
|
+
f"Error: Unsupported file format: {input_path.suffix}", file=sys.stderr
|
|
54
|
+
)
|
|
55
|
+
print("Supported formats: .csv, .json", file=sys.stderr)
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
print(f"Loaded {len(beams)} beam(s)", file=sys.stderr)
|
|
59
|
+
|
|
60
|
+
# Process each beam and collect results
|
|
61
|
+
results = []
|
|
62
|
+
for beam in beams:
|
|
63
|
+
print(f" Processing {beam.story}/{beam.beam_id}...", file=sys.stderr)
|
|
64
|
+
|
|
65
|
+
# Calculate stirrup area (2-legged)
|
|
66
|
+
asv_mm2 = 3.14159 * (beam.stirrup_dia / 2) ** 2 * 2
|
|
67
|
+
|
|
68
|
+
# Run complete design using API
|
|
69
|
+
case_result = api.design_beam_is456(
|
|
70
|
+
units="IS456",
|
|
71
|
+
case_id=f"{beam.story}_{beam.beam_id}",
|
|
72
|
+
b_mm=beam.b,
|
|
73
|
+
D_mm=beam.D,
|
|
74
|
+
d_mm=beam.d,
|
|
75
|
+
d_dash_mm=beam.cover,
|
|
76
|
+
fck_nmm2=beam.fck,
|
|
77
|
+
fy_nmm2=beam.fy,
|
|
78
|
+
mu_knm=beam.Mu,
|
|
79
|
+
vu_kn=beam.Vu,
|
|
80
|
+
asv_mm2=asv_mm2,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Create detailing
|
|
84
|
+
detailing_result = detailing.create_beam_detailing(
|
|
85
|
+
beam_id=beam.beam_id,
|
|
86
|
+
story=beam.story,
|
|
87
|
+
b=beam.b,
|
|
88
|
+
D=beam.D,
|
|
89
|
+
span=beam.span,
|
|
90
|
+
cover=beam.cover,
|
|
91
|
+
fck=beam.fck,
|
|
92
|
+
fy=beam.fy,
|
|
93
|
+
ast_start=beam.Ast_req,
|
|
94
|
+
ast_mid=beam.Ast_req,
|
|
95
|
+
ast_end=beam.Ast_req,
|
|
96
|
+
asc_start=beam.Asc_req,
|
|
97
|
+
asc_mid=beam.Asc_req,
|
|
98
|
+
asc_end=beam.Asc_req,
|
|
99
|
+
stirrup_dia=beam.stirrup_dia,
|
|
100
|
+
stirrup_spacing_start=beam.stirrup_spacing,
|
|
101
|
+
stirrup_spacing_mid=beam.stirrup_spacing * 1.33,
|
|
102
|
+
stirrup_spacing_end=beam.stirrup_spacing,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Compile result
|
|
106
|
+
beam_result = {
|
|
107
|
+
"beam_id": beam.beam_id,
|
|
108
|
+
"story": beam.story,
|
|
109
|
+
"geometry": {
|
|
110
|
+
"b": beam.b,
|
|
111
|
+
"D": beam.D,
|
|
112
|
+
"d": beam.d,
|
|
113
|
+
"span": beam.span,
|
|
114
|
+
"cover": beam.cover,
|
|
115
|
+
},
|
|
116
|
+
"materials": {
|
|
117
|
+
"fck": beam.fck,
|
|
118
|
+
"fy": beam.fy,
|
|
119
|
+
},
|
|
120
|
+
"loads": {
|
|
121
|
+
"Mu": beam.Mu,
|
|
122
|
+
"Vu": beam.Vu,
|
|
123
|
+
},
|
|
124
|
+
"flexure": {
|
|
125
|
+
"ast_req": case_result.flexure.ast_required,
|
|
126
|
+
"asc_req": case_result.flexure.asc_required,
|
|
127
|
+
"status": "OK" if case_result.flexure.is_safe else "FAIL",
|
|
128
|
+
"xu_d": case_result.flexure.xu / beam.d if beam.d > 0 else 0,
|
|
129
|
+
"mu_lim": case_result.flexure.mu_lim,
|
|
130
|
+
"section_type": (
|
|
131
|
+
case_result.flexure.section_type.value
|
|
132
|
+
if hasattr(case_result.flexure.section_type, "value")
|
|
133
|
+
else str(case_result.flexure.section_type)
|
|
134
|
+
),
|
|
135
|
+
},
|
|
136
|
+
"shear": {
|
|
137
|
+
"tau_v": case_result.shear.tv,
|
|
138
|
+
"tau_c": case_result.shear.tc,
|
|
139
|
+
"sv_req": case_result.shear.spacing,
|
|
140
|
+
"status": "OK" if case_result.shear.is_safe else "FAIL",
|
|
141
|
+
},
|
|
142
|
+
"detailing": {
|
|
143
|
+
"bottom_bars": [
|
|
144
|
+
{
|
|
145
|
+
"count": bar.count,
|
|
146
|
+
"diameter": bar.diameter,
|
|
147
|
+
"callout": bar.callout(),
|
|
148
|
+
}
|
|
149
|
+
for bar in detailing_result.bottom_bars
|
|
150
|
+
],
|
|
151
|
+
"top_bars": [
|
|
152
|
+
{
|
|
153
|
+
"count": bar.count,
|
|
154
|
+
"diameter": bar.diameter,
|
|
155
|
+
"callout": bar.callout(),
|
|
156
|
+
}
|
|
157
|
+
for bar in detailing_result.top_bars
|
|
158
|
+
],
|
|
159
|
+
"stirrups": [
|
|
160
|
+
{
|
|
161
|
+
"diameter": stir.diameter,
|
|
162
|
+
"spacing": stir.spacing,
|
|
163
|
+
"callout": stir.callout(),
|
|
164
|
+
}
|
|
165
|
+
for stir in detailing_result.stirrups
|
|
166
|
+
],
|
|
167
|
+
"ld_tension": detailing_result.ld_tension,
|
|
168
|
+
"lap_length": detailing_result.lap_length,
|
|
169
|
+
},
|
|
170
|
+
"status": "OK" if case_result.is_ok else "FAIL",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
results.append(beam_result)
|
|
174
|
+
|
|
175
|
+
# Prepare output
|
|
176
|
+
output_data = {
|
|
177
|
+
"schema_version": 1,
|
|
178
|
+
"code": "IS456",
|
|
179
|
+
"beams": results,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Write output
|
|
183
|
+
if args.output:
|
|
184
|
+
output_path = Path(args.output)
|
|
185
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
186
|
+
|
|
187
|
+
with output_path.open("w", encoding="utf-8") as f:
|
|
188
|
+
json.dump(output_data, f, indent=2)
|
|
189
|
+
|
|
190
|
+
print(f"Design results written to {output_path}", file=sys.stderr)
|
|
191
|
+
else:
|
|
192
|
+
# Print to stdout
|
|
193
|
+
print(json.dumps(output_data, indent=2))
|
|
194
|
+
|
|
195
|
+
print(f"Design complete: {len(results)} beam(s) processed", file=sys.stderr)
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
200
|
+
import traceback
|
|
201
|
+
|
|
202
|
+
traceback.print_exc(file=sys.stderr)
|
|
203
|
+
return 1
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def cmd_bbs(args: argparse.Namespace) -> int:
|
|
207
|
+
"""
|
|
208
|
+
Generate bar bending schedule from design results JSON.
|
|
209
|
+
|
|
210
|
+
Reads design results and generates a detailed bar bending schedule
|
|
211
|
+
with cut lengths, weights, and bar marks.
|
|
212
|
+
"""
|
|
213
|
+
input_path = Path(args.input)
|
|
214
|
+
|
|
215
|
+
if not input_path.exists():
|
|
216
|
+
print(f"Error: Input file not found: {input_path}", file=sys.stderr)
|
|
217
|
+
return 1
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
# Load design results
|
|
221
|
+
print(f"Loading design results from {input_path}...", file=sys.stderr)
|
|
222
|
+
|
|
223
|
+
with input_path.open("r", encoding="utf-8") as f:
|
|
224
|
+
data = json.load(f)
|
|
225
|
+
|
|
226
|
+
beams = data.get("beams", [])
|
|
227
|
+
if not beams:
|
|
228
|
+
print("Error: No beams found in input file", file=sys.stderr)
|
|
229
|
+
return 1
|
|
230
|
+
|
|
231
|
+
print(f"Loaded {len(beams)} beam(s)", file=sys.stderr)
|
|
232
|
+
|
|
233
|
+
# Generate detailing results for BBS
|
|
234
|
+
detailing_list = []
|
|
235
|
+
for beam in beams:
|
|
236
|
+
print(f" Processing {beam['story']}/{beam['beam_id']}...", file=sys.stderr)
|
|
237
|
+
|
|
238
|
+
# Reconstruct detailing from design results
|
|
239
|
+
geom = beam["geometry"]
|
|
240
|
+
mat = beam["materials"]
|
|
241
|
+
det = beam["detailing"]
|
|
242
|
+
|
|
243
|
+
# Create simplified detailing result for BBS
|
|
244
|
+
detailing_result = detailing.create_beam_detailing(
|
|
245
|
+
beam_id=beam["beam_id"],
|
|
246
|
+
story=beam["story"],
|
|
247
|
+
b=geom["b"],
|
|
248
|
+
D=geom["D"],
|
|
249
|
+
span=geom["span"],
|
|
250
|
+
cover=geom["cover"],
|
|
251
|
+
fck=mat["fck"],
|
|
252
|
+
fy=mat["fy"],
|
|
253
|
+
ast_start=beam["flexure"]["ast_req"],
|
|
254
|
+
ast_mid=beam["flexure"]["ast_req"],
|
|
255
|
+
ast_end=beam["flexure"]["ast_req"],
|
|
256
|
+
asc_start=beam["flexure"].get("asc_req", 0),
|
|
257
|
+
asc_mid=beam["flexure"].get("asc_req", 0),
|
|
258
|
+
asc_end=beam["flexure"].get("asc_req", 0),
|
|
259
|
+
stirrup_dia=det["stirrups"][0]["diameter"] if det["stirrups"] else 8,
|
|
260
|
+
stirrup_spacing_start=(
|
|
261
|
+
det["stirrups"][0]["spacing"] if det["stirrups"] else 150
|
|
262
|
+
),
|
|
263
|
+
stirrup_spacing_mid=(
|
|
264
|
+
det["stirrups"][1]["spacing"] if len(det["stirrups"]) > 1 else 200
|
|
265
|
+
),
|
|
266
|
+
stirrup_spacing_end=(
|
|
267
|
+
det["stirrups"][2]["spacing"] if len(det["stirrups"]) > 2 else 150
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
detailing_list.append(detailing_result)
|
|
272
|
+
|
|
273
|
+
# Generate BBS document
|
|
274
|
+
print("Generating bar bending schedule...", file=sys.stderr)
|
|
275
|
+
bbs_doc = bbs.generate_bbs_document(
|
|
276
|
+
detailing_list, project_name=data.get("project_name", "Beam Design BBS")
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# Write output
|
|
280
|
+
if args.output:
|
|
281
|
+
output_path = Path(args.output)
|
|
282
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
283
|
+
|
|
284
|
+
if output_path.suffix.lower() == ".json":
|
|
285
|
+
bbs.export_bbs_to_json(bbs_doc, str(output_path))
|
|
286
|
+
else:
|
|
287
|
+
# Default to CSV
|
|
288
|
+
bbs.export_bbs_to_csv(bbs_doc.items, str(output_path))
|
|
289
|
+
|
|
290
|
+
print(f"Bar bending schedule written to {output_path}", file=sys.stderr)
|
|
291
|
+
else:
|
|
292
|
+
# Print CSV to stdout
|
|
293
|
+
import csv
|
|
294
|
+
import io
|
|
295
|
+
|
|
296
|
+
output = io.StringIO()
|
|
297
|
+
fieldnames = [
|
|
298
|
+
"bar_mark",
|
|
299
|
+
"member_id",
|
|
300
|
+
"location",
|
|
301
|
+
"zone",
|
|
302
|
+
"shape_code",
|
|
303
|
+
"diameter_mm",
|
|
304
|
+
"no_of_bars",
|
|
305
|
+
"cut_length_mm",
|
|
306
|
+
"total_length_mm",
|
|
307
|
+
"unit_weight_kg",
|
|
308
|
+
"total_weight_kg",
|
|
309
|
+
"remarks",
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
writer = csv.DictWriter(output, fieldnames=fieldnames)
|
|
313
|
+
writer.writeheader()
|
|
314
|
+
|
|
315
|
+
for item in bbs_doc.items:
|
|
316
|
+
writer.writerow(
|
|
317
|
+
{
|
|
318
|
+
"bar_mark": item.bar_mark,
|
|
319
|
+
"member_id": item.member_id,
|
|
320
|
+
"location": item.location,
|
|
321
|
+
"zone": item.zone,
|
|
322
|
+
"shape_code": item.shape_code,
|
|
323
|
+
"diameter_mm": item.diameter_mm,
|
|
324
|
+
"no_of_bars": item.no_of_bars,
|
|
325
|
+
"cut_length_mm": item.cut_length_mm,
|
|
326
|
+
"total_length_mm": item.total_length_mm,
|
|
327
|
+
"unit_weight_kg": item.unit_weight_kg,
|
|
328
|
+
"total_weight_kg": item.total_weight_kg,
|
|
329
|
+
"remarks": item.remarks,
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
print(output.getvalue())
|
|
334
|
+
|
|
335
|
+
print(
|
|
336
|
+
f"BBS complete: {bbs_doc.summary.total_bars} bars, "
|
|
337
|
+
f"{bbs_doc.summary.total_weight_kg:.2f} kg",
|
|
338
|
+
file=sys.stderr,
|
|
339
|
+
)
|
|
340
|
+
return 0
|
|
341
|
+
|
|
342
|
+
except Exception as e:
|
|
343
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
344
|
+
import traceback
|
|
345
|
+
|
|
346
|
+
traceback.print_exc(file=sys.stderr)
|
|
347
|
+
return 1
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def cmd_dxf(args: argparse.Namespace) -> int:
|
|
351
|
+
"""
|
|
352
|
+
Generate DXF drawings from design results JSON.
|
|
353
|
+
|
|
354
|
+
Creates detailed reinforcement drawings in DXF format suitable
|
|
355
|
+
for CAD software and fabrication.
|
|
356
|
+
"""
|
|
357
|
+
input_path = Path(args.input)
|
|
358
|
+
|
|
359
|
+
if not input_path.exists():
|
|
360
|
+
print(f"Error: Input file not found: {input_path}", file=sys.stderr)
|
|
361
|
+
return 1
|
|
362
|
+
|
|
363
|
+
# Check if dxf_export module is available
|
|
364
|
+
if dxf_export is None:
|
|
365
|
+
print("Error: dxf_export module not available", file=sys.stderr)
|
|
366
|
+
print("Install with: pip install ezdxf", file=sys.stderr)
|
|
367
|
+
return 1
|
|
368
|
+
|
|
369
|
+
# Check if ezdxf is available
|
|
370
|
+
if not dxf_export.EZDXF_AVAILABLE:
|
|
371
|
+
print("Error: ezdxf library not installed", file=sys.stderr)
|
|
372
|
+
print("Install with: pip install ezdxf", file=sys.stderr)
|
|
373
|
+
return 1
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
# Load design results
|
|
377
|
+
print(f"Loading design results from {input_path}...", file=sys.stderr)
|
|
378
|
+
|
|
379
|
+
with input_path.open("r", encoding="utf-8") as f:
|
|
380
|
+
data = json.load(f)
|
|
381
|
+
|
|
382
|
+
beams = data.get("beams", [])
|
|
383
|
+
if not beams:
|
|
384
|
+
print("Error: No beams found in input file", file=sys.stderr)
|
|
385
|
+
return 1
|
|
386
|
+
|
|
387
|
+
print(f"Loaded {len(beams)} beam(s)", file=sys.stderr)
|
|
388
|
+
|
|
389
|
+
# Generate detailing results for DXF
|
|
390
|
+
detailing_list = []
|
|
391
|
+
for beam in beams:
|
|
392
|
+
print(f" Processing {beam['story']}/{beam['beam_id']}...", file=sys.stderr)
|
|
393
|
+
|
|
394
|
+
geom = beam["geometry"]
|
|
395
|
+
mat = beam["materials"]
|
|
396
|
+
det = beam["detailing"]
|
|
397
|
+
|
|
398
|
+
detailing_result = detailing.create_beam_detailing(
|
|
399
|
+
beam_id=beam["beam_id"],
|
|
400
|
+
story=beam["story"],
|
|
401
|
+
b=geom["b"],
|
|
402
|
+
D=geom["D"],
|
|
403
|
+
span=geom["span"],
|
|
404
|
+
cover=geom["cover"],
|
|
405
|
+
fck=mat["fck"],
|
|
406
|
+
fy=mat["fy"],
|
|
407
|
+
ast_start=beam["flexure"]["ast_req"],
|
|
408
|
+
ast_mid=beam["flexure"]["ast_req"],
|
|
409
|
+
ast_end=beam["flexure"]["ast_req"],
|
|
410
|
+
asc_start=beam["flexure"].get("asc_req", 0),
|
|
411
|
+
asc_mid=beam["flexure"].get("asc_req", 0),
|
|
412
|
+
asc_end=beam["flexure"].get("asc_req", 0),
|
|
413
|
+
stirrup_dia=det["stirrups"][0]["diameter"] if det["stirrups"] else 8,
|
|
414
|
+
stirrup_spacing_start=(
|
|
415
|
+
det["stirrups"][0]["spacing"] if det["stirrups"] else 150
|
|
416
|
+
),
|
|
417
|
+
stirrup_spacing_mid=(
|
|
418
|
+
det["stirrups"][1]["spacing"] if len(det["stirrups"]) > 1 else 200
|
|
419
|
+
),
|
|
420
|
+
stirrup_spacing_end=(
|
|
421
|
+
det["stirrups"][2]["spacing"] if len(det["stirrups"]) > 2 else 150
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
detailing_list.append(detailing_result)
|
|
426
|
+
|
|
427
|
+
# Generate DXF
|
|
428
|
+
if not args.output:
|
|
429
|
+
print(
|
|
430
|
+
"Error: Output file path is required for DXF generation",
|
|
431
|
+
file=sys.stderr,
|
|
432
|
+
)
|
|
433
|
+
return 1
|
|
434
|
+
|
|
435
|
+
output_path = Path(args.output)
|
|
436
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
437
|
+
|
|
438
|
+
print("Generating DXF drawings...", file=sys.stderr)
|
|
439
|
+
|
|
440
|
+
if len(detailing_list) == 1:
|
|
441
|
+
# Single beam - use standard function
|
|
442
|
+
dxf_export.generate_beam_dxf(detailing_list[0], str(output_path))
|
|
443
|
+
else:
|
|
444
|
+
# Multiple beams - use multi-beam layout
|
|
445
|
+
dxf_export.generate_multi_beam_dxf(detailing_list, str(output_path))
|
|
446
|
+
|
|
447
|
+
print(f"DXF drawings written to {output_path}", file=sys.stderr)
|
|
448
|
+
print(f"DXF complete: {len(detailing_list)} beam(s) drawn", file=sys.stderr)
|
|
449
|
+
return 0
|
|
450
|
+
|
|
451
|
+
except Exception as e:
|
|
452
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
453
|
+
import traceback
|
|
454
|
+
|
|
455
|
+
traceback.print_exc(file=sys.stderr)
|
|
456
|
+
return 1
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def cmd_job(args: argparse.Namespace) -> int:
|
|
460
|
+
"""
|
|
461
|
+
Run complete job from JSON specification.
|
|
462
|
+
|
|
463
|
+
Executes a full job including design calculations, BBS generation,
|
|
464
|
+
and optional DXF drawing generation.
|
|
465
|
+
"""
|
|
466
|
+
input_path = Path(args.input)
|
|
467
|
+
|
|
468
|
+
if not input_path.exists():
|
|
469
|
+
print(f"Error: Input file not found: {input_path}", file=sys.stderr)
|
|
470
|
+
return 1
|
|
471
|
+
|
|
472
|
+
if not args.output:
|
|
473
|
+
print("Error: Output directory is required for job processing", file=sys.stderr)
|
|
474
|
+
return 1
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
print(f"Running job from {input_path}...", file=sys.stderr)
|
|
478
|
+
|
|
479
|
+
# Use existing job_runner
|
|
480
|
+
job_runner.run_job(job_path=str(input_path), out_dir=args.output)
|
|
481
|
+
|
|
482
|
+
print(f"Job complete: outputs written to {args.output}", file=sys.stderr)
|
|
483
|
+
return 0
|
|
484
|
+
|
|
485
|
+
except Exception as e:
|
|
486
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
487
|
+
import traceback
|
|
488
|
+
|
|
489
|
+
traceback.print_exc(file=sys.stderr)
|
|
490
|
+
return 1
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
494
|
+
"""Build the main argument parser with subcommands."""
|
|
495
|
+
|
|
496
|
+
parser = argparse.ArgumentParser(
|
|
497
|
+
prog="structural_lib",
|
|
498
|
+
description="IS 456 RC Beam Design Library - Unified CLI",
|
|
499
|
+
epilog='Use "python -m structural_lib <command> --help" for command-specific help',
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
subparsers = parser.add_subparsers(
|
|
503
|
+
dest="command", required=True, help="Available commands"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Design subcommand
|
|
507
|
+
design_parser = subparsers.add_parser(
|
|
508
|
+
"design",
|
|
509
|
+
help="Run beam design from CSV/JSON input",
|
|
510
|
+
description="""
|
|
511
|
+
Run beam design calculations from CSV or JSON input file.
|
|
512
|
+
|
|
513
|
+
Examples:
|
|
514
|
+
python -m structural_lib design input.csv -o results.json
|
|
515
|
+
python -m structural_lib design beams.json -o design_output.json
|
|
516
|
+
""",
|
|
517
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
518
|
+
)
|
|
519
|
+
design_parser.add_argument(
|
|
520
|
+
"input", help="Input CSV or JSON file with beam parameters"
|
|
521
|
+
)
|
|
522
|
+
design_parser.add_argument(
|
|
523
|
+
"-o", "--output", help="Output JSON file (if omitted, prints to stdout)"
|
|
524
|
+
)
|
|
525
|
+
design_parser.set_defaults(func=cmd_design)
|
|
526
|
+
|
|
527
|
+
# BBS subcommand
|
|
528
|
+
bbs_parser = subparsers.add_parser(
|
|
529
|
+
"bbs",
|
|
530
|
+
help="Generate bar bending schedule from design results",
|
|
531
|
+
description="""
|
|
532
|
+
Generate bar bending schedule (BBS) from design results JSON.
|
|
533
|
+
|
|
534
|
+
Examples:
|
|
535
|
+
python -m structural_lib bbs results.json -o bbs.csv
|
|
536
|
+
python -m structural_lib bbs results.json -o bbs.json
|
|
537
|
+
""",
|
|
538
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
539
|
+
)
|
|
540
|
+
bbs_parser.add_argument("input", help="Input JSON file with design results")
|
|
541
|
+
bbs_parser.add_argument(
|
|
542
|
+
"-o",
|
|
543
|
+
"--output",
|
|
544
|
+
help="Output CSV or JSON file (if omitted, prints CSV to stdout)",
|
|
545
|
+
)
|
|
546
|
+
bbs_parser.set_defaults(func=cmd_bbs)
|
|
547
|
+
|
|
548
|
+
# DXF subcommand
|
|
549
|
+
dxf_parser = subparsers.add_parser(
|
|
550
|
+
"dxf",
|
|
551
|
+
help="Generate DXF drawings from design results",
|
|
552
|
+
description="""
|
|
553
|
+
Generate DXF reinforcement drawings from design results JSON.
|
|
554
|
+
Requires ezdxf library: pip install ezdxf
|
|
555
|
+
|
|
556
|
+
Examples:
|
|
557
|
+
python -m structural_lib dxf results.json -o drawings.dxf
|
|
558
|
+
python -m structural_lib dxf design_output.json -o beam_details.dxf
|
|
559
|
+
""",
|
|
560
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
561
|
+
)
|
|
562
|
+
dxf_parser.add_argument("input", help="Input JSON file with design results")
|
|
563
|
+
dxf_parser.add_argument(
|
|
564
|
+
"-o", "--output", required=True, help="Output DXF file path"
|
|
565
|
+
)
|
|
566
|
+
dxf_parser.set_defaults(func=cmd_dxf)
|
|
567
|
+
|
|
568
|
+
# Job subcommand
|
|
569
|
+
job_parser = subparsers.add_parser(
|
|
570
|
+
"job",
|
|
571
|
+
help="Run complete job from JSON specification",
|
|
572
|
+
description="""
|
|
573
|
+
Run a complete job including design, analysis, and report generation.
|
|
574
|
+
|
|
575
|
+
Examples:
|
|
576
|
+
python -m structural_lib job job.json -o output/
|
|
577
|
+
python -m structural_lib job project_spec.json -o results/
|
|
578
|
+
""",
|
|
579
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
580
|
+
)
|
|
581
|
+
job_parser.add_argument("input", help="Input JSON job specification file")
|
|
582
|
+
job_parser.add_argument(
|
|
583
|
+
"-o", "--output", required=True, help="Output directory for job results"
|
|
584
|
+
)
|
|
585
|
+
job_parser.set_defaults(func=cmd_job)
|
|
586
|
+
|
|
587
|
+
return parser
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def main(argv: List[str] | None = None) -> int:
|
|
591
|
+
"""Main entry point for the CLI."""
|
|
592
|
+
parser = _build_parser()
|
|
593
|
+
args = parser.parse_args(argv)
|
|
594
|
+
|
|
595
|
+
# Call the appropriate command function
|
|
596
|
+
return args.func(args)
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
if __name__ == "__main__":
|
|
600
|
+
sys.exit(main())
|