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.
@@ -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())