Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
238 changes: 238 additions & 0 deletions src/osdag/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
from osdag.design_type.connection.fin_plate_connection import FinPlateConnection
from osdag.design_type.connection.cleat_angle_connection import CleatAngleConnection
from osdag.design_type.connection.seated_angle_connection import SeatedAngleConnection
from osdag.design_type.connection.end_plate_connection import EndPlateConnection
from osdag.design_type.connection.base_plate_connection import BasePlateConnection
from osdag.design_type.connection.beam_cover_plate import BeamCoverPlate
from osdag.design_type.connection.beam_cover_plate_weld import BeamCoverPlateWeld
from osdag.design_type.connection.column_cover_plate_weld import ColumnCoverPlateWeld
from osdag.design_type.tension_member.tension_bolted import Tension_bolted
from osdag.design_type.tension_member.tension_welded import Tension_welded
from osdag.design_type.connection.beam_beam_end_plate_splice import BeamBeamEndPlateSplice
from osdag.design_type.connection.beam_column_end_plate import BeamColumnEndPlate
from osdag.design_type.connection.column_cover_plate import ColumnCoverPlate
from osdag.design_type.connection.column_end_plate import ColumnEndPlate
from osdag.design_type.compression_member.compression import Compression
from osdag.design_type.main import Main
from osdag.Common import TYPE_TEXTBOX, TYPE_OUT_BUTTON
from osdag.Common import (
# Shear Connection
KEY_DISP_FINPLATE,
KEY_DISP_ENDPLATE,
KEY_DISP_CLEATANGLE,
KEY_DISP_SEATED_ANGLE,

# Base Plate Connection
KEY_DISP_BASE_PLATE,

# Moment Connection
KEY_DISP_BEAMCOVERPLATE,
KEY_DISP_COLUMNCOVERPLATE,
KEY_DISP_BEAMCOVERPLATEWELD,
KEY_DISP_COLUMNCOVERPLATEWELD,
KEY_DISP_BB_EP_SPLICE,
KEY_DISP_COLUMNENDPLATE,
KEY_DISP_BCENDPLATE,

# Tension Member
KEY_DISP_TENSION_BOLTED,
KEY_DISP_TENSION_WELDED,

# Compression Member
KEY_DISP_COMPRESSION

)


available_modules = {
KEY_DISP_BASE_PLATE:BasePlateConnection,
KEY_DISP_BEAMCOVERPLATE:BeamCoverPlate,
KEY_DISP_CLEATANGLE:CleatAngleConnection,
KEY_DISP_COLUMNCOVERPLATE:ColumnCoverPlate,
KEY_DISP_COLUMNENDPLATE:ColumnEndPlate,
KEY_DISP_ENDPLATE:EndPlateConnection,
KEY_DISP_FINPLATE:FinPlateConnection,
KEY_DISP_SEATED_ANGLE:SeatedAngleConnection,
KEY_DISP_TENSION_BOLTED:Tension_bolted,
KEY_DISP_TENSION_WELDED:Tension_welded,
KEY_DISP_COMPRESSION:Compression,
KEY_DISP_BEAMCOVERPLATEWELD:BeamCoverPlateWeld,
KEY_DISP_COLUMNCOVERPLATEWELD:ColumnCoverPlateWeld,
KEY_DISP_BB_EP_SPLICE:BeamBeamEndPlateSplice,
KEY_DISP_BCENDPLATE:BeamColumnEndPlate,
}

from pathlib import Path
import yaml, click
import pandas as pd

def _print_result(out_dict:dict) -> None:
"""print the output dictionary to the console"""
print("="*100)
for key, value in out_dict.items():
print(f"|| {key}: {value}")
print("="*100)

def _get_design_dictionary(osi_path:Path) -> dict:
"""return the design dictionary from an OSI file."""
with open(osi_path, 'r') as file:
return yaml.safe_load(file)

def _get_module_class(design_dict:dict) -> Main:
"""return the module class from the design dictionary."""
module_name = design_dict.get("Module")
if not module_name:
raise ValueError("Module not specified.")
module_class = available_modules.get(module_name)
if not module_class:
raise ValueError(f"Not a valid module class: {module_name}")
return module_class

def _get_output_dictionary(module_class:Main) -> dict:
"""return the output dictionary for the design"""
status = module_class.design_status
out_list = module_class.output_values(module_class, status)
out_dict = {"Parameter": "Value"}
for option in out_list:
# ('Member.tension_blockshear', 'Block Shear Capacity (kN)', 'TextBox', 83.86, True)
if option[0] is not None and option[2] == TYPE_TEXTBOX:
out_dict[option[0]] = option[3]

# ('pattern1', 'Pattern', 'Output_dock_Button', ['Shear Pattern ', <function Tension_bolted.memb_pattern at 0x000002049DDBB920>], True)
if option[2] == TYPE_OUT_BUTTON:
tup = option[3]
fn = tup[1]
for item in fn(module_class, status):
lable = item[0]
value = item[3]
if lable!=None and value!=None:
out_dict[lable] = value
return out_dict


def _save_to_csv(output_dictionary:dict, output_file:str):
"""save the output dictionary to a csv file"""
df = pd.DataFrame(output_dictionary.items())
df.to_csv(output_file, index=False, header=None)

def _save_to_pdf(module_class:Main, output_file:Path) -> None:
"""save the output dictionary to a pdf file"""
popup_summary = {
'ProfileSummary': {
'CompanyName': 'LoremIpsum',
'CompanyLogo': '',
'Group/TeamName': 'LoremIpsum',
'Designer': 'LoremIpsum'
},
'ProjectTitle': 'Fossee',
'Subtitle': '',
'JobNumber': '123',
'AdditionalComments': 'No comments',
'Client': 'LoremIpsum',
'input_filename': f'{output_file}',
'does_design_exist': True,
'logger_messages': ''
}
module_class.save_design(module_class, popup_summary)



def run_module(*args, **kargs) -> dict:
"""Run the module specified in the OSI file located at osi_path."""

osi_path = kargs["input_path"] if len(kargs) > 0 else None
op_type = kargs["op_type"] if len(kargs) > 1 else "print_result"
output_path = kargs["output_path"] if len(kargs) > 2 else None

result = {
"success": False,
"operation": op_type,
"input": str(osi_path) if osi_path else None,
"output": None,
"data": None,
"errors": [],
}

if osi_path is None:
result["errors"].append("No input file provided.")
print(result)
return result

osi_path = Path(osi_path) if osi_path else None
output_path = Path(output_path) if output_path else None
if not osi_path.exists():
result["errors"].append(f"File not found: {osi_path}")
print(result)
return result

design_dict = _get_design_dictionary(osi_path)
module_name = design_dict.get("Module")
if not module_name:
result["errors"].append("Module not specified.")
print(result)
return result

module_class = available_modules.get(module_name)
if not module_class:
result["errors"].append(f"Not a valid module class: {module_name}")
print(result)
return result

input_filename = osi_path.stem
output_filename = output_path.stem if output_path else None
if not output_path:
output_folder_path = osi_path.parent / "Outputs" / f"{module_class.__name__}"
else:
output_folder_path = output_path.parent / f"{module_class.__name__}"
output_folder_path.mkdir(parents=True, exist_ok=True)
output_file = output_folder_path / f"{output_filename if output_filename else input_filename}"

module_class.set_osdaglogger(None)
val_errors = module_class.func_for_validation(module_class, design_dict)

if val_errors:
result["errors"].extend(val_errors)
print(result)
return result

out_dict = _get_output_dictionary(module_class)
result["data"] = out_dict
if op_type == "save_csv":
try:
_save_to_csv(out_dict, str(output_file) + ".csv")
result["success"] = True
result["output"] = str(output_file)
except Exception as e:
result["success"] = False
result["errors"].append(f"Failed to save CSV: {e}")

elif op_type == "save_pdf":
try:
_save_to_pdf(module_class, output_file)
result["success"] = True
result["output"] = str(output_file)
except Exception as e:
result["success"] = False
result["errors"].append(f"Failed to save PDF: {e}")

elif op_type == "print_result":
try:
result["success"] = True
click.echo(_print_result(out_dict=out_dict))
except Exception as e:
result["success"] = False
result["errors"].append(f"Failed to get result: {e}")

else:
result["errors"].append(f"Unsupported op_type: {op_type}")

if len(result["errors"]) > 0:
print(result)

return result



# run_module(r"C:\Users\1hasa\Osdag\TensionBoltedTest4.osi",op_type="save to csv")
# run_module(r"C:\Users\1hasa\Osdag\TensionBoltedTest4.osi",op_type="save to pdf")
# print(run_module(r"C:\Users\1hasa\Osdag\TensionBoltedTest4.osi",op_type="output dictionary"))
82 changes: 79 additions & 3 deletions src/osdag/osdagMainPage.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
7) Any further Levels will result in an error .
'''

import os
from html import parser
import os, click
from pathlib import Path
import re
import io
Expand Down Expand Up @@ -131,6 +132,7 @@
from PyQt5 import QtWidgets, QtCore, QtGui
import math
import sys
from osdag.cli import run_module
from .gui.ui_tutorial import Ui_Tutorial
from .gui.ui_aboutosdag import Ui_AboutOsdag
from .gui.ui_ask_question import Ui_AskQuestion
Expand Down Expand Up @@ -975,5 +977,79 @@ def do_stuff():
except BaseException as e:
print("ERROR", e)

if __name__ == '__main__':
do_stuff()
# --- Main CLI group ---
help_msg = """\n\b
==================================================
Osdag Steel Design and Graphics Application

Usage:\n
osdag # Launch GUI (default)\n
osdag cli run # Use CLI tools (see below)

By default, running 'osdag' launches the GUI.
You can also run in CLI mode using 'osdag cli run'.

Examples:\n
osdag\n
osdag cli run -i TensionBolted.osi\n
osdag cli run -i TensionBolted.osi -op save_csv -o result.csv\n
osdag cli run -i TensionBolted.osi -op save_pdf -o result.pdf\n
osdag cli run -i TensionBolted.osi -op print_result\n
==================================================\n
"""

@click.group(invoke_without_command=True,
help="\nOsdag Application. Run osdag to launch GUI, or use 'osdag cli run' for command-line tools.\n",
epilog=help_msg,
context_settings=dict(help_option_names=['-h', '--help']),
)

@click.pass_context
def osdag(ctx):
if ctx.invoked_subcommand is None:
do_stuff()


# --- CLI group ---
@osdag.group(help="\nRun in CLI mode (use subcommands like 'run').\n",
epilog=help_msg,
context_settings=dict(help_option_names=['-h', '--help']),
)
def cli():
pass


# --- Subcommand: run ---
@cli.command(help="\nOsdag Application. Run osdag to launch GUI, or use 'osdag cli run' for command-line tools.\n",
epilog=help_msg,
context_settings=dict(help_option_names=['-h', '--help']),
)
@click.option("-i", "--input", "input_path",
type=click.Path(exists=True, dir_okay=False),
required=True,
help="Path to input file (.osi)")
@click.option("-op", "--op_type",
type=click.Choice(["save_csv", "save_pdf", "print_result"]),
default="print_result",
show_default=True,
help="Type of operation")
@click.option("-o", "--output", "output_path",
type=click.Path(dir_okay=False, writable=True),
help="Path for output file")
def run(input_path, op_type, output_path):
result = run_module(input_path=input_path,
op_type=op_type,
output_path=output_path)

if not result["success"]:
click.echo("Errors encountered:")
for err in result["errors"]:
click.echo(f" - {err}")
else:
click.echo("Operation completed successfully")
if result.get("output"):
click.echo(f"Output saved at: {result['output']}")


if __name__ == "__main__":
osdag()